diff --git a/CHANGELOG.md b/CHANGELOG.md index d1c3012c01..ff63df08f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Remove runtime APIs that were deprecated for removal in ReScript 13, including the `Char` module, unsafe `Obj` operations, legacy `Pervasives` helpers, and `Array.unsafe_get`. https://github.com/rescript-lang/rescript/pull/8564 - Remove the deprecated `Js` namespace and its runtime modules. https://github.com/rescript-lang/rescript/pull/8531 - Move Belt into the separately installed `@rescript/belt` package. Projects using Belt must install the package and list it in their `rescript.json` dependencies. https://github.com/rescript-lang/rescript/pull/8554 +- Correct the structured function details produced by `rescript-tools doc` and exposed by `RescriptTools.Docgen`: parameters now retain labels and optionality, nested functions, tuples, variables, and generic arguments retain their type structure, return types are identified correctly, and non-function values no longer receive fake function details. This changes the published docgen detail schema. https://github.com/rescript-lang/rescript/pull/8576 #### :eyeglasses: Spec Compliance diff --git a/compiler/ml/oprint.ml b/compiler/ml/oprint.ml index 6d0b764d50..795a1b30fb 100644 --- a/compiler/ml/oprint.ml +++ b/compiler/ml/oprint.ml @@ -253,9 +253,10 @@ and print_out_type_1 ppf = function pp_open_box ppf 0; List.iter (fun (lab, ty1) -> - if lab <> "" then ( - pp_print_string ppf lab; - pp_print_char ppf ':'); + (match lab with + | Asttypes.Noloc.Nolabel -> () + | Asttypes.Noloc.Labelled label -> fprintf ppf "%s:" label + | Asttypes.Noloc.Optional label -> fprintf ppf "?%s:" label); print_out_type_2 ppf ty1; pp_print_string ppf " ->"; pp_print_space ppf ()) @@ -406,10 +407,6 @@ let rec print_out_class_type ppf = function | tyl -> fprintf ppf "@[<1>[%a]@]@ " (print_typlist !out_type ",") tyl in fprintf ppf "@[%a%a@]" pr_tyl tyl print_ident id - | Octy_arrow (lab, ty, cty) -> - fprintf ppf "@[%s%a ->@ %a@]" - (if lab <> "" then lab ^ ":" else "") - print_out_type_2 ty print_out_class_type cty | Octy_signature (self_ty, csil) -> let pr_param ppf = function | Some ty -> fprintf ppf "@ @[(%a)@]" !out_type ty diff --git a/compiler/ml/outcometree.ml b/compiler/ml/outcometree.ml index d2e02e036e..4d6e4190eb 100644 --- a/compiler/ml/outcometree.ml +++ b/compiler/ml/outcometree.ml @@ -53,7 +53,7 @@ type out_type = | Otyp_abstract | Otyp_open | Otyp_alias of out_type * string - | Otyp_arrow of (string * out_type) list * out_type + | Otyp_arrow of (Asttypes.Noloc.arg_label * out_type) list * out_type | Otyp_class of bool * out_ident * out_type list | Otyp_constr of out_ident * out_type list | Otyp_manifest of out_type * out_type @@ -74,7 +74,6 @@ and out_variant = type out_class_type = | Octy_constr of out_ident * out_type list - | Octy_arrow of string * out_type * out_class_type | Octy_signature of out_type option * out_class_sig_item list and out_class_sig_item = | Ocsg_constraint of out_type * out_type diff --git a/compiler/ml/printtyp.ml b/compiler/ml/printtyp.ml index 624a3b75a0..3f79ca004a 100644 --- a/compiler/ml/printtyp.ml +++ b/compiler/ml/printtyp.ml @@ -616,7 +616,6 @@ let rec tree_of_typexp ?(printing_context : printing_context option) sch ty = let args = List.map (fun (arg : Types.arg) -> - let lab = string_of_label arg.lbl in let t1 = if is_optional arg.lbl then match (repr arg.typ).desc with @@ -626,7 +625,7 @@ let rec tree_of_typexp ?(printing_context : printing_context option) sch ty = | _ -> Otyp_stuff "" else tree_of_typexp ?printing_context sch arg.typ in - (lab, t1)) + (Asttypes.to_noloc arg.lbl, t1)) params in Otyp_arrow (args, tree_of_typexp ?printing_context sch ret) diff --git a/compiler/syntax/src/res_outcome_printer.ml b/compiler/syntax/src/res_outcome_printer.ml index ac72345995..684b300668 100644 --- a/compiler/syntax/src/res_outcome_printer.ml +++ b/compiler/syntax/src/res_outcome_printer.ml @@ -238,29 +238,26 @@ let rec print_out_type_doc (out_type : Outcometree.out_type) = and print_out_arrow_type typ = let typ_args, typ = collect_arrow_args typ in + let print_labeled_arg label optional_indicator typ = + Doc.group + (Doc.concat + [ + Doc.text ("~" ^ label ^ ": "); + print_out_type_doc typ; + optional_indicator; + ]) + in let args = Doc.join ~sep:(Doc.concat [Doc.comma; Doc.line]) (List.map (fun (lbl, typ) -> - let lbl_len = String.length lbl in - if lbl_len = 0 then print_out_type_doc typ - else - let lbl, optional_indicator = - (* the ocaml compiler hardcodes the optional label inside the string of the label in printtyp.ml *) - match String.unsafe_get lbl 0 with - | '?' -> - ( (String.sub [@doesNotRaise]) lbl 1 (lbl_len - 1), - Doc.text "=?" ) - | _ -> (lbl, Doc.nil) - in - Doc.group - (Doc.concat - [ - Doc.text ("~" ^ lbl ^ ": "); - print_out_type_doc typ; - optional_indicator; - ])) + match lbl with + | Asttypes.Noloc.Nolabel -> print_out_type_doc typ + | Asttypes.Noloc.Labelled label -> + print_labeled_arg label Doc.nil typ + | Asttypes.Noloc.Optional label -> + print_labeled_arg label (Doc.text "=?") typ) typ_args) in let args_doc = @@ -268,7 +265,7 @@ and print_out_arrow_type typ = match typ_args with | [(_, (Otyp_tuple _ | Otyp_arrow _))] -> true (* single argument should not be wrapped *) - | [("", _)] -> false + | [(Asttypes.Noloc.Nolabel, _)] -> false | _ -> true in if needs_parens then diff --git a/packages/@rescript/runtime/RescriptTools_Docgen.res b/packages/@rescript/runtime/RescriptTools_Docgen.res index 6dd7454747..8dd1cc2e88 100644 --- a/packages/@rescript/runtime/RescriptTools_Docgen.res +++ b/packages/@rescript/runtime/RescriptTools_Docgen.res @@ -17,13 +17,21 @@ type constructor = { payload?: constructorPayload, } -type rec typeInSignature = { - path: string, - genericTypeParameters: array, +@tag("kind") +type rec typeInSignature = + | @as("constructor") Constructor({path: string, genericTypeParameters: array}) + | @as("variable") Variable({name: string, weak: bool}) + | @as("tuple") Tuple({elements: array}) + | @as("function") Function({parameters: array, returnType: typeInSignature}) + | @as("rendered") Rendered({signature: string}) +and signatureParameter = { + label?: string, + optional: bool, + @as("type") type_: typeInSignature, } type signatureDetails = { - parameters: array, + parameters: array, returnType: typeInSignature, } @@ -31,7 +39,7 @@ type signatureDetails = { type detail = | @as("record") Record({items: array}) | @as("variant") Variant({items: array}) - | @as("alias") Signature({details: signatureDetails}) + | @as("signature") Signature({details: signatureDetails}) type source = { filepath: string, diff --git a/packages/@rescript/runtime/RescriptTools_Docgen.resi b/packages/@rescript/runtime/RescriptTools_Docgen.resi index 2c8b1d4ad1..ae20015090 100644 --- a/packages/@rescript/runtime/RescriptTools_Docgen.resi +++ b/packages/@rescript/runtime/RescriptTools_Docgen.resi @@ -17,13 +17,21 @@ type constructor = { payload?: constructorPayload, } -type rec typeInSignature = { - path: string, - genericTypeParameters: array, +@tag("kind") +type rec typeInSignature = + | @as("constructor") Constructor({path: string, genericTypeParameters: array}) + | @as("variable") Variable({name: string, weak: bool}) + | @as("tuple") Tuple({elements: array}) + | @as("function") Function({parameters: array, returnType: typeInSignature}) + | @as("rendered") Rendered({signature: string}) +and signatureParameter = { + label?: string, + optional: bool, + @as("type") type_: typeInSignature, } type signatureDetails = { - parameters: array, + parameters: array, returnType: typeInSignature, } diff --git a/tests/tools_tests/src/DocgenSignatureDetails.res b/tests/tools_tests/src/DocgenSignatureDetails.res new file mode 100644 index 0000000000..b52a9a8a4a --- /dev/null +++ b/tests/tools_tests/src/DocgenSignatureDetails.res @@ -0,0 +1,17 @@ +let labeledOptional: (~required: 'a, ~optional: array<'a>=?) => result<'a, string> = ( + ~required, + ~optional=?, +) => { + ignore(optional) + Ok(required) +} + +let takesCallback: ('a => string) => bool = _callback => true + +let returnsTuple: int => (string, int) = value => ("value", value) + +let returnsFunction: int => string => bool = _value => _text => true + +let takesVariant: [#enabled | #count(int)] => unit = _variant => () + +let constant = 42 diff --git a/tests/tools_tests/src/DocgenSignatureDetails.resi b/tests/tools_tests/src/DocgenSignatureDetails.resi new file mode 100644 index 0000000000..d6c6a598c4 --- /dev/null +++ b/tests/tools_tests/src/DocgenSignatureDetails.resi @@ -0,0 +1,17 @@ +/** Labeled and optional parameters keep their parameter metadata. */ +let labeledOptional: (~required: 'a, ~optional: array<'a>=?) => result<'a, string> + +/** A callback remains one parameter instead of becoming outer parameters. */ +let takesCallback: ('a => string) => bool + +/** A tuple remains one return-type node. */ +let returnsTuple: int => (string, int) + +/** A returned function remains nested in the return type. */ +let returnsFunction: int => string => bool + +/** Less common type forms remain visible through an explicit fallback. */ +let takesVariant: [#enabled | #count(int)] => unit + +/** Non-functions do not receive function signature details. */ +let constant: int diff --git a/tests/tools_tests/src/expected/DocExtraction2.res.json b/tests/tools_tests/src/expected/DocExtraction2.res.json index 79eac3729c..1221c0b1b3 100644 --- a/tests/tools_tests/src/expected/DocExtraction2.res.json +++ b/tests/tools_tests/src/expected/DocExtraction2.res.json @@ -29,8 +29,21 @@ "detail": { "kind": "signature", "details": { - "parameters": [ { "path": "unit", "genericTypeParameters": [] } ], - "returnType": { "path": "t", "genericTypeParameters": [] } + "parameters": [ + { + "optional": false, + "type": { + "kind": "constructor", + "path": "unit", + "genericTypeParameters": [] + } + } + ], + "returnType": { + "kind": "constructor", + "path": "t", + "genericTypeParameters": [] + } } } }, @@ -72,9 +85,20 @@ "kind": "signature", "details": { "parameters": [ - { "path": "unit", "genericTypeParameters": [] } + { + "optional": false, + "type": { + "kind": "constructor", + "path": "unit", + "genericTypeParameters": [] + } + } ], - "returnType": { "path": "t", "genericTypeParameters": [] } + "returnType": { + "kind": "constructor", + "path": "t", + "genericTypeParameters": [] + } } } } @@ -95,8 +119,17 @@ "detail": { "kind": "signature", "details": { - "parameters": [], - "returnType": { "path": "unit", "genericTypeParameters": [] } + "parameters": [ + { + "optional": false, + "type": { "kind": "variable", "name": "a", "weak": false } + } + ], + "returnType": { + "kind": "constructor", + "path": "unit", + "genericTypeParameters": [] + } } } } diff --git a/tests/tools_tests/src/expected/DocExtraction2.resi.json b/tests/tools_tests/src/expected/DocExtraction2.resi.json index 79eac3729c..1221c0b1b3 100644 --- a/tests/tools_tests/src/expected/DocExtraction2.resi.json +++ b/tests/tools_tests/src/expected/DocExtraction2.resi.json @@ -29,8 +29,21 @@ "detail": { "kind": "signature", "details": { - "parameters": [ { "path": "unit", "genericTypeParameters": [] } ], - "returnType": { "path": "t", "genericTypeParameters": [] } + "parameters": [ + { + "optional": false, + "type": { + "kind": "constructor", + "path": "unit", + "genericTypeParameters": [] + } + } + ], + "returnType": { + "kind": "constructor", + "path": "t", + "genericTypeParameters": [] + } } } }, @@ -72,9 +85,20 @@ "kind": "signature", "details": { "parameters": [ - { "path": "unit", "genericTypeParameters": [] } + { + "optional": false, + "type": { + "kind": "constructor", + "path": "unit", + "genericTypeParameters": [] + } + } ], - "returnType": { "path": "t", "genericTypeParameters": [] } + "returnType": { + "kind": "constructor", + "path": "t", + "genericTypeParameters": [] + } } } } @@ -95,8 +119,17 @@ "detail": { "kind": "signature", "details": { - "parameters": [], - "returnType": { "path": "unit", "genericTypeParameters": [] } + "parameters": [ + { + "optional": false, + "type": { "kind": "variable", "name": "a", "weak": false } + } + ], + "returnType": { + "kind": "constructor", + "path": "unit", + "genericTypeParameters": [] + } } } } diff --git a/tests/tools_tests/src/expected/DocExtractionRes.res.json b/tests/tools_tests/src/expected/DocExtractionRes.res.json index 5d3d3c2bdc..da2ef13a17 100644 --- a/tests/tools_tests/src/expected/DocExtractionRes.res.json +++ b/tests/tools_tests/src/expected/DocExtractionRes.res.json @@ -48,8 +48,21 @@ "detail": { "kind": "signature", "details": { - "parameters": [ { "path": "string", "genericTypeParameters": [] } ], - "returnType": { "path": "t", "genericTypeParameters": [] } + "parameters": [ + { + "optional": false, + "type": { + "kind": "constructor", + "path": "string", + "genericTypeParameters": [] + } + } + ], + "returnType": { + "kind": "constructor", + "path": "t", + "genericTypeParameters": [] + } } } }, @@ -67,8 +80,21 @@ "detail": { "kind": "signature", "details": { - "parameters": [ { "path": "t", "genericTypeParameters": [] } ], - "returnType": { "path": "t", "genericTypeParameters": [] } + "parameters": [ + { + "optional": false, + "type": { + "kind": "constructor", + "path": "t", + "genericTypeParameters": [] + } + } + ], + "returnType": { + "kind": "constructor", + "path": "t", + "genericTypeParameters": [] + } } } }, @@ -82,13 +108,6 @@ "filepath": "src/DocExtractionRes.res", "line": 26, "col": 5 - }, - "detail": { - "kind": "signature", - "details": { - "parameters": [], - "returnType": { "path": "int", "genericTypeParameters": [] } - } } }, { @@ -211,11 +230,19 @@ "details": { "parameters": [ { - "path": "SomeInnerModule.status", - "genericTypeParameters": [] + "optional": false, + "type": { + "kind": "constructor", + "path": "SomeInnerModule.status", + "genericTypeParameters": [] + } } ], - "returnType": { "path": "bool", "genericTypeParameters": [] } + "returnType": { + "kind": "constructor", + "path": "bool", + "genericTypeParameters": [] + } } } }, @@ -312,9 +339,20 @@ "kind": "signature", "details": { "parameters": [ - { "path": "unit", "genericTypeParameters": [] } + { + "optional": false, + "type": { + "kind": "constructor", + "path": "unit", + "genericTypeParameters": [] + } + } ], - "returnType": { "path": "t", "genericTypeParameters": [] } + "returnType": { + "kind": "constructor", + "path": "t", + "genericTypeParameters": [] + } } } } @@ -357,8 +395,21 @@ "detail": { "kind": "signature", "details": { - "parameters": [ { "path": "t", "genericTypeParameters": [] } ], - "returnType": { "path": "t", "genericTypeParameters": [] } + "parameters": [ + { + "optional": false, + "type": { + "kind": "constructor", + "path": "t", + "genericTypeParameters": [] + } + } + ], + "returnType": { + "kind": "constructor", + "path": "t", + "genericTypeParameters": [] + } } } } @@ -402,9 +453,20 @@ "kind": "signature", "details": { "parameters": [ - { "path": "int", "genericTypeParameters": [] } + { + "optional": false, + "type": { + "kind": "constructor", + "path": "int", + "genericTypeParameters": [] + } + } ], - "returnType": { "path": "int", "genericTypeParameters": [] } + "returnType": { + "kind": "constructor", + "path": "int", + "genericTypeParameters": [] + } } } } @@ -432,13 +494,6 @@ "filepath": "src/DocExtractionRes.res", "line": 138, "col": 3 - }, - "detail": { - "kind": "signature", - "details": { - "parameters": [], - "returnType": { "path": "int", "genericTypeParameters": [] } - } } } ] @@ -464,13 +519,6 @@ "filepath": "src/DocExtractionRes.res", "line": 142, "col": 7 - }, - "detail": { - "kind": "signature", - "details": { - "parameters": [], - "returnType": { "path": "int", "genericTypeParameters": [] } - } } } ], @@ -508,16 +556,6 @@ "filepath": "src/DocExtractionRes.res", "line": 147, "col": 9 - }, - "detail": { - "kind": "signature", - "details": { - "parameters": [], - "returnType": { - "path": "int", - "genericTypeParameters": [] - } - } } } ], diff --git a/tests/tools_tests/src/expected/DocgenSignatureDetails.res.json b/tests/tools_tests/src/expected/DocgenSignatureDetails.res.json new file mode 100644 index 0000000000..381618d270 --- /dev/null +++ b/tests/tools_tests/src/expected/DocgenSignatureDetails.res.json @@ -0,0 +1,243 @@ +{ + "name": "DocgenSignatureDetails", + "docstrings": [], + "source": { + "filepath": "src/DocgenSignatureDetails.resi", + "line": 1, + "col": 1 + }, + "items": [ + { + "id": "DocgenSignatureDetails.labeledOptional", + "kind": "value", + "name": "labeledOptional", + "signature": "let labeledOptional: (\n ~required: 'a,\n ~optional: array<'a>=?,\n) => result<'a, string>", + "docstrings": [ + "Labeled and optional parameters keep their parameter metadata." + ], + "source": { + "filepath": "src/DocgenSignatureDetails.resi", + "line": 2, + "col": 1 + }, + "detail": { + "kind": "signature", + "details": { + "parameters": [ + { + "label": "required", + "optional": false, + "type": { "kind": "variable", "name": "a", "weak": false } + }, + { + "label": "optional", + "optional": true, + "type": { + "kind": "constructor", + "path": "array", + "genericTypeParameters": [ + { "kind": "variable", "name": "a", "weak": false } + ] + } + } + ], + "returnType": { + "kind": "constructor", + "path": "result", + "genericTypeParameters": [ + { "kind": "variable", "name": "a", "weak": false }, + { + "kind": "constructor", + "path": "string", + "genericTypeParameters": [] + } + ] + } + } + } + }, + { + "id": "DocgenSignatureDetails.takesCallback", + "kind": "value", + "name": "takesCallback", + "signature": "let takesCallback: ('a => string) => bool", + "docstrings": [ + "A callback remains one parameter instead of becoming outer parameters." + ], + "source": { + "filepath": "src/DocgenSignatureDetails.resi", + "line": 5, + "col": 1 + }, + "detail": { + "kind": "signature", + "details": { + "parameters": [ + { + "optional": false, + "type": { + "kind": "function", + "parameters": [ + { + "optional": false, + "type": { + "kind": "variable", + "name": "a", + "weak": false + } + } + ], + "returnType": { + "kind": "constructor", + "path": "string", + "genericTypeParameters": [] + } + } + } + ], + "returnType": { + "kind": "constructor", + "path": "bool", + "genericTypeParameters": [] + } + } + } + }, + { + "id": "DocgenSignatureDetails.returnsTuple", + "kind": "value", + "name": "returnsTuple", + "signature": "let returnsTuple: int => (string, int)", + "docstrings": [ "A tuple remains one return-type node." ], + "source": { + "filepath": "src/DocgenSignatureDetails.resi", + "line": 8, + "col": 1 + }, + "detail": { + "kind": "signature", + "details": { + "parameters": [ + { + "optional": false, + "type": { + "kind": "constructor", + "path": "int", + "genericTypeParameters": [] + } + } + ], + "returnType": { + "kind": "tuple", + "elements": [ + { + "kind": "constructor", + "path": "string", + "genericTypeParameters": [] + }, + { + "kind": "constructor", + "path": "int", + "genericTypeParameters": [] + } + ] + } + } + } + }, + { + "id": "DocgenSignatureDetails.returnsFunction", + "kind": "value", + "name": "returnsFunction", + "signature": "let returnsFunction: int => string => bool", + "docstrings": [ + "A returned function remains nested in the return type." + ], + "source": { + "filepath": "src/DocgenSignatureDetails.resi", + "line": 11, + "col": 1 + }, + "detail": { + "kind": "signature", + "details": { + "parameters": [ + { + "optional": false, + "type": { + "kind": "constructor", + "path": "int", + "genericTypeParameters": [] + } + } + ], + "returnType": { + "kind": "function", + "parameters": [ + { + "optional": false, + "type": { + "kind": "constructor", + "path": "string", + "genericTypeParameters": [] + } + } + ], + "returnType": { + "kind": "constructor", + "path": "bool", + "genericTypeParameters": [] + } + } + } + } + }, + { + "id": "DocgenSignatureDetails.takesVariant", + "kind": "value", + "name": "takesVariant", + "signature": "let takesVariant: [#count(int) | #enabled] => unit", + "docstrings": [ + "Less common type forms remain visible through an explicit fallback." + ], + "source": { + "filepath": "src/DocgenSignatureDetails.resi", + "line": 14, + "col": 1 + }, + "detail": { + "kind": "signature", + "details": { + "parameters": [ + { + "optional": false, + "type": { + "kind": "rendered", + "signature": "[#count(int) | #enabled]" + } + } + ], + "returnType": { + "kind": "constructor", + "path": "unit", + "genericTypeParameters": [] + } + } + } + }, + { + "id": "DocgenSignatureDetails.constant", + "kind": "value", + "name": "constant", + "signature": "let constant: int", + "docstrings": [ + "Non-functions do not receive function signature details." + ], + "source": { + "filepath": "src/DocgenSignatureDetails.resi", + "line": 17, + "col": 1 + } + } + ] +} diff --git a/tests/tools_tests/src/expected/DocgenSignatureDetails.resi.json b/tests/tools_tests/src/expected/DocgenSignatureDetails.resi.json new file mode 100644 index 0000000000..381618d270 --- /dev/null +++ b/tests/tools_tests/src/expected/DocgenSignatureDetails.resi.json @@ -0,0 +1,243 @@ +{ + "name": "DocgenSignatureDetails", + "docstrings": [], + "source": { + "filepath": "src/DocgenSignatureDetails.resi", + "line": 1, + "col": 1 + }, + "items": [ + { + "id": "DocgenSignatureDetails.labeledOptional", + "kind": "value", + "name": "labeledOptional", + "signature": "let labeledOptional: (\n ~required: 'a,\n ~optional: array<'a>=?,\n) => result<'a, string>", + "docstrings": [ + "Labeled and optional parameters keep their parameter metadata." + ], + "source": { + "filepath": "src/DocgenSignatureDetails.resi", + "line": 2, + "col": 1 + }, + "detail": { + "kind": "signature", + "details": { + "parameters": [ + { + "label": "required", + "optional": false, + "type": { "kind": "variable", "name": "a", "weak": false } + }, + { + "label": "optional", + "optional": true, + "type": { + "kind": "constructor", + "path": "array", + "genericTypeParameters": [ + { "kind": "variable", "name": "a", "weak": false } + ] + } + } + ], + "returnType": { + "kind": "constructor", + "path": "result", + "genericTypeParameters": [ + { "kind": "variable", "name": "a", "weak": false }, + { + "kind": "constructor", + "path": "string", + "genericTypeParameters": [] + } + ] + } + } + } + }, + { + "id": "DocgenSignatureDetails.takesCallback", + "kind": "value", + "name": "takesCallback", + "signature": "let takesCallback: ('a => string) => bool", + "docstrings": [ + "A callback remains one parameter instead of becoming outer parameters." + ], + "source": { + "filepath": "src/DocgenSignatureDetails.resi", + "line": 5, + "col": 1 + }, + "detail": { + "kind": "signature", + "details": { + "parameters": [ + { + "optional": false, + "type": { + "kind": "function", + "parameters": [ + { + "optional": false, + "type": { + "kind": "variable", + "name": "a", + "weak": false + } + } + ], + "returnType": { + "kind": "constructor", + "path": "string", + "genericTypeParameters": [] + } + } + } + ], + "returnType": { + "kind": "constructor", + "path": "bool", + "genericTypeParameters": [] + } + } + } + }, + { + "id": "DocgenSignatureDetails.returnsTuple", + "kind": "value", + "name": "returnsTuple", + "signature": "let returnsTuple: int => (string, int)", + "docstrings": [ "A tuple remains one return-type node." ], + "source": { + "filepath": "src/DocgenSignatureDetails.resi", + "line": 8, + "col": 1 + }, + "detail": { + "kind": "signature", + "details": { + "parameters": [ + { + "optional": false, + "type": { + "kind": "constructor", + "path": "int", + "genericTypeParameters": [] + } + } + ], + "returnType": { + "kind": "tuple", + "elements": [ + { + "kind": "constructor", + "path": "string", + "genericTypeParameters": [] + }, + { + "kind": "constructor", + "path": "int", + "genericTypeParameters": [] + } + ] + } + } + } + }, + { + "id": "DocgenSignatureDetails.returnsFunction", + "kind": "value", + "name": "returnsFunction", + "signature": "let returnsFunction: int => string => bool", + "docstrings": [ + "A returned function remains nested in the return type." + ], + "source": { + "filepath": "src/DocgenSignatureDetails.resi", + "line": 11, + "col": 1 + }, + "detail": { + "kind": "signature", + "details": { + "parameters": [ + { + "optional": false, + "type": { + "kind": "constructor", + "path": "int", + "genericTypeParameters": [] + } + } + ], + "returnType": { + "kind": "function", + "parameters": [ + { + "optional": false, + "type": { + "kind": "constructor", + "path": "string", + "genericTypeParameters": [] + } + } + ], + "returnType": { + "kind": "constructor", + "path": "bool", + "genericTypeParameters": [] + } + } + } + } + }, + { + "id": "DocgenSignatureDetails.takesVariant", + "kind": "value", + "name": "takesVariant", + "signature": "let takesVariant: [#count(int) | #enabled] => unit", + "docstrings": [ + "Less common type forms remain visible through an explicit fallback." + ], + "source": { + "filepath": "src/DocgenSignatureDetails.resi", + "line": 14, + "col": 1 + }, + "detail": { + "kind": "signature", + "details": { + "parameters": [ + { + "optional": false, + "type": { + "kind": "rendered", + "signature": "[#count(int) | #enabled]" + } + } + ], + "returnType": { + "kind": "constructor", + "path": "unit", + "genericTypeParameters": [] + } + } + } + }, + { + "id": "DocgenSignatureDetails.constant", + "kind": "value", + "name": "constant", + "signature": "let constant: int", + "docstrings": [ + "Non-functions do not receive function signature details." + ], + "source": { + "filepath": "src/DocgenSignatureDetails.resi", + "line": 17, + "col": 1 + } + } + ] +} diff --git a/tests/tools_tests/src/expected/ModC.res.json b/tests/tools_tests/src/expected/ModC.res.json index cc4cce09bb..db9cdf11e5 100644 --- a/tests/tools_tests/src/expected/ModC.res.json +++ b/tests/tools_tests/src/expected/ModC.res.json @@ -16,14 +16,7 @@ "name": "name", "signature": "let name: string", "docstrings": [], - "source": { "filepath": "src/ModC.resi", "line": 5, "col": 3 }, - "detail": { - "kind": "signature", - "details": { - "parameters": [], - "returnType": { "path": "string", "genericTypeParameters": [] } - } - } + "source": { "filepath": "src/ModC.resi", "line": 5, "col": 3 } } ] } diff --git a/tests/tools_tests/src/expected/ModC.resi.json b/tests/tools_tests/src/expected/ModC.resi.json index cc4cce09bb..db9cdf11e5 100644 --- a/tests/tools_tests/src/expected/ModC.resi.json +++ b/tests/tools_tests/src/expected/ModC.resi.json @@ -16,14 +16,7 @@ "name": "name", "signature": "let name: string", "docstrings": [], - "source": { "filepath": "src/ModC.resi", "line": 5, "col": 3 }, - "detail": { - "kind": "signature", - "details": { - "parameters": [], - "returnType": { "path": "string", "genericTypeParameters": [] } - } - } + "source": { "filepath": "src/ModC.resi", "line": 5, "col": 3 } } ] } diff --git a/tools/src/tools.ml b/tools/src/tools.ml index d778707395..f893f81f98 100644 --- a/tools/src/tools.ml +++ b/tools/src/tools.ml @@ -20,8 +20,19 @@ type constructor_doc = { items: constructor_payload option; } -type type_doc = {path: string; generic_parameters: type_doc list} -type value_signature = {parameters: type_doc list; return_type: type_doc} +type type_doc = + | Constructor of {path: string; generic_parameters: type_doc list} + | Variable of {name: string; weak: bool} + | Tuple of type_doc list + | Function of value_signature + | Rendered of string + +and signature_parameter = {label: string option; optional: bool; typ: type_doc} + +and value_signature = { + parameters: signature_parameter list; + return_type: type_doc; +} type source = {filepath: string; line: int; col: int} @@ -103,13 +114,45 @@ let stringify_constructor_payload (constructor_payload : constructor_payload) = ("fields", `List (field_docs |> List.map stringify_field_doc)); ] -let rec stringify_type_doc (td : type_doc) = - let ps = - match td.generic_parameters with - | [] -> `List [] - | ts -> ts |> List.map stringify_type_doc |> fun ts -> `List ts - in - `Assoc [("path", `String td.path); ("genericTypeParameters", ps)] +let rec stringify_type_doc = function + | Constructor {path; generic_parameters} -> + `Assoc + [ + ("kind", `String "constructor"); + ("path", `String path); + ( "genericTypeParameters", + `List (List.map stringify_type_doc generic_parameters) ); + ] + | Variable {name; weak} -> + `Assoc + [ + ("kind", `String "variable"); + ("name", `String name); + ("weak", `Bool weak); + ] + | Tuple elements -> + `Assoc + [ + ("kind", `String "tuple"); + ("elements", `List (List.map stringify_type_doc elements)); + ] + | Function signature -> + `Assoc (("kind", `String "function") :: stringify_value_signature signature) + | Rendered signature -> + `Assoc [("kind", `String "rendered"); ("signature", `String signature)] + +and stringify_signature_parameter {label; optional; typ} = + `Assoc + ((match label with + | Some label -> [("label", `String label)] + | None -> []) + @ [("optional", `Bool optional); ("type", stringify_type_doc typ)]) + +and stringify_value_signature {parameters; return_type} = + [ + ("parameters", `List (List.map stringify_signature_parameter parameters)); + ("returnType", stringify_type_doc return_type); + ] let stringify_detail (detail : doc_item_detail) = match detail with @@ -147,18 +190,10 @@ let stringify_detail (detail : doc_item_detail) = | None -> []))) ); ] | Signature {parameters; return_type} -> - let ps = - match parameters with - | [] -> `List [] - | ps -> ps |> List.map stringify_type_doc |> fun ps -> `List ps - in `Assoc [ ("kind", `String "signature"); - ( "details", - `Assoc - [("parameters", ps); ("returnType", stringify_type_doc return_type)] - ); + ("details", `Assoc (stringify_value_signature {parameters; return_type})); ] let stringify_source source = @@ -309,60 +344,51 @@ let type_detail typ ~env ~full ~state = }) | _ -> None -(* split a list into two parts all the items except the last one and the last item *) -let split_last l = - let rec splitLast' acc = function - | [] -> failwith "splitLast: empty list" - | [x] -> (List.rev acc, x) - | x :: xs -> splitLast' (x :: acc) xs - in - splitLast' [] l +let rec string_of_out_ident = function + | Outcometree.Oide_ident name -> name + | Oide_dot (path, name) -> string_of_out_ident path ^ "." ^ name + | Oide_apply (functor_, argument) -> + string_of_out_ident functor_ ^ "(" ^ string_of_out_ident argument ^ ")" + +let render_out_type typ = + Res_doc.to_string ~width:80 (Res_outcome_printer.print_out_type_doc typ) + +let rec type_doc_of_out_type (typ : Outcometree.out_type) = + match typ with + | Otyp_constr (path, generic_parameters) -> + Constructor + { + path = string_of_out_ident path; + generic_parameters = List.map type_doc_of_out_type generic_parameters; + } + | Otyp_var (weak, name) -> Variable {name; weak} + | Otyp_tuple elements -> Tuple (List.map type_doc_of_out_type elements) + | Otyp_arrow (parameters, return_type) -> + Function (value_signature_of_arrow parameters return_type) + | _ -> Rendered (render_out_type typ) -let path_to_string path = - let buf = Buffer.create 64 in - let rec aux = function - | Path.Pident id -> Buffer.add_string buf (Ident.name id) - | Path.Pdot (p, s, _) -> - aux p; - Buffer.add_char buf '.'; - Buffer.add_string buf s - | Path.Papply (p1, p2) -> - aux p1; - Buffer.add_char buf '('; - aux p2; - Buffer.add_char buf ')' +and signature_parameter_of_out_type (label, typ) = + let label, optional = + match label with + | Asttypes.Noloc.Nolabel -> (None, false) + | Asttypes.Noloc.Labelled label -> (Some label, false) + | Asttypes.Noloc.Optional label -> (Some label, true) in - aux path; - Buffer.contents buf + {label; optional; typ = type_doc_of_out_type typ} + +and value_signature_of_arrow parameters return_type = + { + parameters = List.map signature_parameter_of_out_type parameters; + return_type = type_doc_of_out_type return_type; + } let value_detail (typ : Types.type_expr) = - let rec collect_signature_types (typ : Types.type_expr) = - match typ.desc with - | Tlink t | Tsubst t | Tpoly (t, []) -> collect_signature_types t - | Tconstr (path, ts, _) -> ( - let p = path_to_string path in - match ts with - | [] -> [{path = p; generic_parameters = []}] - | ts -> - let ts = - ts - |> List.concat_map (fun (t : Types.type_expr) -> - collect_signature_types t) - in - [{path = p; generic_parameters = ts}]) - | Tarrow (params, ret) -> - List.concat_map - (fun ({typ} : Types.arg) -> collect_signature_types typ) - params - @ collect_signature_types ret - | Tvar None -> [{path = "_"; generic_parameters = []}] - | _ -> [] - in - match collect_signature_types typ with - | [] -> None - | ts -> - let parameters, return_type = split_last ts in - Some (Signature {parameters; return_type}) + Printtyp.reset_names (); + Printtyp.reset_and_mark_loops typ; + match Printtyp.tree_of_typexp false typ with + | Otyp_arrow (parameters, return_type) -> + Some (Signature (value_signature_of_arrow parameters return_type)) + | _ -> None let make_id module_path ~identifier = identifier :: module_path |> List.rev |> Shared_types.ident