diff --git a/formatTest/typeCheckedTests/expected_output/sequences.re b/formatTest/typeCheckedTests/expected_output/sequences.re index 831e4c1f0..7fbc336ed 100644 --- a/formatTest/typeCheckedTests/expected_output/sequences.re +++ b/formatTest/typeCheckedTests/expected_output/sequences.re @@ -75,3 +75,55 @@ let singlePunAcceptedIfExtended = { ...firstFieldPunned, a, }; + +module Option = { + let map = (x, f) => + switch (x) { + | Some(x) => Some(f(x)) + | None => None + }; + + let flatMap = (x, f) => + switch (x) { + | Some(x) => f(x) + | None => None + }; + + let pair = (x, y) => + switch (x, y) { + | (Some(x), Some(y)) => Some((x, y)) + | _ => None + }; +}; + +let _ = { + let!Option x = Some(23) + and!Option y = Some(5); + + Some(x + y) +}; + +module Async = { + type t('value) = Js.Promise.t('value); + let let_: (t('a), 'a => t('b)) => t('b) = Js.Promise.then_; + let and_: (t('a), t('b)) => t(('a, 'b)) = Js.Promise.all2; + let try_: (t('a), exn => t('a)) => t('a) = Js.Promise.catch; + let resolve = Js.Promise.resolve; + let reject = Js.Promise.reject; +}; + +let getAge = () => Async.reject(Failure("Cannot get age")); + +let _ = { + let!Async x = try!Async (getAge()) { + | Failure(message) => Ok(23) + | exn => raise(exn) + }; + + let!Async a = Async.resolve(2) + and!Async b = Async.resolve(5) + and!Async c = Async.resolve(7); + print_endline(string_of_int(a)); + + Async.resolve(a + x * b + c); +}; diff --git a/formatTest/typeCheckedTests/input/sequences.re b/formatTest/typeCheckedTests/input/sequences.re index 151ec035f..711678af6 100644 --- a/formatTest/typeCheckedTests/input/sequences.re +++ b/formatTest/typeCheckedTests/input/sequences.re @@ -79,3 +79,56 @@ let thirdFieldPunned = { c }; let singlePunAcceptedIfExtended = {...firstFieldPunned, a}; + + +module Option = { + let map = (x, f) => + switch (x) { + | Some(x) => Some(f(x)) + | None => None + }; + + let flatMap = (x, f) => + switch (x) { + | Some(x) => f(x) + | None => None + }; + + let pair = (x, y) => + switch (x, y) { + | (Some(x), Some(y)) => Some((x, y)) + | _ => None + }; +}; + +let _ = { + let!Option x = Some(23) + and!Option y = Some(5); + + Some(x + y) +}; + +module Async = { + type t('value) = Js.Promise.t('value); + let let_: (t('a), 'a => t('b)) => t('b) = Js.Promise.then_; + let and_: (t('a), t('b)) => t(('a, 'b)) = Js.Promise.all2; + let try_: (t('a), exn => t('a)) => t('a) = Js.Promise.catch; + let resolve = Js.Promise.resolve; + let reject = Js.Promise.reject; +}; + +let getAge = () => Async.reject(Failure("Cannot get age")); + +let _ = { + let!Async x = try!Async (getAge()) { + | Failure(message) => Ok(23) + | exn => raise(exn) + }; + + let!Async a = Async.resolve(2) + and!Async b = Async.resolve(5) + and!Async c = Async.resolve(7); + print_endline(string_of_int(a)); + + Async.resolve(a + x * b + c); +}; diff --git a/src/reason-parser/dune b/src/reason-parser/dune index d166d1467..9a92e2452 100644 --- a/src/reason-parser/dune +++ b/src/reason-parser/dune @@ -69,6 +69,7 @@ reason_syntax_util reason_comment reason_layout + reason_attrs reason_heuristics reason_location reason_toolchain diff --git a/src/reason-parser/reason_attrs.ml b/src/reason-parser/reason_attrs.ml new file mode 100644 index 000000000..2e5c6aadc --- /dev/null +++ b/src/reason-parser/reason_attrs.ml @@ -0,0 +1,103 @@ + +open Ast_404 +open Parsetree +open Location + +module T = struct + (** Kinds of attributes *) + type attributesPartition = { + arityAttrs : attributes; + docAttrs : attributes; + stdAttrs : attributes; + jsxAttrs : attributes; + refmtAttrs : attributes; + literalAttrs : attributes; + uncurried : bool + } +end + +let letCombinator = "letCombinator" +let andCombinator = "andCombinator" + +let isRefmtTag tag attr = + match attr with + | ( + {txt="refmt"}, + PStr [{pstr_desc=Pstr_eval({pexp_desc=Pexp_constant(Pconst_string(foundTag, None))}, _)}] + ) -> foundTag = tag + | _ -> false + +let hasRefmtTag tag = List.exists (isRefmtTag tag) + +let isRefmt ~filter attr = + match attr with + | ( + {txt="refmt"}, + PStr [{pstr_desc=Pstr_eval({pexp_desc=Pexp_constant(Pconst_string(tag, None))}, _)}] + ) -> ( + match filter with + | None -> true + | Some style -> String.compare tag style == 0 + ) + | _ -> false +let isRefmtExplicitBraces = isRefmt ~filter:(Some "explicitBraces") +let isRefmtInlineOpen = isRefmt ~filter:(Some "inlineOpen") + +(** Partition attributes into kinds *) +let rec partitionAttributes ?(partDoc=false) ?(allowUncurry=true) attrs : T.attributesPartition = + let open T in + match attrs with + | [] -> + {arityAttrs=[]; docAttrs=[]; stdAttrs=[]; jsxAttrs=[]; refmtAttrs=[]; literalAttrs=[]; uncurried = false} + | (({txt = "bs"}, PStr []) as attr)::atTl -> + let partition = partitionAttributes ~partDoc ~allowUncurry atTl in + if allowUncurry then + {partition with uncurried = true} + else {partition with stdAttrs=attr::partition.stdAttrs} + | attr::atTl when isRefmt ~filter:None attr -> + let partition = partitionAttributes ~partDoc ~allowUncurry atTl in + {partition with refmtAttrs=attr::partition.refmtAttrs} + | (({txt="JSX"}, _) as jsx)::atTl -> + let partition = partitionAttributes ~partDoc ~allowUncurry atTl in + {partition with jsxAttrs=jsx::partition.jsxAttrs} + | (({txt="explicit_arity"}, _) as arity_attr)::atTl + | (({txt="implicit_arity"}, _) as arity_attr)::atTl -> + let partition = partitionAttributes ~partDoc ~allowUncurry atTl in + {partition with arityAttrs=arity_attr::partition.arityAttrs} + | (({txt="ocaml.text"}, _) as doc)::atTl when partDoc = true -> + let partition = partitionAttributes ~partDoc ~allowUncurry atTl in + {partition with docAttrs=doc::partition.docAttrs} + | (({txt="ocaml.doc"}, _) as doc)::atTl when partDoc = true -> + let partition = partitionAttributes ~partDoc ~allowUncurry atTl in + {partition with docAttrs=doc::partition.docAttrs} + | (({txt="reason.raw_literal"; _}, _) as attr) :: atTl -> + let partition = partitionAttributes ~partDoc ~allowUncurry atTl in + {partition with literalAttrs=attr::partition.literalAttrs} + | atHd :: atTl -> + let partition = partitionAttributes ~partDoc ~allowUncurry atTl in + {partition with stdAttrs=atHd::partition.stdAttrs} + +let extractStdAttrs attrs = + (partitionAttributes attrs).stdAttrs + +let extract_raw_literal attrs = + let rec loop acc = function + | ({txt="reason.raw_literal"}, + PStr [{pstr_desc = Pstr_eval({pexp_desc = Pexp_constant(Pconst_string(text, None)); _}, _); _}]) + :: rest -> + (Some text, List.rev_append acc rest) + | [] -> (None, List.rev acc) + | attr :: rest -> loop (attr :: acc) rest + in + loop [] attrs + +(* Returns (selected, remaining) *) +let rec partition fn attrs : attribute list * attribute list = + match attrs with + | [] -> ([], []) + | attr::atTl when fn attr -> + let (selectedRec, remainingRec) = partition fn atTl in + (attr::selectedRec, remainingRec) + | attr::atTl -> + let (selectedRec, remainingRec) = partition fn atTl in + (selectedRec, attr::remainingRec) \ No newline at end of file diff --git a/src/reason-parser/reason_parser.mly b/src/reason-parser/reason_parser.mly index 705d927ba..ac381c448 100644 --- a/src/reason-parser/reason_parser.mly +++ b/src/reason-parser/reason_parser.mly @@ -241,6 +241,15 @@ let simple_ghost_text_attr ?(loc=dummy_loc ()) txt = let loc = set_loc_state true loc in [({txt; loc}, PStr [])] +(* used for storing stylistic information in the AST *) +(* As if you had written [@refmt "txt"] *) +let simple_ghost_refmt_text_attr ?(loc=dummy_loc ()) txt = + let loc = set_loc_state true loc in + ( + {txt="refmt"; loc}, + PStr [{pstr_desc=Pstr_eval(mkexp ~loc (Pexp_constant(Pconst_string(txt, None))), []); pstr_loc=loc}] + ) + let mkExplicitArityTuplePat ?(loc=dummy_loc ()) pat = (* Tell OCaml type system that what this tuple construction represents is not actually a tuple, and should represent several constructor @@ -863,12 +872,14 @@ type let_bindings = { lbs_bindings: Parsetree.value_binding list; lbs_rec: rec_flag; lbs_extension: (attributes * string Asttypes.loc) option; + lbs_combinator: Longident.t Asttypes.loc option; lbs_loc: Location.t } -let mklbs ext rf lb loc = +let mklbs combinator ext rf lb loc = { lbs_bindings = [lb]; lbs_rec = rf; lbs_extension = ext; + lbs_combinator = combinator; lbs_loc = loc; } let addlbs lbs lbs' = @@ -876,21 +887,101 @@ let addlbs lbs lbs' = let val_of_let_bindings lbs = let str = Str.value lbs.lbs_rec lbs.lbs_bindings in + if lbs.lbs_combinator <> None then + raise Syntaxerr.(Error(Not_expecting(lbs.lbs_loc, "let!Foo is not allowed at the top level"))); match lbs.lbs_extension with | None -> str | Some ext -> struct_item_extension ext [str] +(* Transforms + + let!Foo x = a + and y = b; + rest_of_code + + into + + Foo.let_(Foo.and_(a, b), (x, y) => rest_of_code); + + foo is the "combinator." It is a module with two functions: + + - Foo.let_ is a monadic bind or functor map operation, like Option.map, + Promise.then, etc. It applies a continuation (rest_of_code) to some value, + which is unwrapped from an option, promise, etc. It is used to replace let. + - Foo.and_ is a pairing operation, which takes two values, and wraps them in + an option, promise, etc. Foo.and_ is used to replace and. *) +let combinator_call_of_let_bindings combinator let_bindings rest_of_code = + let modulePath = combinator.txt |> Longident.flatten |> String.concat "." in + let func = + Exp.ident + ~loc:combinator.loc + (mkloc (parse (modulePath ^ ".let_")) combinator.loc) + in + let pair and_binding = + Exp.ident + ~loc:and_binding.pvb_loc + (mkloc (parse (modulePath ^ ".and_")) and_binding.pvb_loc) + in + + match let_bindings.lbs_bindings with + | [] -> + assert false + + | let_binding::and_bindings -> + let grow_pair (nested_pair_pattern, pairing_expression) and_binding = + let nested_pair_pattern = + Pat.tuple + ~loc:and_binding.pvb_pat.ppat_loc + [nested_pair_pattern; and_binding.pvb_pat] + in + let pairing_expression = + Exp.apply + ~attrs:[simple_ghost_refmt_text_attr Reason_attrs.andCombinator] + ~loc:and_binding.pvb_loc + (pair and_binding) + [(Nolabel, pairing_expression); (Nolabel, and_binding.pvb_expr)] + in + (nested_pair_pattern, pairing_expression) + in + + let let_pattern = let_binding.pvb_pat in + let let_expression = let_binding.pvb_expr in + + let (nested_pair_pattern, pairing_expression) = + List.fold_left grow_pair (let_pattern, let_expression) and_bindings in + + let continuation = + Exp.fun_ + ~loc:combinator.loc Nolabel None nested_pair_pattern rest_of_code + in + + Exp.apply + ~attrs:[simple_ghost_refmt_text_attr Reason_attrs.letCombinator] + ~loc:let_bindings.lbs_loc + func + [(Nolabel, pairing_expression); (Nolabel, continuation)] + let expr_of_let_bindings lbs body = (* The location of this expression unfortunately includes the entire rule, * which will include any preceeding extensions. *) - let item_expr = Exp.let_ lbs.lbs_rec lbs.lbs_bindings body in - match lbs.lbs_extension with - | None -> item_expr - | Some ext -> expression_extension ext item_expr + match lbs.lbs_combinator with + | Some attr -> + if lbs.lbs_extension <> None then + raise Syntaxerr.(Error(Not_expecting(lbs.lbs_loc, "let!Foo cannot be combined with let%foo"))); + combinator_call_of_let_bindings attr lbs body + | None -> + let item_expr = Exp.let_ lbs.lbs_rec lbs.lbs_bindings body in + begin + match lbs.lbs_extension with + | None -> item_expr + | Some ext -> expression_extension ext item_expr + end let class_of_let_bindings lbs body = if lbs.lbs_extension <> None then raise Syntaxerr.(Error(Not_expecting(lbs.lbs_loc, "extension"))); + if lbs.lbs_combinator <> None then + raise Syntaxerr.(Error(Not_expecting(lbs.lbs_loc, "let!Foo is not allowed in class bindings"))); Cl.let_ lbs.lbs_rec lbs.lbs_bindings body (* @@ -2860,9 +2951,9 @@ mark_position_exp | SWITCH optional_expr_extension simple_expr_no_constructor LBRACE match_cases(seq_expr) RBRACE { $2 (mkexp (Pexp_match ($3, $5))) } - | TRY optional_expr_extension simple_expr_no_constructor + | TRY let_combinator optional_expr_extension simple_expr_no_constructor LBRACE match_cases(seq_expr) RBRACE - { $2 (mkexp (Pexp_try ($3, $5))) } + { $3 (mkexp (Pexp_try ($4, $6))) } | TRY optional_expr_extension simple_expr_no_constructor WITH error { syntax_error_exp (mklocation $startpos($5) $endpos($5)) "Invalid try with"} | IF optional_expr_extension parenthesized_expr @@ -3335,19 +3426,45 @@ labeled_expr: * error if this is an *expression * let binding. Otherwise, they become * attribute* on the structure item for the "and" binding. *) - item_attributes AND let_binding_body - { let pat, expr = $3 in - Vb.mk ~loc:(mklocation $symbolstartpos $endpos) ~attrs:$1 pat expr } -; - -let_bindings: let_binding and_let_binding* { addlbs $1 $2 }; + item_attributes AND let_combinator? let_binding_body + { let pat, expr = $4 in + let vb = + Vb.mk ~loc:(mklocation $symbolstartpos $endpos) ~attrs:$1 pat expr in + (vb, $3) } +; + +let_bindings: let_binding and_let_binding* + { let let_binding = $1 in + let and_bindings = $2 in + (* Make sure that the and combinators match the let combinators, i.e. if + there is let!Foo, then if there are ands, they must be and!Foo as + well. *) + and_bindings |> List.iter (fun (and_binding, and_combinator) -> + match (and_combinator, let_binding.lbs_combinator) with + | (None, None) -> () + | (Some a, Some b) when a.txt = b.txt -> () + | _ -> + let loc = + match and_combinator with + | None -> and_binding.pvb_loc + | Some identifier -> identifier.loc + in + let expected = + match let_binding.lbs_combinator with + | None -> "and" + | Some identifier -> "and!" ^ (Longident.flatten identifier.txt |> String.concat ".") + in + let message = "and!Foo must match let!Foo, " ^ expected in + raise Syntaxerr.(Error (Expecting (loc, message)))); + let and_bindings = fst (List.split and_bindings) in + addlbs let_binding and_bindings }; let_binding: (* Form with item extension sugar *) - item_attributes LET item_extension_sugar? rec_flag let_binding_body + item_attributes LET let_combinator? item_extension_sugar? rec_flag let_binding_body { let loc = mklocation $symbolstartpos $endpos in - let pat, expr = $5 in - mklbs $3 $4 (Vb.mk ~loc ~attrs:$1 pat expr) loc } + let pat, expr = $6 in + mklbs $3 $4 $5 (Vb.mk ~loc ~attrs:$1 pat expr) loc } ; let_binding_body: @@ -4851,6 +4968,10 @@ item_extension_sugar: PERCENT attr_id { ([], $2) } ; +let_combinator: + DOT as_loc(val_longident) { $2 } +; + extension: LBRACKETPERCENT attr_id payload RBRACKET { ($2, $3) } ; diff --git a/src/reason-parser/reason_pprint_ast.ml b/src/reason-parser/reason_pprint_ast.ml index 81ce1d082..5955bb568 100644 --- a/src/reason-parser/reason_pprint_ast.ml +++ b/src/reason-parser/reason_pprint_ast.ml @@ -55,6 +55,7 @@ open Longident open Parsetree open Easy_format open Reason_syntax_util +open Reason_attrs.T module Comment = Reason_comment module Layout = Reason_layout @@ -300,61 +301,6 @@ let extractLocationFromValBindList expr vbs = in { loc with loc_start = expr.pexp_loc.loc_start } -(** Kinds of attributes *) -type attributesPartition = { - arityAttrs : attributes; - docAttrs : attributes; - stdAttrs : attributes; - jsxAttrs : attributes; - literalAttrs : attributes; - uncurried : bool -} - -(** Partition attributes into kinds *) -let rec partitionAttributes ?(partDoc=false) ?(allowUncurry=true) attrs : attributesPartition = - match attrs with - | [] -> - {arityAttrs=[]; docAttrs=[]; stdAttrs=[]; jsxAttrs=[]; literalAttrs=[]; uncurried = false} - | (({txt = "bs"}, PStr []) as attr)::atTl -> - let partition = partitionAttributes ~partDoc ~allowUncurry atTl in - if allowUncurry then - {partition with uncurried = true} - else {partition with stdAttrs=attr::partition.stdAttrs} - | (({txt="JSX"}, _) as jsx)::atTl -> - let partition = partitionAttributes ~partDoc ~allowUncurry atTl in - {partition with jsxAttrs=jsx::partition.jsxAttrs} - | (({txt="explicit_arity"}, _) as arity_attr)::atTl - | (({txt="implicit_arity"}, _) as arity_attr)::atTl -> - let partition = partitionAttributes ~partDoc ~allowUncurry atTl in - {partition with arityAttrs=arity_attr::partition.arityAttrs} - | (({txt="ocaml.text"}, _) as doc)::atTl when partDoc = true -> - let partition = partitionAttributes ~partDoc ~allowUncurry atTl in - {partition with docAttrs=doc::partition.docAttrs} - | (({txt="ocaml.doc"}, _) as doc)::atTl when partDoc = true -> - let partition = partitionAttributes ~partDoc ~allowUncurry atTl in - {partition with docAttrs=doc::partition.docAttrs} - | (({txt="reason.raw_literal"}, _) as attr) :: atTl -> - let partition = partitionAttributes ~partDoc ~allowUncurry atTl in - {partition with literalAttrs=attr::partition.literalAttrs} - | atHd :: atTl -> - let partition = partitionAttributes ~partDoc ~allowUncurry atTl in - {partition with stdAttrs=atHd::partition.stdAttrs} - -let extractStdAttrs attrs = - (partitionAttributes attrs).stdAttrs - -let extract_raw_literal attrs = - let rec loop acc = function - | ({txt="reason.raw_literal"}, - PStr [{pstr_desc = Pstr_eval({pexp_desc = Pexp_constant(Pconst_string(text, None))}, _)}]) - :: rest -> - (Some text, List.rev_append acc rest) - | [] -> (None, List.rev acc) - | attr :: rest -> loop (attr :: acc) rest - in - loop [] attrs - - let rec sequentialIfBlocks x = match x with | Some ({pexp_desc=Pexp_ifthenelse (e1, e2, els)}) -> ( @@ -2064,7 +2010,7 @@ let isJSXComponent expr = match expr with | ({pexp_desc= Pexp_apply ({pexp_desc=Pexp_ident _}, args); pexp_attributes}) | ({pexp_desc= Pexp_apply ({pexp_desc=Pexp_letmodule(_,_,_)}, args); pexp_attributes}) -> - let {jsxAttrs} = partitionAttributes pexp_attributes in + let {jsxAttrs} = Reason_attrs.partitionAttributes pexp_attributes in let hasLabelledChildrenLiteral = List.exists (function | (Labelled "children", _) -> true | _ -> false @@ -2412,7 +2358,7 @@ let printer = object(self:'self) method non_arrowed_core_type x = self#non_arrowed_non_simple_core_type x method core_type2 x = - let {stdAttrs; uncurried} = partitionAttributes x.ptyp_attributes in + let {stdAttrs; uncurried} = Reason_attrs.partitionAttributes x.ptyp_attributes in let uncurried = uncurried || try Hashtbl.find uncurriedTable x.ptyp_loc with | Not_found -> false in if stdAttrs <> [] then formatAttributed @@ -2465,7 +2411,7 @@ let printer = object(self:'self) (* Same as core_type2 but can be aliased *) method core_type x = - let {stdAttrs; uncurried} = partitionAttributes x.ptyp_attributes in + let {stdAttrs; uncurried} = Reason_attrs.partitionAttributes x.ptyp_attributes in let () = if uncurried then Hashtbl.add uncurriedTable x.ptyp_loc true in if stdAttrs <> [] then formatAttributed @@ -2668,7 +2614,7 @@ let printer = object(self:'self) (atom "=") td in - let {stdAttrs; docAttrs} = partitionAttributes ~partDoc:true td.ptype_attributes in + let {stdAttrs; docAttrs} = Reason_attrs.partitionAttributes ~partDoc:true td.ptype_attributes in let layout = self#attach_std_item_attrs stdAttrs itm in self#attachDocAttrsToLayout ~stdAttrs @@ -2701,7 +2647,7 @@ let printer = object(self:'self) * not parsed or printed correctly. *) method type_variant_leaf1 opt_ampersand polymorphic print_bar x = let {pcd_name; pcd_args; pcd_res; pcd_loc; pcd_attributes} = x in - let {stdAttrs} = partitionAttributes pcd_attributes in + let {stdAttrs} = Reason_attrs.partitionAttributes pcd_attributes in let ampersand_helper i arg = let ct = self#core_type arg in let ct = match arg.ptyp_desc with @@ -2803,7 +2749,7 @@ let printer = object(self:'self) let recordRow = match pld.pld_attributes with | [] -> recordRow | attrs -> - let {stdAttrs; docAttrs} = partitionAttributes ~partDoc:true attrs in + let {stdAttrs; docAttrs} = Reason_attrs.partitionAttributes ~partDoc:true attrs in let stdAttrsLayout = makeList ~inline:(true, true) ~postSpace:true (self#attributes stdAttrs) in @@ -2966,7 +2912,7 @@ let printer = object(self:'self) "hello", None. *) method non_arrowed_non_simple_core_type x = - let {stdAttrs} = partitionAttributes x.ptyp_attributes in + let {stdAttrs} = Reason_attrs.partitionAttributes x.ptyp_attributes in if stdAttrs <> [] then formatAttributed (self#non_arrowed_simple_core_type {x with ptyp_attributes=[]}) @@ -2983,7 +2929,7 @@ let printer = object(self:'self) | t -> self#core_type t method non_arrowed_simple_core_type x = - let {stdAttrs} = partitionAttributes x.ptyp_attributes in + let {stdAttrs} = Reason_attrs.partitionAttributes x.ptyp_attributes in if stdAttrs <> [] then formatSimpleAttributed (self#non_arrowed_simple_core_type {x with ptyp_attributes=[]}) @@ -3133,7 +3079,7 @@ let printer = object(self:'self) method pattern_without_or x = (* TODOATTRIBUTES: Handle the stdAttrs here *) - let {arityAttrs} = partitionAttributes x.ppat_attributes in + let {arityAttrs} = Reason_attrs.partitionAttributes x.ppat_attributes in match x.ppat_desc with | Ppat_alias (p, s) -> let raw_pattern = (self#pattern p) in @@ -3174,7 +3120,7 @@ let printer = object(self:'self) | _ -> self#simple_pattern x method pattern x = - let {arityAttrs; stdAttrs} = partitionAttributes x.ppat_attributes in + let {arityAttrs; stdAttrs} = Reason_attrs.partitionAttributes x.ppat_attributes in if stdAttrs <> [] then formatAttributed (* Doesn't need to be simple_pattern because attributes are parse as @@ -3255,7 +3201,7 @@ let printer = object(self:'self) | _ -> self#pattern x method simple_pattern x = - let {arityAttrs; stdAttrs} = partitionAttributes x.ppat_attributes in + let {arityAttrs; stdAttrs} = Reason_attrs.partitionAttributes x.ppat_attributes in if stdAttrs <> [] then formatSimpleAttributed (self#simple_pattern {x with ppat_attributes=arityAttrs}) @@ -3308,7 +3254,7 @@ let printer = object(self:'self) | Ppat_tuple l -> self#patternTuple l | Ppat_constant c -> - let raw_literal, _ = extract_raw_literal x.ppat_attributes in + let raw_literal, _ = Reason_attrs.extract_raw_literal x.ppat_attributes in (self#constant ?raw_literal c) | Ppat_interval (c1, c2) -> makeList [self#constant c1; atom ".."; self#constant c2] @@ -3364,7 +3310,7 @@ let printer = object(self:'self) method simple_get_application x = - let {stdAttrs; jsxAttrs} = partitionAttributes x.pexp_attributes in + let {stdAttrs; jsxAttrs} = Reason_attrs.partitionAttributes x.pexp_attributes in match (x.pexp_desc, stdAttrs, jsxAttrs) with | (_, _::_, []) -> None (* Has some printed attributes - not simple *) | (Pexp_apply ({pexp_desc=Pexp_ident loc}, l), [], _jsx::_) -> ( @@ -3587,7 +3533,7 @@ let printer = object(self:'self) (* This method may not even be needed *) method unparseUnattributedExpr x = - match partitionAttributes x.pexp_attributes with + match Reason_attrs.partitionAttributes x.pexp_attributes with | {docAttrs = []; stdAttrs = []} -> self#unparseExpr x | _ -> makeList ~wrap:("(",")") [self#unparseExpr x] @@ -3614,7 +3560,7 @@ let printer = object(self:'self) * Some((-1)) should be printed as Some(-1). This is in contrast with * 1 + (-1) where we print the parens for readability. *) let raw_literal, pexp_attributes = - extract_raw_literal pexp_attributes + Reason_attrs.extract_raw_literal pexp_attributes in let constant = self#constant ?raw_literal ~parens:ensureExpr c in begin match pexp_attributes with @@ -3866,8 +3812,8 @@ let printer = object(self:'self) let x = self#process_underscore_application x in (* If there are any attributes, render unary like `(~-) x [@ppx]`, and infix like `(+) x y [@attr]` *) - let {arityAttrs; stdAttrs; jsxAttrs; literalAttrs; uncurried} = - partitionAttributes ~allowUncurry:(Reason_heuristics.bsExprCanBeUncurried x) x.pexp_attributes + let {arityAttrs; stdAttrs; jsxAttrs; refmtAttrs; literalAttrs; uncurried} = + Reason_attrs.partitionAttributes ~allowUncurry:(Reason_heuristics.bsExprCanBeUncurried x) x.pexp_attributes in let () = if uncurried then Hashtbl.add uncurriedTable x.pexp_loc true in let x = {x with pexp_attributes = (literalAttrs @ arityAttrs @ stdAttrs @ jsxAttrs) } in @@ -3900,6 +3846,9 @@ let printer = object(self:'self) | Some se -> Simple se | None -> match x.pexp_desc with + | Pexp_apply _ when Reason_attrs.(hasRefmtTag letCombinator) refmtAttrs -> + let x = {x with pexp_attributes = refmtAttrs @ x.pexp_attributes} in + Simple (makeLetSequence (self#letList x)) | Pexp_apply (e, ls) -> ( let ls = List.map (fun (l,expr) -> (l, self#process_underscore_application expr)) ls in match (e, ls) with @@ -4106,7 +4055,7 @@ let printer = object(self:'self) let leftItm = self#unparseResolvedRule ( self#ensureExpression ~reducesOnToken:prec e ) in - let {stdAttrs} = partitionAttributes e.pexp_attributes in + let {stdAttrs} = Reason_attrs.partitionAttributes e.pexp_attributes in let formattedLeftItm = if stdAttrs = [] then leftItm else @@ -5033,7 +4982,7 @@ let printer = object(self:'self) let appTerms = self#unparseExprApplicationItems x.pvb_expr in self#formatSimplePatternBinding prefixText layoutPattern None appTerms in - let {stdAttrs; docAttrs} = partitionAttributes ~partDoc:true x.pvb_attributes in + let {stdAttrs; docAttrs} = Reason_attrs.partitionAttributes ~partDoc:true x.pvb_attributes in let body = makeList ~inline:(true, true) [body] in let layout = self#attach_std_item_attrs stdAttrs (source_map ~loc:x.pvb_loc body) in @@ -5110,7 +5059,8 @@ let printer = object(self:'self) * list containing the location indicating start/end of the "let-item" and * its layout. *) let rec processLetList acc expr = - match (expr.pexp_attributes, expr.pexp_desc) with + let (refmtAttrs, otherAttrs) = Reason_attrs.partition (Reason_attrs.isRefmt ~filter:None) expr.pexp_attributes in + match (otherAttrs, expr.pexp_desc) with | ([], Pexp_let (rf, l, e)) -> (* For "letList" bindings, the start/end isn't as simple as with * module value bindings. For "let lists", the sequences were formed @@ -5120,6 +5070,82 @@ let printer = object(self:'self) let bindingsLoc = self#bindingsLocationRange l in let layout = source_map ~loc:bindingsLoc bindingsLayout in processLetList ((bindingsLoc, layout)::acc) e + + | (_attrs, Pexp_apply ( + {pexp_desc = Pexp_ident function_name}, [ + Nolabel, bound_expression; + Nolabel, {pexp_desc = Pexp_fun ( + Nolabel, None, continuation_pattern, continuation_body)}])) + when Reason_attrs.(hasRefmtTag letCombinator) refmtAttrs -> + + (* Find all the nested applications that were generated from and!Foo + bindings. They are tagged with andCombinator. *) + let rec find_bindings pattern expr = + let (refmtAttrs, otherAttrs) = + Reason_attrs.partition + (Reason_attrs.isRefmt ~filter:None) expr.pexp_attributes + in + + match (otherAttrs, expr.pexp_desc) with + | (_, Pexp_apply ( + {pexp_desc = Pexp_ident function_name}, [ + Nolabel, left_expr; + Nolabel, right_expr + ])) + when Reason_attrs.(hasRefmtTag andCombinator) refmtAttrs -> + + let (left_pattern, right_pattern) = + match pattern.ppat_desc with + | Ppat_tuple [l; r] -> (l, r) + | _ -> assert false + in + + let nested_bindings_reversed = + find_bindings left_pattern left_expr in + + let this_and_binding = + Ast_helper.Vb.mk + ~loc:function_name.loc right_pattern right_expr + in + + this_and_binding::nested_bindings_reversed + + | _ -> + let let_binding = + Ast_helper.Vb.mk + ~loc:function_name.loc pattern expr + in + [let_binding] + in + + let bindings = + find_bindings continuation_pattern bound_expression + |> List.rev + in + + let combinator_name = + match function_name.txt with + | Ldot (Lident module_name, "let_") -> module_name + | _ -> assert false + in + + let bindings_loc = self#bindingsLocationRange bindings in + + let layout = + let let_layout = + self#binding ("let!" ^ combinator_name) (List.hd bindings) in + let and_layouts = + List.map + (self#binding ("and!" ^ combinator_name)) (List.tl bindings) + in + makeList + ~postSpace:true ~break:Always ~indent:0 ~inline:(true, true) + (let_layout::and_layouts) + |> source_map ~loc:bindings_loc + in + + processLetList ((bindings_loc, layout)::acc) continuation_body + | (attrs, Pexp_open (ovf, lid, e)) (* Add this when check to make sure these are handled as regular "simple expressions" *) when not (self#isSeriesOfOpensFollowedByNonSequencyExpression {expr with pexp_attributes = []}) -> @@ -5159,7 +5185,7 @@ let printer = object(self:'self) * Pexp location is parsed (potentially) beginning with the open * brace {} in the let sequence. *) let layout = source_map ~loc:letModuleLoc letModuleLayout in - let (_, return) = self#curriedFunctorPatternsAndReturnStruct moduleExpr in + let (_, return) = self#curriedFunctorPatternsAndReturnStruct moduleExpr in let loc = { letModuleLoc with loc_end = return.pmod_loc.loc_end @@ -5378,7 +5404,7 @@ let printer = object(self:'self) (* Expressions requiring parens, in most contexts such as separated by infix *) method expression_requiring_parens_in_infix x = - let {stdAttrs} = partitionAttributes x.pexp_attributes in + let {stdAttrs} = Reason_attrs.partitionAttributes x.pexp_attributes in assert (stdAttrs == []); (* keep the incoming expression around, an expr with * immediate extension sugar might contain less than perfect location @@ -5779,7 +5805,7 @@ let printer = object(self:'self) method unparseObject ?wrap:((lwrap,rwrap)=("", "")) ?(withStringKeys=false) l o = let core_field_type (s, attrs, ct) = - let l = extractStdAttrs attrs in + let l = Reason_attrs.extractStdAttrs attrs in let row = let rowKey = if withStringKeys then (makeList ~wrap:("\"", "\"") [atom s]) @@ -5857,7 +5883,7 @@ let printer = object(self:'self) | _ -> assert false method simplest_expression x = - let {stdAttrs; jsxAttrs} = partitionAttributes x.pexp_attributes in + let {stdAttrs; jsxAttrs; refmtAttrs} = Reason_attrs.partitionAttributes x.pexp_attributes in if stdAttrs <> [] then None else @@ -5867,6 +5893,9 @@ let printer = object(self:'self) token will be confused with the match token. *) | Pexp_fun _ when pipe || semi -> Some (self#reset#simplifyUnparseExpr x) | Pexp_function l when pipe || semi -> Some (formatPrecedence ~loc:x.pexp_loc (self#reset#patternFunction x.pexp_loc l)) + | Pexp_apply _ + when Reason_attrs.(hasRefmtTag letCombinator) refmtAttrs -> + Some (makeLetSequence (self#letList x)) | Pexp_apply _ -> ( match self#simple_get_application x with (* If it's the simple form of application. *) @@ -5917,7 +5946,7 @@ let printer = object(self:'self) Some (ensureSingleTokenSticksToLabel (self#longident_loc li)) | Pexp_constant c -> (* Constants shouldn't break when to the right of a label *) - let raw_literal, _ = extract_raw_literal x.pexp_attributes in + let raw_literal, _ = Reason_attrs.extract_raw_literal x.pexp_attributes in Some (ensureSingleTokenSticksToLabel (self#constant ?raw_literal c)) | Pexp_pack me -> @@ -5996,7 +6025,7 @@ let printer = object(self:'self) method formatChildren children processedRev = match children with | {pexp_desc = Pexp_constant constant} as x :: remaining -> - let raw_literal, _ = extract_raw_literal x.pexp_attributes in + let raw_literal, _ = Reason_attrs.extract_raw_literal x.pexp_attributes in self#formatChildren remaining (self#constant ?raw_literal constant :: processedRev) | {pexp_desc = Pexp_construct ({txt = Lident "::"}, Some {pexp_desc = Pexp_tuple children} )} as x :: remaining -> begin match x.pexp_attributes with @@ -6100,13 +6129,13 @@ let printer = object(self:'self) method attributes l = List.map self#attribute l method attach_std_attrs l toThis = - let l = extractStdAttrs l in + let l = Reason_attrs.extractStdAttrs l in match l with | [] -> toThis | _::_ -> makeList ~postSpace:true (List.concat [self#attributes l; [toThis]]) method attach_std_item_attrs ?(allowUncurry=true) ?extension l toThis = - let l = (partitionAttributes ~allowUncurry l).stdAttrs in + let l = (Reason_attrs.partitionAttributes ~allowUncurry l).stdAttrs in match extension, l with | None, [] -> toThis | _, _ -> @@ -6129,7 +6158,7 @@ let printer = object(self:'self) | Pext_rebind id -> [atom pcd_name.txt; atom "="; (self#longident_loc id)] in let {stdAttrs; docAttrs} = - partitionAttributes ~partDoc:true ed.pext_attributes + Reason_attrs.partitionAttributes ~partDoc:true ed.pext_attributes in let layout = self#attach_std_item_attrs @@ -6232,7 +6261,7 @@ let printer = object(self:'self) match vd.pval_attributes with | [] -> primDecl | attrs -> - let {stdAttrs; docAttrs} = partitionAttributes ~partDoc:true attrs in + let {stdAttrs; docAttrs} = Reason_attrs.partitionAttributes ~partDoc:true attrs in let docs = List.map self#item_attribute docAttrs in let formattedDocs = makeList ~postSpace:true docs in let attrs = List.map self#item_attribute stdAttrs in @@ -6315,7 +6344,7 @@ let printer = object(self:'self) in let includingEqual = makeList ~postSpace:true [upToName; atom "="] in let {stdAttrs; docAttrs} = - partitionAttributes ~partDoc:true pci_attributes + Reason_attrs.partitionAttributes ~partDoc:true pci_attributes in let layout = self#attach_std_item_attrs stdAttrs @@ @@ -6523,7 +6552,7 @@ let printer = object(self:'self) source_map ~loc:p.ppat_loc field :: fields method simple_class_expr x = - let {stdAttrs} = partitionAttributes x.pcl_attributes in + let {stdAttrs} = Reason_attrs.partitionAttributes x.pcl_attributes in if stdAttrs <> [] then formatSimpleAttributed (self#simple_class_expr {x with pcl_attributes=[]}) @@ -6568,7 +6597,7 @@ let printer = object(self:'self) | _ -> [self#class_expr x] method class_expr x = - let {stdAttrs} = partitionAttributes x.pcl_attributes in + let {stdAttrs} = Reason_attrs.partitionAttributes x.pcl_attributes in (* We cannot handle the attributes here. Must handle them in each item *) if stdAttrs <> [] then (* Do not need a "simple" attributes precedence wrapper. *) @@ -6644,7 +6673,7 @@ let printer = object(self:'self) self#primitive_declaration vd else let intro = atom "let" in - let {stdAttrs; docAttrs} = partitionAttributes ~partDoc:true vd.pval_attributes in + let {stdAttrs; docAttrs} = Reason_attrs.partitionAttributes ~partDoc:true vd.pval_attributes in let layout = self#attach_std_item_attrs stdAttrs (formatTypeConstraint (label ~space:true intro @@ -6676,7 +6705,7 @@ let printer = object(self:'self) patternAux ([(self#class_constructor_type x.pci_expr)], None) in - let {stdAttrs; docAttrs} = partitionAttributes ~partDoc:true x.pci_attributes in + let {stdAttrs; docAttrs} = Reason_attrs.partitionAttributes ~partDoc:true x.pci_attributes in let layout = self#attach_std_item_attrs stdAttrs withColon in source_map ~loc:pci_loc (self#attachDocAttrsToLayout @@ -6696,7 +6725,7 @@ let printer = object(self:'self) ) | Psig_module {pmd_name; pmd_type={pmty_desc=Pmty_alias alias}; pmd_attributes} -> let {stdAttrs; docAttrs} = - partitionAttributes ~partDoc:true pmd_attributes + Reason_attrs.partitionAttributes ~partDoc:true pmd_attributes in let layout = self#attach_std_item_attrs stdAttrs @@ @@ -6716,7 +6745,7 @@ let printer = object(self:'self) () | Psig_module pmd -> let {stdAttrs; docAttrs} = - partitionAttributes ~partDoc:true pmd.pmd_attributes + Reason_attrs.partitionAttributes ~partDoc:true pmd.pmd_attributes in let letPattern = makeList @@ -6735,7 +6764,7 @@ let printer = object(self:'self) () | Psig_open od -> let {stdAttrs; docAttrs} = - partitionAttributes ~partDoc:true od.popen_attributes + Reason_attrs.partitionAttributes ~partDoc:true od.popen_attributes in let layout = self#attach_std_item_attrs stdAttrs @@ @@ -6751,7 +6780,7 @@ let printer = object(self:'self) () | Psig_include incl -> let {stdAttrs; docAttrs} = - partitionAttributes ~partDoc:true incl.pincl_attributes + Reason_attrs.partitionAttributes ~partDoc:true incl.pincl_attributes in let layout = self#attach_std_item_attrs stdAttrs @@ @@ -6771,7 +6800,7 @@ let printer = object(self:'self) | Some mt -> self#module_type letPattern mt in let {stdAttrs; docAttrs} = - partitionAttributes ~partDoc:true x.pmtd_attributes + Reason_attrs.partitionAttributes ~partDoc:true x.pmtd_attributes in let layout = self#attach_std_item_attrs stdAttrs main @@ -6786,7 +6815,7 @@ let printer = object(self:'self) | Psig_recmodule decls -> let first xx = let {stdAttrs; docAttrs} = - partitionAttributes ~partDoc:true xx.pmd_attributes + Reason_attrs.partitionAttributes ~partDoc:true xx.pmd_attributes in let letPattern = makeList @@ -6821,7 +6850,7 @@ let printer = object(self:'self) | Psig_attribute a -> self#floating_attribute a | Psig_extension (({loc}, _) as ext, attrs) -> let {stdAttrs; docAttrs} = - partitionAttributes ~partDoc:true attrs + Reason_attrs.partitionAttributes ~partDoc:true attrs in let layout = self#attach_std_item_attrs stdAttrs (self#item_extension ext) @@ -7132,7 +7161,7 @@ let printer = object(self:'self) let item = ( match term.pstr_desc with | Pstr_eval (e, attrs) -> - let {stdAttrs; jsxAttrs; uncurried} = partitionAttributes attrs in + let {stdAttrs; jsxAttrs; uncurried} = Reason_attrs.partitionAttributes attrs in if uncurried then Hashtbl.add uncurriedTable e.pexp_loc true; let layout = self#attach_std_item_attrs stdAttrs (self#unparseUnattributedExpr e) in (* If there was a JSX attribute BUT JSX component wasn't detected, @@ -7197,7 +7226,7 @@ let printer = object(self:'self) | Pstr_value (rf, l) -> self#bindings ~extension (rf, l) | _ -> let {stdAttrs; docAttrs} = - partitionAttributes ~partDoc:true a + Reason_attrs.partitionAttributes ~partDoc:true a in let layout = self#attach_std_item_attrs ~extension stdAttrs @@ -7218,7 +7247,7 @@ let printer = object(self:'self) let name = self#longident_loc ptyext_path in let item = self#formatOneTypeExt prepend name (atom "+=") te in let {stdAttrs; docAttrs} = - partitionAttributes ~partDoc:true te.ptyext_attributes + Reason_attrs.partitionAttributes ~partDoc:true te.ptyext_attributes in let layout = self#attach_std_item_attrs stdAttrs item in self#attachDocAttrsToLayout @@ -7446,7 +7475,7 @@ let printer = object(self:'self) * MyModuleBlah.toList(argument) *) let (argLbl, cb) = callbackArg in - let {stdAttrs; uncurried} = partitionAttributes cb.pexp_attributes in + let {stdAttrs; uncurried} = Reason_attrs.partitionAttributes cb.pexp_attributes in let cbAttrs = stdAttrs in if uncurried then Hashtbl.add uncurriedTable cb.pexp_loc true; let (cbArgs, retCb) = self#curriedPatternsAndReturnVal {cb with pexp_attributes = []} in