diff --git a/CHANGELOG.md b/CHANGELOG.md index 2047caef9a..c5715a2453 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ #### :nail_care: Polish +- Omit unnecessary parentheses around coercions in call arguments, collection elements, and array indices while preserving expression grouping. https://github.com/rescript-lang/rescript/pull/8614 - Print external declarations in signatures and type errors with their processed attributes instead of the `"#rescript-external"` placeholder, and print inline constants using `@inline` syntax. https://github.com/rescript-lang/rescript/pull/8581 - Improve diagnostics for dynamic imports of local values and attempts to use `import` as a first-class value. https://github.com/rescript-lang/rescript/pull/8582 - Allow inferred labeled functions to be called with labels in any order by removing legacy curried-arrow commutation locks. https://github.com/rescript-lang/rescript/pull/8547 diff --git a/compiler/syntax/src/res_parens.ml b/compiler/syntax/src/res_parens.ml index 0c25e46853..ab67bb23b4 100644 --- a/compiler/syntax/src/res_parens.ml +++ b/compiler/syntax/src/res_parens.ml @@ -1,7 +1,7 @@ module Parsetree_viewer = Res_parsetree_viewer type kind = Parenthesized | Braced of Location.t | Nothing -let expr expr = +let expr ?(allow_coercion = false) expr = let opt_braces, _ = Parsetree_viewer.process_braces_attr expr in match opt_braces with | Some ({Location.loc = braces_loc}, _) -> Braced braces_loc @@ -12,7 +12,8 @@ let expr expr = Pexp_constraint ({pexp_desc = Pexp_pack _}, {ptyp_desc = Ptyp_package _}); } -> Nothing - | {pexp_desc = Pexp_constraint _} -> Parenthesized + | {pexp_desc = Pexp_coerce _} when allow_coercion -> Nothing + | {pexp_desc = Pexp_constraint _ | Pexp_coerce _} -> Parenthesized | _ -> Nothing) let expr_record_row_rhs ~optional e = @@ -50,9 +51,9 @@ let call_expr expr = Nothing | { pexp_desc = - ( Pexp_assert _ | Pexp_fun _ | Pexp_constraint _ | Pexp_setfield _ - | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_for_of _ - | Pexp_for_await_of _ | Pexp_ifthenelse _ ); + ( Pexp_assert _ | Pexp_fun _ | Pexp_constraint _ | Pexp_coerce _ + | Pexp_setfield _ | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ + | Pexp_for_of _ | Pexp_for_await_of _ | Pexp_ifthenelse _ ); } -> Parenthesized | _ when Parsetree_viewer.expr_is_await expr -> Parenthesized @@ -72,7 +73,7 @@ let structure_expr expr = Pexp_constraint ({pexp_desc = Pexp_pack _}, {ptyp_desc = Ptyp_package _}); } -> Nothing - | {pexp_desc = Pexp_constraint _} -> Parenthesized + | {pexp_desc = Pexp_constraint _ | Pexp_coerce _} -> Parenthesized | _ -> Nothing) let unary_expr_operand expr = @@ -100,8 +101,8 @@ let unary_expr_operand expr = Nothing | { pexp_desc = - ( Pexp_assert _ | Pexp_fun _ | Pexp_constraint _ | Pexp_setfield _ - | Pexp_extension _ (* readability? maybe remove *) + ( Pexp_assert _ | Pexp_fun _ | Pexp_constraint _ | Pexp_coerce _ + | Pexp_setfield _ | Pexp_extension _ (* readability? maybe remove *) | Pexp_object_literal _ (* ({"a": 1})["a"] *) | Pexp_object_set _ (* (o["x"] = v)["y"] *) | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_for_of _ | Pexp_for_await_of _ @@ -125,7 +126,8 @@ let binary_expr_operand ~is_lhs expr = | {pexp_desc = Pexp_fun _} when Parsetree_viewer.is_underscore_apply_sugar expr -> Nothing - | {pexp_desc = Pexp_constraint _ | Pexp_fun _} -> Parenthesized + | {pexp_desc = Pexp_constraint _ | Pexp_coerce _ | Pexp_fun _} -> + Parenthesized | expr when Parsetree_viewer.is_binary_expression expr -> Parenthesized | expr when Parsetree_viewer.is_ternary_expr expr -> Parenthesized | {pexp_desc = Pexp_assert _} when is_lhs -> Parenthesized @@ -182,7 +184,7 @@ let flatten_operand_rhs parent_operator rhs = false | Pexp_fun {params = {p_pat = {ppat_desc = Ppat_var {txt = "__x"}}} :: _} -> false - | Pexp_fun _ | Pexp_setfield _ | Pexp_constraint _ -> true + | Pexp_fun _ | Pexp_setfield _ | Pexp_constraint _ | Pexp_coerce _ -> true | _ when Parsetree_viewer.is_ternary_expr rhs -> true | _ -> false @@ -220,9 +222,9 @@ let assert_or_await_expr_rhs ?(in_await = false) expr = Nothing | { pexp_desc = - ( Pexp_assert _ | Pexp_fun _ | Pexp_constraint _ | Pexp_setfield _ - | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_for_of _ - | Pexp_for_await_of _ | Pexp_ifthenelse _ ); + ( Pexp_assert _ | Pexp_fun _ | Pexp_constraint _ | Pexp_coerce _ + | Pexp_setfield _ | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ + | Pexp_for_of _ | Pexp_for_await_of _ | Pexp_ifthenelse _ ); } -> Parenthesized | _ when (not in_await) && Parsetree_viewer.expr_is_await expr -> @@ -267,9 +269,9 @@ let field_expr expr = pexp_desc = ( Pexp_assert _ | Pexp_extension _ (* %extension.x vs (%extension).x *) | Pexp_object_literal _ (* ({"a": 1})["a"] *) | Pexp_fun _ - | Pexp_constraint _ | Pexp_setfield _ | Pexp_match _ | Pexp_try _ - | Pexp_while _ | Pexp_for _ | Pexp_for_of _ | Pexp_for_await_of _ - | Pexp_ifthenelse _ ); + | Pexp_constraint _ | Pexp_coerce _ | Pexp_setfield _ | Pexp_match _ + | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_for_of _ + | Pexp_for_await_of _ | Pexp_ifthenelse _ ); } -> Parenthesized | _ when Parsetree_viewer.expr_is_await expr -> Parenthesized @@ -286,11 +288,11 @@ let ternary_operand expr = Pexp_constraint ({pexp_desc = Pexp_pack _}, {ptyp_desc = Ptyp_package _}); } -> Nothing - | {pexp_desc = Pexp_constraint _} -> Parenthesized + | {pexp_desc = Pexp_constraint _ | Pexp_coerce _} -> Parenthesized | _ when Res_parsetree_viewer.is_fun_expr expr -> ( let _, _parameters, return_expr = Parsetree_viewer.fun_expr expr in match return_expr.pexp_desc with - | Pexp_constraint _ -> Parenthesized + | Pexp_constraint _ | Pexp_coerce _ -> Parenthesized | _ -> Nothing) | _ -> Nothing) diff --git a/compiler/syntax/src/res_parens.mli b/compiler/syntax/src/res_parens.mli index 3ce0218c84..74022a87fb 100644 --- a/compiler/syntax/src/res_parens.mli +++ b/compiler/syntax/src/res_parens.mli @@ -1,6 +1,9 @@ type kind = Parenthesized | Braced of Location.t | Nothing -val expr : Parsetree.expression -> kind +(* Set [allow_coercion] to [true] only in grammar positions accepting a trailing + coercion without parentheses, such as call arguments and collection elements. + Bindings and arrow bodies require parentheses. *) +val expr : ?allow_coercion:bool -> Parsetree.expression -> kind val structure_expr : Parsetree.expression -> kind val unary_expr_operand : Parsetree.expression -> kind diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index 35b5087f2a..1f364890c5 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -1698,7 +1698,7 @@ and print_spread_dict_expr ~state parts (expr : Parsetree.expression) cmt_tbl = in let spread_doc = let doc = print_expression ~state spread_expr cmt_tbl in - match Parens.expr spread_expr with + match Parens.expr ~allow_coercion:true spread_expr with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc spread_expr braces | Nothing -> doc @@ -2394,7 +2394,7 @@ and print_value_binding ~state ~rec_flag (vb : Parsetree.value_binding) cmt_tbl print_typ_expr ~state pvc_type cmt_tbl; Doc.text " ="; Doc.line; - print_expression_with_comments ~state expr cmt_tbl; + print_expression_with_comments_and_parens ~state expr cmt_tbl; ]); ]) | { @@ -2438,7 +2438,8 @@ and print_value_binding ~state ~rec_flag (vb : Parsetree.value_binding) cmt_tbl Doc.concat [ Doc.line; - print_expression_with_comments ~state expr cmt_tbl; + print_expression_with_comments_and_parens ~state expr + cmt_tbl; ]; ]); ]) @@ -2465,7 +2466,8 @@ and print_value_binding ~state ~rec_flag (vb : Parsetree.value_binding) cmt_tbl Doc.concat [ Doc.line; - print_expression_with_comments ~state expr cmt_tbl; + print_expression_with_comments_and_parens ~state expr + cmt_tbl; ]; ]); ])) @@ -3060,6 +3062,13 @@ and print_expression_with_comments ~state expr cmt_tbl : Doc.t = let doc = print_expression ~state expr cmt_tbl in print_comments doc cmt_tbl expr.Parsetree.pexp_loc +and print_expression_with_comments_and_parens ~state expr cmt_tbl = + let doc = print_expression_with_comments ~state expr cmt_tbl in + match Parens.expr expr with + | Parens.Parenthesized -> add_parens doc + | Braced braces -> print_braces doc expr braces + | Nothing -> doc + and print_if_chain ~state pexp_attributes ifs else_expr cmt_tbl = let if_docs = Doc.join ~sep:Doc.space @@ -3244,7 +3253,7 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = Doc.line; Doc.dotdotdot; (let doc = print_expression_with_comments ~state expr cmt_tbl in - match Parens.expr expr with + match Parens.expr ~allow_coercion:true expr with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc expr braces | Nothing -> doc); @@ -3266,7 +3275,7 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = let doc = print_expression_with_comments ~state expr cmt_tbl in - match Parens.expr expr with + match Parens.expr ~allow_coercion:true expr with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc expr braces | Nothing -> doc) @@ -3291,7 +3300,7 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = [ Doc.lparen; (let doc = print_expression_with_comments ~state arg cmt_tbl in - match Parens.expr arg with + match Parens.expr ~allow_coercion:true arg with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc arg braces | Nothing -> doc); @@ -3312,7 +3321,7 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = let doc = print_expression_with_comments ~state expr cmt_tbl in - match Parens.expr expr with + match Parens.expr ~allow_coercion:true expr with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc expr braces | Nothing -> doc) @@ -3325,7 +3334,7 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = | Some arg -> let arg_doc = let doc = print_expression_with_comments ~state arg cmt_tbl in - match Parens.expr arg with + match Parens.expr ~allow_coercion:true arg with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc arg braces | Nothing -> doc @@ -3363,7 +3372,7 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = let doc = print_expression_with_comments ~state expr cmt_tbl in - match Parens.expr expr with + match Parens.expr ~allow_coercion:true expr with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc expr braces | Nothing -> doc) @@ -3392,7 +3401,7 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = let doc = print_expression_with_comments ~state expr cmt_tbl in - match Parens.expr expr with + match Parens.expr ~allow_coercion:true expr with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc expr braces | Nothing -> doc) @@ -3418,7 +3427,7 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = [ Doc.lparen; (let doc = print_expression_with_comments ~state arg cmt_tbl in - match Parens.expr arg with + match Parens.expr ~allow_coercion:true arg with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc arg braces | Nothing -> doc); @@ -3439,7 +3448,7 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = let doc = print_expression_with_comments ~state expr cmt_tbl in - match Parens.expr expr with + match Parens.expr ~allow_coercion:true expr with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc expr braces | Nothing -> doc) @@ -3452,7 +3461,7 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = | Some arg -> let arg_doc = let doc = print_expression_with_comments ~state arg cmt_tbl in - match Parens.expr arg with + match Parens.expr ~allow_coercion:true arg with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc arg braces | Nothing -> doc @@ -3491,7 +3500,7 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = Doc.concat [ Doc.dotdotdot; - (match Parens.expr expr with + (match Parens.expr ~allow_coercion:true expr with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc expr braces | Nothing -> doc); @@ -3843,7 +3852,17 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = | Pexp_coerce (expr, (), typ) -> let doc_expr = print_expression_with_comments ~state expr cmt_tbl in let doc_typ = print_typ_expr ~state typ cmt_tbl in - Doc.concat [Doc.lparen; doc_expr; Doc.text " :> "; doc_typ; Doc.rparen] + let doc_expr = + match Parens.expr expr with + | Parens.Parenthesized -> add_parens doc_expr + | Braced braces -> print_braces doc_expr expr braces + | Nothing -> doc_expr + in + let doc = Doc.concat [doc_expr; Doc.text " :> "; doc_typ] in + (* Keep attributes on the coercion rather than its operand. *) + if Parsetree_viewer.has_printable_attributes e.pexp_attributes then + add_parens doc + else doc | Pexp_object_get (parent_expr, label) -> print_object_get_doc ~state parent_expr label cmt_tbl | Pexp_object_set (obj, member, rhs) -> @@ -4344,7 +4363,7 @@ and print_array_spread_apply ~state sub_lists cmt_tbl = (* Print expression without leading comments (they're already extracted) *) let expr_doc = let doc = print_expression ~state expr cmt_tbl in - match Parens.expr expr with + match Parens.expr ~allow_coercion:true expr with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc expr braces | Nothing -> doc @@ -4376,7 +4395,7 @@ and print_array_spread_apply ~state sub_lists cmt_tbl = (List.map (fun expr -> let doc = print_expression_with_comments ~state expr cmt_tbl in - match Parens.expr expr with + match Parens.expr ~allow_coercion:true expr with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc expr braces | Nothing -> doc) @@ -4411,7 +4430,7 @@ and print_list_spread_apply ~state sub_lists cmt_tbl = comma_before_spread; Doc.dotdotdot; (let doc = print_expression_with_comments ~state expr cmt_tbl in - match Parens.expr expr with + match Parens.expr ~allow_coercion:true expr with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc expr braces | Nothing -> doc); @@ -4432,7 +4451,7 @@ and print_list_spread_apply ~state sub_lists cmt_tbl = (List.map (fun expr -> let doc = print_expression_with_comments ~state expr cmt_tbl in - match Parens.expr expr with + match Parens.expr ~allow_coercion:true expr with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc expr braces | Nothing -> doc) @@ -4532,7 +4551,7 @@ and print_pexp_apply ~state expr cmt_tbl = let member = let member_doc = let doc = print_expression_with_comments ~state member_expr cmt_tbl in - match Parens.expr member_expr with + match Parens.expr ~allow_coercion:true member_expr with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc member_expr braces | Nothing -> doc @@ -4579,7 +4598,7 @@ and print_pexp_apply ~state expr cmt_tbl = let member = let member_doc = let doc = print_expression_with_comments ~state member_expr cmt_tbl in - match Parens.expr member_expr with + match Parens.expr ~allow_coercion:true member_expr with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc member_expr braces | Nothing -> doc @@ -5004,7 +5023,7 @@ and print_jsx_prop ~state prop cmt_tbl = [ Doc.lbrace; Doc.dotdotdot; - print_expression_with_comments ~state value cmt_tbl; + print_expression_with_comments_and_parens ~state value cmt_tbl; Doc.rbrace; ]) in @@ -5247,7 +5266,7 @@ and print_arguments ~state ~partial | [(Nolabel, arg)] when Parsetree_viewer.is_huggable_expression arg -> let arg_doc = let doc = print_expression_with_comments ~state arg cmt_tbl in - match Parens.expr arg with + match Parens.expr ~allow_coercion:true arg with | Parens.Parenthesized -> add_parens doc | Braced braces -> print_braces doc arg braces | Nothing -> doc @@ -5350,7 +5369,7 @@ and print_argument ~state (arg_lbl, arg) cmt_tbl = in let printed_expr = let doc = print_expression_with_comments ~state expr cmt_tbl in - match Parens.expr expr with + match Parens.expr ~allow_coercion:true expr with | Parenthesized -> add_parens doc | Braced braces -> print_braces doc expr braces | Nothing -> doc @@ -5408,7 +5427,7 @@ and print_case ~state (case : Parsetree.case) cmt_tbl = [ Doc.line; Doc.text "if "; - print_expression_with_comments ~state expr cmt_tbl; + print_expression_with_comments_and_parens ~state expr cmt_tbl; ]) in let should_inline_rhs = @@ -5967,7 +5986,7 @@ and print_payload ~state (payload : Parsetree.payload) cmt_tbl = [ Doc.line; Doc.text "if "; - print_expression_with_comments ~state expr cmt_tbl; + print_expression_with_comments_and_parens ~state expr cmt_tbl; ] | None -> Doc.nil in diff --git a/packages/dev-playground/src/CompilerApi.res b/packages/dev-playground/src/CompilerApi.res index 901eb31473..4e8914b590 100644 --- a/packages/dev-playground/src/CompilerApi.res +++ b/packages/dev-playground/src/CompilerApi.res @@ -218,7 +218,7 @@ let applyConfig = ( ~experimentalFeatures: array, ) => { if hasFunction(instance, "setModuleSystem") { - instance->Instance.setModuleSystem((moduleSystem :> string)) + instance->Instance.setModuleSystem(moduleSystem :> string) } if hasFunction(instance, "setWarnFlags") { instance->Instance.setWarnFlags(warnFlags === "" ? defaultConfig.warnFlags : warnFlags) diff --git a/tests/syntax_tests/data/printer/expr/coerce.res b/tests/syntax_tests/data/printer/expr/coerce.res index 1d5f309de6..e0b0c4b194 100644 --- a/tests/syntax_tests/data/printer/expr/coerce.res +++ b/tests/syntax_tests/data/printer/expr/coerce.res @@ -24,3 +24,77 @@ let foo = (~a=(3:int:>int), b) => 34 // let x : int1 :> int2 = 3 :> int3 let x = (/* c0 */ x /* c1 */ :> /* c2 */ int /* c3 */) + +// Delimited arguments need no extra parentheses. +foo(v :> b) +foo((v :> b)) +foo(~arg=(v :> b), ~optional=?(v :> b)) +foo((v :> b), x => x) +foo(x => x, (v :> b)) +let tuple = ((v :> b), (w :> c)) +let array = [(v :> b), (w :> c)] +let spreadArray = [...(vs :> array), (w :> b)] +let list = list{(v :> b), ...(vs :> list)} +let constructor = Some((v :> b)) +let variant = #Value((v :> b)) + +// Preserve grouping when the surrounding expression needs it. +let result = x => (x :> b) +let callback = foo(x => (x :> b)) +let coercedFunction = (x => x) :> (a => b) +let call = (f :> (a => b))(v) +let field = (v :> b).name +let objectField = (v :> b)["name"] +let equalLeft = (v :> b) == w +let equalRight = v == (w :> b) +let unary = !(v :> b) +let awaited = await (v :> b) +let nested = (v :> b) :> c +let record = {field: (v :> b)} +let conditional = condition ? (v :> b) : (w :> b) +let block = {v :> b} +let default = (~arg=(v :> b)) => arg +let jsx = b)}> {(v :> b)} + +// Comments must stay attached when parentheses disappear. +foo(/* before */ (v /* operand */ :> /* type */ b) /* after */) +foo((v :> b) // trailing line comment +) +let attributed = (@foo v) :> b +let coercionAttribute = @foo (v :> b) +let long = functionWithAVeryLongName((valueWithAVeryLongName :> typeWithAVeryLongName)) + +let arrayIndex = values[(index :> int)] +values[(index :> int)] = (value :> b) +let recordSpread = {...(value :> b), field: value} +let bracedOperand = {value} :> b +let blockSequence = {foo(); value :> b} +let checked = assert(value :> bool) +(value :> b) +let ternaryCallback = condition ? (x => (x :> b)) : other +let piped = (value :> b)->foo +let blockTail = {foo(); (value :> b)} +let blockHead = {(value :> b); foo()} +let dictSpread = dict{...(value :> dict), "field": value} + +// A constrained operand needs parentheses when the coercion is a binding RHS. +let constrainedOperand = (x: t) :> u +let constrainedChain = ((x: t) :> u) :> v +let constrainedBlock = {(x: t) :> u} +call((x: t) :> u) + +// A following JSX element must not be read as coercion type arguments. +let beforeJsx = () => { + let value = (x :> string) +