diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml index fc0298ecdd..3cf7908260 100644 --- a/compiler/syntax/src/res_core.ml +++ b/compiler/syntax/src/res_core.ml @@ -21,6 +21,21 @@ let extend_current_type_name_path current_type_name_path field_name = | None -> None | Some path -> Some (path @ [field_name]) +let make_inline_record_return_type_name current_type_name_path + inline_types_context = + let base = "return.type" in + let extend name = extend_current_type_name_path current_type_name_path name in + match (current_type_name_path, inline_types_context) with + | Some prefix, Some ctx -> + let full = String.concat "." (prefix @ [base]) in + if List.exists (fun (n, _, _) -> n = full) ctx.found_inline_types then + extend "return.type$" + else extend base + | _ -> extend base + +let pending_structure_items : Parsetree.structure_item list ref = ref [] +let pending_signature_items : Parsetree.signature_item list ref = ref [] + module Recover = struct let default_expr () = let id = Location.mknoloc "rescript.exprhole" in @@ -4394,7 +4409,7 @@ and parse_record_or_object_type ?current_type_name_path ?inline_types_context in match (inline_types_context, current_type_name_path) with | Some inline_types_context, Some current_type_name_path - when Grammar.is_record_decl_start p.token -> + when Grammar.is_field_decl_start p.token -> let labels = parse_comma_delimited_region ~grammar:Grammar.RecordDecl ~closing:Rbrace ~f: @@ -4455,7 +4470,7 @@ and parse_type_alias p typ = * dotted_type_parameter ::= * | . type_parameter *) -and parse_type_parameter p = +and parse_type_parameter ?current_type_name_path ?inline_types_context p = let doc_attr : Parsetree.attributes = match p.Parser.token with | DocComment (loc, s) -> @@ -4479,7 +4494,12 @@ and parse_type_parameter p = Parser.next p; let name, loc = parse_lident p in Parser.expect ~grammar:Grammar.TypeExpression Colon p; - let typ = parse_typ_expr p in + let arg_path = + extend_current_type_name_path current_type_name_path name + in + let typ = + parse_typ_expr ?current_type_name_path:arg_path ?inline_types_context p + in match p.Parser.token with | Equal -> Parser.next p; @@ -4498,7 +4518,13 @@ and parse_type_parameter p = Parser.err ~start_pos:loc.loc_start ~end_pos:loc.loc_end p error in Parser.next p; - let typ = parse_typ_expr p in + let arg_path = + extend_current_type_name_path current_type_name_path name + in + let typ = + parse_typ_expr ?current_type_name_path:arg_path ?inline_types_context + p + in match p.Parser.token with | Equal -> Parser.next p; @@ -4518,7 +4544,10 @@ and parse_type_parameter p = let typ = parse_type_alias p typ in Some {attrs = []; label = Nolabel; typ; start_pos}) | _ -> - let typ = parse_typ_expr p in + let typ = + if !InExternal.status then parse_typ_expr p + else parse_typ_expr ?current_type_name_path ?inline_types_context p + in let typ_with_attributes = {typ with ptyp_attributes = List.concat [attrs; typ.ptyp_attributes]} in @@ -4526,7 +4555,7 @@ and parse_type_parameter p = else None (* (int, ~x:string, float) *) -and parse_type_parameters p = +and parse_type_parameters ?current_type_name_path ?inline_types_context p = let start_pos = p.Parser.start_pos in Parser.expect Lparen p; match p.Parser.token with @@ -4539,19 +4568,26 @@ and parse_type_parameters p = | _ -> let params = parse_comma_delimited_region ~grammar:Grammar.TypeParameters - ~closing:Rparen ~f:parse_type_parameter p + ~closing:Rparen + ~f:(parse_type_parameter ?current_type_name_path ?inline_types_context) + p in Parser.expect Rparen p; params -and parse_es6_arrow_type ~attrs p = +and parse_es6_arrow_type ?current_type_name_path ?inline_types_context ~attrs p + = let start_pos = p.Parser.start_pos in match p.Parser.token with | Tilde -> Parser.next p; let name, label_loc = parse_lident p in Parser.expect ~grammar:Grammar.TypeExpression Colon p; - let typ = parse_typ_expr ~alias:false ~es6_arrow:false p in + let arg_path = extend_current_type_name_path current_type_name_path name in + let typ = + parse_typ_expr ~alias:false ~es6_arrow:false + ?current_type_name_path:arg_path ?inline_types_context p + in let lbl = match p.Parser.token with | Equal -> @@ -4561,14 +4597,30 @@ and parse_es6_arrow_type ~attrs p = | _ -> Asttypes.Labelled {txt = name; loc = label_loc} in Parser.expect EqualGreater p; - let return_type = parse_typ_expr ~alias:false p in + let return_path = + make_inline_record_return_type_name current_type_name_path + inline_types_context + in + let return_type = + parse_typ_expr ~alias:false ?current_type_name_path:return_path + ?inline_types_context p + in let loc = mk_loc start_pos p.prev_end_pos in Ast_helper.Typ.arrow ~loc ~arity:None {attrs; lbl; typ} return_type | DocComment _ -> assert false | _ -> - let parameters = parse_type_parameters p in + let parameters = + parse_type_parameters ?current_type_name_path ?inline_types_context p + in Parser.expect EqualGreater p; - let return_type = parse_typ_expr ~alias:false p in + let return_path = + make_inline_record_return_type_name current_type_name_path + inline_types_context + in + let return_type = + parse_typ_expr ~alias:false ?current_type_name_path:return_path + ?inline_types_context p + in let end_pos = p.prev_end_pos in let return_type_arity = 0 in let _paramNum, typ, _arity = @@ -4636,25 +4688,36 @@ and parse_typ_expr ?current_type_name_path ?inline_types_context ?attrs | None -> parse_attributes p in let typ = - if es6_arrow && is_es6_arrow_type p then parse_es6_arrow_type ~attrs p + if es6_arrow && is_es6_arrow_type p then + parse_es6_arrow_type ~attrs ?current_type_name_path ?inline_types_context + p else let typ = parse_atomic_typ_expr ?current_type_name_path ?inline_types_context ~attrs p in - parse_arrow_type_rest ~es6_arrow ~start_pos typ p + parse_arrow_type_rest ?current_type_name_path ?inline_types_context + ~es6_arrow ~start_pos typ p in let typ = if alias then parse_type_alias p typ else typ in (* Parser.eatBreadcrumb p; *) typ -and parse_arrow_type_rest ~es6_arrow ~start_pos typ p = +and parse_arrow_type_rest ?current_type_name_path ?inline_types_context + ~es6_arrow ~start_pos typ p = match p.Parser.token with | (EqualGreater | MinusGreater) as token when es6_arrow == true -> (* error recovery *) if token = MinusGreater then Parser.expect EqualGreater p; Parser.next p; - let return_type = parse_typ_expr ~alias:false p in + let return_path = + make_inline_record_return_type_name current_type_name_path + inline_types_context + in + let return_type = + parse_typ_expr ~alias:false ?current_type_name_path:return_path + ?inline_types_context p + in let loc = mk_loc start_pos p.prev_end_pos in Ast_helper.Typ.arrow ~loc ~arity:(Some 1) {attrs = []; lbl = Nolabel; typ} @@ -5915,7 +5978,11 @@ and parse_external_def ~attrs ~start_pos p = let name, loc = parse_lident p in let name = Location.mkloc name loc in Parser.expect ~grammar:Grammar.TypeExpression Colon p; - let typ_expr = parse_typ_expr p in + let current_type_name_path = Some [name.txt] in + let inline_types_context = {found_inline_types = []; params = []} in + let typ_expr = + parse_typ_expr ?current_type_name_path ~inline_types_context p + in let equal_start = p.start_pos in let equal_end = p.end_pos in Parser.expect Equal p; @@ -5935,7 +6002,15 @@ and parse_external_def ~attrs ~start_pos p = let vb = Ast_helper.Val.mk ~loc ~attrs ~prim name typ_expr in Parser.eat_breadcrumb p; InExternal.status := in_external; - vb + let inline_types = + inline_types_context.found_inline_types + |> List.map (fun (inline_type_name, loc, kind) -> + Ast_helper.Type.mk + ~attrs:[(Location.mknoloc "res.inlineRecordDefinition", PStr [])] + ~loc ~kind + {name with txt = inline_type_name}) + in + (vb, inline_types) (* constr-def ::= * | constr-decl @@ -6001,94 +6076,110 @@ and parse_newline_or_semicolon_structure p = and parse_structure_item_region p = let start_pos = p.Parser.start_pos in - let attrs = parse_attributes p in - match p.Parser.token with - | Open -> - let open_description = parse_open_description ~attrs p in - parse_newline_or_semicolon_structure p; - let loc = mk_loc start_pos p.prev_end_pos in - Some (Ast_helper.Str.open_ ~loc open_description) - | Let -> - let rec_flag, let_bindings = parse_let_bindings ~attrs ~start_pos p in - parse_newline_or_semicolon_structure p; - let loc = mk_loc start_pos p.prev_end_pos in - Some (Ast_helper.Str.value ~loc rec_flag let_bindings) - | Typ -> ( - Parser.begin_region p; - match parse_type_definition_or_extension ~attrs p with - | TypeDef {rec_flag; types} -> + (* If we have any synthesized items to emit, output them first, without + consuming attributes for the next real item. *) + match !pending_structure_items with + | item :: rest -> + pending_structure_items := rest; + Some item + | [] -> ( + let attrs = parse_attributes p in + match p.Parser.token with + | Open -> + let open_description = parse_open_description ~attrs p in parse_newline_or_semicolon_structure p; let loc = mk_loc start_pos p.prev_end_pos in - Parser.end_region p; - Some (Ast_helper.Str.type_ ~loc rec_flag types) - | TypeExt ext -> + Some (Ast_helper.Str.open_ ~loc open_description) + | Let -> + let rec_flag, let_bindings = parse_let_bindings ~attrs ~start_pos p in + parse_newline_or_semicolon_structure p; + let loc = mk_loc start_pos p.prev_end_pos in + Some (Ast_helper.Str.value ~loc rec_flag let_bindings) + | Typ -> ( + Parser.begin_region p; + match parse_type_definition_or_extension ~attrs p with + | TypeDef {rec_flag; types} -> + parse_newline_or_semicolon_structure p; + let loc = mk_loc start_pos p.prev_end_pos in + Parser.end_region p; + Some (Ast_helper.Str.type_ ~loc rec_flag types) + | TypeExt ext -> + parse_newline_or_semicolon_structure p; + let loc = mk_loc start_pos p.prev_end_pos in + Parser.end_region p; + Some (Ast_helper.Str.type_extension ~loc ext)) + | External -> ( + let external_def, inline_types = parse_external_def ~attrs ~start_pos p in + parse_newline_or_semicolon_structure p; + let loc = mk_loc start_pos p.prev_end_pos in + match inline_types with + | [] -> Some (Ast_helper.Str.primitive ~loc external_def) + | _ -> + (* Emit the inline types first, then queue the primitive. *) + let type_item = + Ast_helper.Str.type_ ~loc Asttypes.Recursive inline_types + in + let prim_item = Ast_helper.Str.primitive ~loc external_def in + pending_structure_items := prim_item :: !pending_structure_items; + Some type_item) + | Exception -> + let exception_def = parse_exception_def ~attrs p in + parse_newline_or_semicolon_structure p; + let loc = mk_loc start_pos p.prev_end_pos in + Some (Ast_helper.Str.exception_ ~loc exception_def) + | Include -> + let include_statement = parse_include_statement ~attrs p in + parse_newline_or_semicolon_structure p; + let loc = mk_loc start_pos p.prev_end_pos in + Some (Ast_helper.Str.include_ ~loc include_statement) + | Module -> + Parser.begin_region p; + let structure_item = + parse_module_or_module_type_impl_or_pack_expr ~attrs p + in parse_newline_or_semicolon_structure p; let loc = mk_loc start_pos p.prev_end_pos in Parser.end_region p; - Some (Ast_helper.Str.type_extension ~loc ext)) - | External -> - let external_def = parse_external_def ~attrs ~start_pos p in - parse_newline_or_semicolon_structure p; - let loc = mk_loc start_pos p.prev_end_pos in - Some (Ast_helper.Str.primitive ~loc external_def) - | Exception -> - let exception_def = parse_exception_def ~attrs p in - parse_newline_or_semicolon_structure p; - let loc = mk_loc start_pos p.prev_end_pos in - Some (Ast_helper.Str.exception_ ~loc exception_def) - | Include -> - let include_statement = parse_include_statement ~attrs p in - parse_newline_or_semicolon_structure p; - let loc = mk_loc start_pos p.prev_end_pos in - Some (Ast_helper.Str.include_ ~loc include_statement) - | Module -> - Parser.begin_region p; - let structure_item = - parse_module_or_module_type_impl_or_pack_expr ~attrs p - in - parse_newline_or_semicolon_structure p; - let loc = mk_loc start_pos p.prev_end_pos in - Parser.end_region p; - Some {structure_item with pstr_loc = loc} - | ModuleComment (loc, s) -> - Parser.next p; - Some - (Ast_helper.Str.attribute ~loc - ( {txt = "res.doc"; loc}, - PStr - [ - Ast_helper.Str.eval ~loc - (Ast_helper.Exp.constant ~loc (Pconst_string (s, None))); - ] )) - | AtAt -> - let attr = parse_standalone_attribute p in - parse_newline_or_semicolon_structure p; - let loc = mk_loc start_pos p.prev_end_pos in - Some (Ast_helper.Str.attribute ~loc attr) - | PercentPercent -> - let extension = parse_extension ~module_language:true p in - parse_newline_or_semicolon_structure p; - let loc = mk_loc start_pos p.prev_end_pos in - Some (Ast_helper.Str.extension ~attrs ~loc extension) - | token when Grammar.is_expr_start token -> - let prev_end_pos = p.Parser.end_pos in - let exp = parse_expr p in - parse_newline_or_semicolon_structure p; - let loc = mk_loc start_pos p.prev_end_pos in - Parser.check_progress ~prev_end_pos - ~result:(Ast_helper.Str.eval ~loc ~attrs exp) - p - | _ -> ( - match attrs with - | (({Asttypes.loc = attr_loc}, _) as attr) :: _ -> - Parser.err ~start_pos:attr_loc.loc_start ~end_pos:attr_loc.loc_end p - (Diagnostics.message (ErrorMessages.attribute_without_node attr)); - let expr = parse_expr p in + Some {structure_item with pstr_loc = loc} + | ModuleComment (loc, s) -> + Parser.next p; Some - (Ast_helper.Str.eval - ~loc:(mk_loc p.start_pos p.prev_end_pos) - ~attrs expr) - | _ -> None) + (Ast_helper.Str.attribute ~loc + ( {txt = "res.doc"; loc}, + PStr + [ + Ast_helper.Str.eval ~loc + (Ast_helper.Exp.constant ~loc (Pconst_string (s, None))); + ] )) + | AtAt -> + let attr = parse_standalone_attribute p in + parse_newline_or_semicolon_structure p; + let loc = mk_loc start_pos p.prev_end_pos in + Some (Ast_helper.Str.attribute ~loc attr) + | PercentPercent -> + let extension = parse_extension ~module_language:true p in + parse_newline_or_semicolon_structure p; + let loc = mk_loc start_pos p.prev_end_pos in + Some (Ast_helper.Str.extension ~attrs ~loc extension) + | token when Grammar.is_expr_start token -> + let prev_end_pos = p.Parser.end_pos in + let exp = parse_expr p in + parse_newline_or_semicolon_structure p; + let loc = mk_loc start_pos p.prev_end_pos in + Parser.check_progress ~prev_end_pos + ~result:(Ast_helper.Str.eval ~loc ~attrs exp) + p + | _ -> ( + match attrs with + | (({Asttypes.loc = attr_loc}, _) as attr) :: _ -> + Parser.err ~start_pos:attr_loc.loc_start ~end_pos:attr_loc.loc_end p + (Diagnostics.message (ErrorMessages.attribute_without_node attr)); + let expr = parse_expr p in + Some + (Ast_helper.Str.eval + ~loc:(mk_loc p.start_pos p.prev_end_pos) + ~attrs expr) + | _ -> None)) [@@progress Parser.next, Parser.expect] (* include-statement ::= include module-expr *) @@ -6636,107 +6727,121 @@ and parse_newline_or_semicolon_signature p = and parse_signature_item_region p = let start_pos = p.Parser.start_pos in - let attrs = parse_attributes p in - match p.Parser.token with - | Let -> - Parser.begin_region p; - let value_desc = parse_sign_let_desc ~attrs p in - parse_newline_or_semicolon_signature p; - let loc = mk_loc start_pos p.prev_end_pos in - Parser.end_region p; - Some (Ast_helper.Sig.value ~loc value_desc) - | Typ -> ( - Parser.begin_region p; - match parse_type_definition_or_extension ~attrs p with - | TypeDef {rec_flag; types} -> + (* Flush any synthesized items first, before consuming attributes. *) + match !pending_signature_items with + | item :: rest -> + pending_signature_items := rest; + Some item + | [] -> ( + let attrs = parse_attributes p in + match p.Parser.token with + | Let -> + Parser.begin_region p; + let value_desc = parse_sign_let_desc ~attrs p in parse_newline_or_semicolon_signature p; let loc = mk_loc start_pos p.prev_end_pos in Parser.end_region p; - Some (Ast_helper.Sig.type_ ~loc rec_flag types) - | TypeExt ext -> + Some (Ast_helper.Sig.value ~loc value_desc) + | Typ -> ( + Parser.begin_region p; + match parse_type_definition_or_extension ~attrs p with + | TypeDef {rec_flag; types} -> + parse_newline_or_semicolon_signature p; + let loc = mk_loc start_pos p.prev_end_pos in + Parser.end_region p; + Some (Ast_helper.Sig.type_ ~loc rec_flag types) + | TypeExt ext -> + parse_newline_or_semicolon_signature p; + let loc = mk_loc start_pos p.prev_end_pos in + Parser.end_region p; + Some (Ast_helper.Sig.type_extension ~loc ext)) + | External -> ( + let external_def, inline_types = parse_external_def ~attrs ~start_pos p in parse_newline_or_semicolon_signature p; let loc = mk_loc start_pos p.prev_end_pos in - Parser.end_region p; - Some (Ast_helper.Sig.type_extension ~loc ext)) - | External -> - let external_def = parse_external_def ~attrs ~start_pos p in - parse_newline_or_semicolon_signature p; - let loc = mk_loc start_pos p.prev_end_pos in - Some (Ast_helper.Sig.value ~loc external_def) - | Exception -> - let exception_def = parse_exception_def ~attrs p in - parse_newline_or_semicolon_signature p; - let loc = mk_loc start_pos p.prev_end_pos in - Some (Ast_helper.Sig.exception_ ~loc exception_def) - | Open -> - let open_description = parse_open_description ~attrs p in - parse_newline_or_semicolon_signature p; - let loc = mk_loc start_pos p.prev_end_pos in - Some (Ast_helper.Sig.open_ ~loc open_description) - | Include -> - Parser.next p; - let module_type = parse_module_type p in - let include_description = - Ast_helper.Incl.mk - ~loc:(mk_loc start_pos p.prev_end_pos) - ~attrs module_type - in - parse_newline_or_semicolon_signature p; - let loc = mk_loc start_pos p.prev_end_pos in - Some (Ast_helper.Sig.include_ ~loc include_description) - | Module -> ( - Parser.begin_region p; - Parser.next p; - match p.Parser.token with - | Uident _ -> - let mod_decl = parse_module_declaration_or_alias ~attrs p in + match inline_types with + | [] -> Some (Ast_helper.Sig.value ~loc external_def) + | _ -> + let type_item = + Ast_helper.Sig.type_ ~loc Asttypes.Recursive inline_types + in + let val_item = Ast_helper.Sig.value ~loc external_def in + pending_signature_items := val_item :: !pending_signature_items; + Some type_item) + | Exception -> + let exception_def = parse_exception_def ~attrs p in parse_newline_or_semicolon_signature p; let loc = mk_loc start_pos p.prev_end_pos in - Parser.end_region p; - Some (Ast_helper.Sig.module_ ~loc mod_decl) - | Rec -> - let rec_module = parse_rec_module_spec ~attrs ~start_pos p in + Some (Ast_helper.Sig.exception_ ~loc exception_def) + | Open -> + let open_description = parse_open_description ~attrs p in parse_newline_or_semicolon_signature p; let loc = mk_loc start_pos p.prev_end_pos in - Parser.end_region p; - Some (Ast_helper.Sig.rec_module ~loc rec_module) - | Typ -> - let mod_type_decl = parse_module_type_declaration ~attrs ~start_pos p in - Parser.end_region p; - Some mod_type_decl - | _t -> - let mod_decl = parse_module_declaration_or_alias ~attrs p in + Some (Ast_helper.Sig.open_ ~loc open_description) + | Include -> + Parser.next p; + let module_type = parse_module_type p in + let include_description = + Ast_helper.Incl.mk + ~loc:(mk_loc start_pos p.prev_end_pos) + ~attrs module_type + in parse_newline_or_semicolon_signature p; let loc = mk_loc start_pos p.prev_end_pos in - Parser.end_region p; - Some (Ast_helper.Sig.module_ ~loc mod_decl)) - | AtAt -> - let attr = parse_standalone_attribute p in - parse_newline_or_semicolon_signature p; - let loc = mk_loc start_pos p.prev_end_pos in - Some (Ast_helper.Sig.attribute ~loc attr) - | ModuleComment (loc, s) -> - Parser.next p; - Some - (Ast_helper.Sig.attribute ~loc - ( {txt = "res.doc"; loc}, - PStr - [ - Ast_helper.Str.eval ~loc - (Ast_helper.Exp.constant ~loc (Pconst_string (s, None))); - ] )) - | PercentPercent -> - let extension = parse_extension ~module_language:true p in - parse_newline_or_semicolon_signature p; - let loc = mk_loc start_pos p.prev_end_pos in - Some (Ast_helper.Sig.extension ~attrs ~loc extension) - | _ -> ( - match attrs with - | (({Asttypes.loc = attr_loc}, _) as attr) :: _ -> - Parser.err ~start_pos:attr_loc.loc_start ~end_pos:attr_loc.loc_end p - (Diagnostics.message (ErrorMessages.attribute_without_node attr)); - Some Recover.default_signature_item - | _ -> None) + Some (Ast_helper.Sig.include_ ~loc include_description) + | Module -> ( + Parser.begin_region p; + Parser.next p; + match p.Parser.token with + | Uident _ -> + let mod_decl = parse_module_declaration_or_alias ~attrs p in + parse_newline_or_semicolon_signature p; + let loc = mk_loc start_pos p.prev_end_pos in + Parser.end_region p; + Some (Ast_helper.Sig.module_ ~loc mod_decl) + | Rec -> + let rec_module = parse_rec_module_spec ~attrs ~start_pos p in + parse_newline_or_semicolon_signature p; + let loc = mk_loc start_pos p.prev_end_pos in + Parser.end_region p; + Some (Ast_helper.Sig.rec_module ~loc rec_module) + | Typ -> + let mod_type_decl = parse_module_type_declaration ~attrs ~start_pos p in + Parser.end_region p; + Some mod_type_decl + | _t -> + let mod_decl = parse_module_declaration_or_alias ~attrs p in + parse_newline_or_semicolon_signature p; + let loc = mk_loc start_pos p.prev_end_pos in + Parser.end_region p; + Some (Ast_helper.Sig.module_ ~loc mod_decl)) + | AtAt -> + let attr = parse_standalone_attribute p in + parse_newline_or_semicolon_signature p; + let loc = mk_loc start_pos p.prev_end_pos in + Some (Ast_helper.Sig.attribute ~loc attr) + | ModuleComment (loc, s) -> + Parser.next p; + Some + (Ast_helper.Sig.attribute ~loc + ( {txt = "res.doc"; loc}, + PStr + [ + Ast_helper.Str.eval ~loc + (Ast_helper.Exp.constant ~loc (Pconst_string (s, None))); + ] )) + | PercentPercent -> + let extension = parse_extension ~module_language:true p in + parse_newline_or_semicolon_signature p; + let loc = mk_loc start_pos p.prev_end_pos in + Some (Ast_helper.Sig.extension ~attrs ~loc extension) + | _ -> ( + match attrs with + | (({Asttypes.loc = attr_loc}, _) as attr) :: _ -> + Parser.err ~start_pos:attr_loc.loc_start ~end_pos:attr_loc.loc_end p + (Diagnostics.message (ErrorMessages.attribute_without_node attr)); + Some Recover.default_signature_item + | _ -> None)) [@@progress Parser.next, Parser.expect] (* module rec module-name : module-type { and module-name: module-type } *) diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index a7c95cf5ee..4d9d17af73 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -566,11 +566,21 @@ let print_constant ?(template_literal = false) c = module State = struct let custom_layout_threshold = 2 - type t = {custom_layout: int} + type t = { + custom_layout: int; + mutable pending_inline_record_defs_for_external: + Parsetree.type_declaration list option; + } - let init () = {custom_layout = 0} + let init () = + {custom_layout = 0; pending_inline_record_defs_for_external = None} - let next_custom_layout t = {custom_layout = t.custom_layout + 1} + let next_custom_layout t = + { + custom_layout = t.custom_layout + 1; + pending_inline_record_defs_for_external = + t.pending_inline_record_defs_for_external; + } let should_break_callback t = t.custom_layout > custom_layout_threshold end @@ -1141,6 +1151,16 @@ and print_value_description ~state value_description cmt_tbl = value_description.pval_attributes cmt_tbl in let header = if is_external then "external " else "let " in + let inline_record_definitions = + match + (state.State.pending_inline_record_defs_for_external, is_external) + with + | Some defs, true -> + (* consume the pending inline record defs for this external *) + state.State.pending_inline_record_defs_for_external <- None; + Some defs + | _ -> None + in Doc.group (Doc.concat [ @@ -1150,7 +1170,8 @@ and print_value_description ~state value_description cmt_tbl = (print_ident_like value_description.pval_name.txt) cmt_tbl value_description.pval_name.loc; Doc.text ": "; - print_typ_expr ~state value_description.pval_type cmt_tbl; + print_typ_expr ?inline_record_definitions ~state + value_description.pval_type cmt_tbl; (if is_external then Doc.group (Doc.concat @@ -1179,20 +1200,28 @@ and print_type_declarations ~state ~rec_flag type_declarations cmt_tbl = Res_parsetree_viewer.has_inline_record_definition_attribute td.ptype_attributes) in - let adjusted_rec_flag = - match rec_flag with - | Recursive -> - if List.length regular_declarations > 1 then Doc.text "rec " - else Doc.nil - | Nonrecursive -> Doc.nil - in - print_listi - ~get_loc:(fun n -> n.Parsetree.ptype_loc) - ~nodes:regular_declarations - ~print: - (print_type_declaration2 ~inline_record_definitions ~state - ~rec_flag:adjusted_rec_flag) - cmt_tbl + match regular_declarations with + | [] -> + (* No regular declarations; only inline-record defs produced by the + parser for an external. Capture them and skip printing this type item. *) + state.State.pending_inline_record_defs_for_external <- + Some inline_record_definitions; + Doc.nil + | _ -> + let adjusted_rec_flag = + match rec_flag with + | Recursive -> + if List.length regular_declarations > 1 then Doc.text "rec " + else Doc.nil + | Nonrecursive -> Doc.nil + in + print_listi + ~get_loc:(fun n -> n.Parsetree.ptype_loc) + ~nodes:regular_declarations + ~print: + (print_type_declaration2 ~inline_record_definitions ~state + ~rec_flag:adjusted_rec_flag) + cmt_tbl else print_listi ~get_loc:(fun n -> n.Parsetree.ptype_loc) @@ -1709,7 +1738,9 @@ and print_typ_expr ?inline_record_definitions ~(state : State.t) | _ -> false in let return_doc = - let doc = print_typ_expr ~state return_type cmt_tbl in + let doc = + print_typ_expr ?inline_record_definitions ~state return_type cmt_tbl + in if return_type_needs_parens then Doc.concat [Doc.lparen; doc; Doc.rparen] else doc in @@ -1723,7 +1754,9 @@ and print_typ_expr ?inline_record_definitions ~(state : State.t) else Doc.nil in let typ_doc = - let doc = print_typ_expr ~state typ cmt_tbl in + let doc = + print_typ_expr ?inline_record_definitions ~state typ cmt_tbl + in match typ.ptyp_desc with | Ptyp_arrow _ | Ptyp_tuple _ | Ptyp_alias _ -> add_parens doc | _ -> doc @@ -1759,7 +1792,9 @@ and print_typ_expr ?inline_record_definitions ~(state : State.t) Doc.join ~sep:(Doc.concat [Doc.comma; Doc.line]) (List.map - (fun tp -> print_type_parameter ~state tp cmt_tbl) + (fun tp -> + print_type_parameter ?inline_record_definitions ~state + tp cmt_tbl) args); ]); Doc.trailing_comma; @@ -1787,7 +1822,9 @@ and print_typ_expr ?inline_record_definitions ~(state : State.t) | Ptyp_arrow _ -> true | _ -> false in - let doc = print_typ_expr ~state typ cmt_tbl in + let doc = + print_typ_expr ?inline_record_definitions ~state typ cmt_tbl + in if needs_parens then Doc.concat [Doc.lparen; doc; Doc.rparen] else doc in Doc.concat @@ -1830,7 +1867,8 @@ and print_typ_expr ?inline_record_definitions ~(state : State.t) [ constr_name; Doc.less_than; - print_tuple_type ~state ~inline:true tuple cmt_tbl; + print_tuple_type ?inline_record_definitions ~state ~inline:true + tuple cmt_tbl; Doc.greater_than; ]) | Ptyp_constr (longident_loc, constr_args) -> ( @@ -1859,7 +1897,9 @@ and print_typ_expr ?inline_record_definitions ~(state : State.t) Doc.soft_line; Doc.greater_than; ])) - | Ptyp_tuple types -> print_tuple_type ~state ~inline:false types cmt_tbl + | Ptyp_tuple types -> + print_tuple_type ?inline_record_definitions ~state ~inline:false types + cmt_tbl | Ptyp_poly ([], typ) -> print_typ_expr ?inline_record_definitions ~state typ cmt_tbl | Ptyp_poly (string_locs, typ) -> @@ -1873,7 +1913,7 @@ and print_typ_expr ?inline_record_definitions ~(state : State.t) string_locs); Doc.dot; Doc.space; - print_typ_expr ~state typ cmt_tbl; + print_typ_expr ?inline_record_definitions ~state typ cmt_tbl; ] | Ptyp_package package_type -> print_package_type ~state ~print_module_keyword_and_parens:true @@ -2021,7 +2061,8 @@ and print_object ~state ~inline fields open_flag cmt_tbl = in if inline then doc else Doc.group doc -and print_tuple_type ~state ~inline (types : Parsetree.core_type list) cmt_tbl = +and print_tuple_type ?inline_record_definitions ~state ~inline + (types : Parsetree.core_type list) cmt_tbl = let tuple = Doc.concat [ @@ -2033,7 +2074,9 @@ and print_tuple_type ~state ~inline (types : Parsetree.core_type list) cmt_tbl = Doc.join ~sep:(Doc.concat [Doc.comma; Doc.line]) (List.map - (fun typexpr -> print_typ_expr ~state typexpr cmt_tbl) + (fun typexpr -> + print_typ_expr ?inline_record_definitions ~state typexpr + cmt_tbl) types); ]); Doc.trailing_comma; @@ -2067,7 +2110,8 @@ and print_object_field ~state (field : Parsetree.object_field) cmt_tbl = (* es6 arrow type arg * type t = (~foo: string, ~bar: float=?, unit) => unit * i.e. ~foo: string, ~bar: float *) -and print_type_parameter ~state {attrs; lbl; typ} cmt_tbl = +and print_type_parameter ?inline_record_definitions ~state {attrs; lbl; typ} + cmt_tbl = (* Converting .ml code to .res requires processing uncurried attributes *) let attrs = print_attributes ~state attrs cmt_tbl in let label = @@ -2087,7 +2131,12 @@ and print_type_parameter ~state {attrs; lbl; typ} cmt_tbl = let doc = Doc.group (Doc.concat - [attrs; label; print_typ_expr ~state typ cmt_tbl; optional_indicator]) + [ + attrs; + label; + print_typ_expr ?inline_record_definitions ~state typ cmt_tbl; + optional_indicator; + ]) in print_comments doc cmt_tbl loc diff --git a/tests/syntax_tests/data/parsing/grammar/structure/expected/externalInlineRecord.res.txt b/tests/syntax_tests/data/parsing/grammar/structure/expected/externalInlineRecord.res.txt new file mode 100644 index 0000000000..49d5b86ddc --- /dev/null +++ b/tests/syntax_tests/data/parsing/grammar/structure/expected/externalInlineRecord.res.txt @@ -0,0 +1,11 @@ +type f1.a = { + x: int }[@@res.inlineRecordDefinition ] +external f1 : a:f1.a -> unit (a:1) = "f1" +type f2.return.type = { + id: string }[@@res.inlineRecordDefinition ] +external f2 : int -> f2.return.type (a:1) = "f2" +type f3.return.type = { + b: int }[@@res.inlineRecordDefinition ] +and f3.returnType = { + a: int }[@@res.inlineRecordDefinition ] +external f3 : returnType:f3.returnType -> f3.return.type (a:1) = "f3" \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/grammar/structure/externalInlineRecord.res b/tests/syntax_tests/data/parsing/grammar/structure/externalInlineRecord.res new file mode 100644 index 0000000000..2e01e6a7ca --- /dev/null +++ b/tests/syntax_tests/data/parsing/grammar/structure/externalInlineRecord.res @@ -0,0 +1,4 @@ +external f1: (~a: {x: int}) => unit = "f1" +external f2: int => {id: string} = "f2" + +external f3: (~returnType: {a:int}) => {b:int} = "f3" diff --git a/tests/syntax_tests/data/printer/signature/expected/externalInlineRecord.resi.txt b/tests/syntax_tests/data/printer/signature/expected/externalInlineRecord.resi.txt new file mode 100644 index 0000000000..cc3a22ab8d --- /dev/null +++ b/tests/syntax_tests/data/printer/signature/expected/externalInlineRecord.resi.txt @@ -0,0 +1,12 @@ + +@module("node:fs") +external readFileSync: ( + string, + ~options: { + encoding?: [#utf8 | #ascii | #base64], + flag?: string, + misc?: { + mode?: int, + }, + }, +) => option<{filename: string, size: string}> = "fs.readFileSync" diff --git a/tests/syntax_tests/data/printer/signature/expected/externalInlineRecordBareArrow.resi.txt b/tests/syntax_tests/data/printer/signature/expected/externalInlineRecordBareArrow.resi.txt new file mode 100644 index 0000000000..92321d9e51 --- /dev/null +++ b/tests/syntax_tests/data/printer/signature/expected/externalInlineRecordBareArrow.resi.txt @@ -0,0 +1,2 @@ + +external getMeta: int => {id: string} = "getMeta" diff --git a/tests/syntax_tests/data/printer/signature/expected/externalObjectSpread.resi.txt b/tests/syntax_tests/data/printer/signature/expected/externalObjectSpread.resi.txt new file mode 100644 index 0000000000..3e3e77034b --- /dev/null +++ b/tests/syntax_tests/data/printer/signature/expected/externalObjectSpread.resi.txt @@ -0,0 +1,3 @@ +type user = {"name": string} +@val +external steve: {...user, "age": int} = "steve" diff --git a/tests/syntax_tests/data/printer/signature/externalInlineRecord.resi b/tests/syntax_tests/data/printer/signature/externalInlineRecord.resi new file mode 100644 index 0000000000..f71df8a2ce --- /dev/null +++ b/tests/syntax_tests/data/printer/signature/externalInlineRecord.resi @@ -0,0 +1,9 @@ +@module("node:fs") +external readFileSync: (string, ~options: { + encoding?: [#utf8 | #ascii | #base64], + flag?: string, + misc?: { + mode?: int + } +}) => option<{filename: string, size: string}> = "fs.readFileSync" + diff --git a/tests/syntax_tests/data/printer/signature/externalInlineRecordBareArrow.resi b/tests/syntax_tests/data/printer/signature/externalInlineRecordBareArrow.resi new file mode 100644 index 0000000000..abd8d0ee71 --- /dev/null +++ b/tests/syntax_tests/data/printer/signature/externalInlineRecordBareArrow.resi @@ -0,0 +1,2 @@ +external getMeta: int => {id: string} = "getMeta" + diff --git a/tests/syntax_tests/data/printer/signature/externalObjectSpread.resi b/tests/syntax_tests/data/printer/signature/externalObjectSpread.resi new file mode 100644 index 0000000000..8635b86591 --- /dev/null +++ b/tests/syntax_tests/data/printer/signature/externalObjectSpread.resi @@ -0,0 +1,4 @@ +type user = {"name": string} +@val +external steve: {...user, "age": int} = "steve" + diff --git a/tests/syntax_tests/data/printer/structure/expected/externalInlineRecord.res.txt b/tests/syntax_tests/data/printer/structure/expected/externalInlineRecord.res.txt new file mode 100644 index 0000000000..cc3a22ab8d --- /dev/null +++ b/tests/syntax_tests/data/printer/structure/expected/externalInlineRecord.res.txt @@ -0,0 +1,12 @@ + +@module("node:fs") +external readFileSync: ( + string, + ~options: { + encoding?: [#utf8 | #ascii | #base64], + flag?: string, + misc?: { + mode?: int, + }, + }, +) => option<{filename: string, size: string}> = "fs.readFileSync" diff --git a/tests/syntax_tests/data/printer/structure/expected/externalInlineRecordAll.res.txt b/tests/syntax_tests/data/printer/structure/expected/externalInlineRecordAll.res.txt new file mode 100644 index 0000000000..c719019894 --- /dev/null +++ b/tests/syntax_tests/data/printer/structure/expected/externalInlineRecordAll.res.txt @@ -0,0 +1,27 @@ + +@module("m1") +external f1: (~a: {x: int}, ~b: {y: string}) => unit = "f1" + + +@module("m2") +external f2: (~opt: {z: int}=?) => unit = "f2" + + +@module("m3") +external f3: ( + ~options: { + misc?: { + details: { + n: int, + }, + }, + }, +) => unit = "f3" + + +@module("m4") +external f4: int => {id2: string} = "f4" + +// Non-arrow external should not derive inline records +@val +external s1: {...user, "age": int} = "s1" diff --git a/tests/syntax_tests/data/printer/structure/expected/externalInlineRecordBareArrow.res.txt b/tests/syntax_tests/data/printer/structure/expected/externalInlineRecordBareArrow.res.txt new file mode 100644 index 0000000000..92321d9e51 --- /dev/null +++ b/tests/syntax_tests/data/printer/structure/expected/externalInlineRecordBareArrow.res.txt @@ -0,0 +1,2 @@ + +external getMeta: int => {id: string} = "getMeta" diff --git a/tests/syntax_tests/data/printer/structure/expected/externalObjectSpread.res.txt b/tests/syntax_tests/data/printer/structure/expected/externalObjectSpread.res.txt new file mode 100644 index 0000000000..3e3e77034b --- /dev/null +++ b/tests/syntax_tests/data/printer/structure/expected/externalObjectSpread.res.txt @@ -0,0 +1,3 @@ +type user = {"name": string} +@val +external steve: {...user, "age": int} = "steve" diff --git a/tests/syntax_tests/data/printer/structure/externalInlineRecord.res b/tests/syntax_tests/data/printer/structure/externalInlineRecord.res new file mode 100644 index 0000000000..f71df8a2ce --- /dev/null +++ b/tests/syntax_tests/data/printer/structure/externalInlineRecord.res @@ -0,0 +1,9 @@ +@module("node:fs") +external readFileSync: (string, ~options: { + encoding?: [#utf8 | #ascii | #base64], + flag?: string, + misc?: { + mode?: int + } +}) => option<{filename: string, size: string}> = "fs.readFileSync" + diff --git a/tests/syntax_tests/data/printer/structure/externalInlineRecordAll.res b/tests/syntax_tests/data/printer/structure/externalInlineRecordAll.res new file mode 100644 index 0000000000..060c5d8723 --- /dev/null +++ b/tests/syntax_tests/data/printer/structure/externalInlineRecordAll.res @@ -0,0 +1,21 @@ +@module("m1") +external f1: (~a: {x: int}, ~b: {y: string}) => unit = "f1" + +@module("m2") +external f2: (~opt: {z: int}=?) => unit = "f2" + +@module("m3") +external f3: (~options: { + misc?: { + details: { + n: int + } + } +}) => unit = "f3" + +@module("m4") +external f4: int => {id2: string} = "f4" + +// Non-arrow external should not derive inline records +@val +external s1: {...user, "age": int} = "s1" diff --git a/tests/syntax_tests/data/printer/structure/externalInlineRecordBareArrow.res b/tests/syntax_tests/data/printer/structure/externalInlineRecordBareArrow.res new file mode 100644 index 0000000000..9118f3b79d --- /dev/null +++ b/tests/syntax_tests/data/printer/structure/externalInlineRecordBareArrow.res @@ -0,0 +1 @@ +external getMeta: int => {id: string} = "getMeta" diff --git a/tests/syntax_tests/data/printer/structure/externalObjectSpread.res b/tests/syntax_tests/data/printer/structure/externalObjectSpread.res new file mode 100644 index 0000000000..8635b86591 --- /dev/null +++ b/tests/syntax_tests/data/printer/structure/externalObjectSpread.res @@ -0,0 +1,4 @@ +type user = {"name": string} +@val +external steve: {...user, "age": int} = "steve" +