Skip to content

Commit 49ff009

Browse files
committed
Preserve structure in docgen function details
Function labels in Outcometree were strings, with optionality encoded by a leading question mark. That forced printers to decode the spelling and left downstream consumers without the label structure already known by the type system. Store Noloc.arg_label directly on Otyp_arrow, update both printers to match it exhaustively, and remove the unproduced Octy_arrow constructor. The doc generator previously walked Types.type_expr independently and flattened every reachable constructor into one list. Nested arrows became outer parameters, tuples and type variables disappeared, labels and optionality were lost, and non-function values acquired fabricated zero-parameter signatures. Build details from the normalized Outcometree instead: parameters retain their metadata, constructors, variables, tuples, and functions form recursive nodes, uncommon forms remain visible through a rendered fallback, and only top-level arrows receive signature details. Update the published RescriptTools.Docgen types and snapshots for the intentionally breaking JSON shape, and correct the implementation's stale alias tag to match the signature tag declared by its interface. The documentation site drops value details before publishing its data, but third-party consumers of rescript-tools doc need the changelog warning. Focused fixtures cover labeled and optional parameters, generic variables, callbacks, tuple returns, returned functions, fallback rendering, and non-function values. Compiler, tools, analysis, syntax, roundtrip, and full test suites remain green. Signed-off-by: Cristiano Calcagno <cristianoc@users.noreply.github.com>
1 parent 91d564a commit 49ff009

17 files changed

Lines changed: 824 additions & 179 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
- Remove the deprecated `Js` namespace and its runtime modules. https://github.com/rescript-lang/rescript/pull/8531
1818
- 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
19+
- 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.
1920

2021
#### :eyeglasses: Spec Compliance
2122

compiler/ml/oprint.ml

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,10 @@ and print_out_type_1 ppf = function
253253
pp_open_box ppf 0;
254254
List.iter
255255
(fun (lab, ty1) ->
256-
if lab <> "" then (
257-
pp_print_string ppf lab;
258-
pp_print_char ppf ':');
256+
(match lab with
257+
| Asttypes.Noloc.Nolabel -> ()
258+
| Asttypes.Noloc.Labelled label -> fprintf ppf "%s:" label
259+
| Asttypes.Noloc.Optional label -> fprintf ppf "?%s:" label);
259260
print_out_type_2 ppf ty1;
260261
pp_print_string ppf " ->";
261262
pp_print_space ppf ())
@@ -406,10 +407,6 @@ let rec print_out_class_type ppf = function
406407
| tyl -> fprintf ppf "@[<1>[%a]@]@ " (print_typlist !out_type ",") tyl
407408
in
408409
fprintf ppf "@[%a%a@]" pr_tyl tyl print_ident id
409-
| Octy_arrow (lab, ty, cty) ->
410-
fprintf ppf "@[%s%a ->@ %a@]"
411-
(if lab <> "" then lab ^ ":" else "")
412-
print_out_type_2 ty print_out_class_type cty
413410
| Octy_signature (self_ty, csil) ->
414411
let pr_param ppf = function
415412
| Some ty -> fprintf ppf "@ @[(%a)@]" !out_type ty

compiler/ml/outcometree.ml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ type out_type =
5353
| Otyp_abstract
5454
| Otyp_open
5555
| Otyp_alias of out_type * string
56-
| Otyp_arrow of (string * out_type) list * out_type
56+
| Otyp_arrow of (Asttypes.Noloc.arg_label * out_type) list * out_type
5757
| Otyp_class of bool * out_ident * out_type list
5858
| Otyp_constr of out_ident * out_type list
5959
| Otyp_manifest of out_type * out_type
@@ -74,7 +74,6 @@ and out_variant =
7474

7575
type out_class_type =
7676
| Octy_constr of out_ident * out_type list
77-
| Octy_arrow of string * out_type * out_class_type
7877
| Octy_signature of out_type option * out_class_sig_item list
7978
and out_class_sig_item =
8079
| Ocsg_constraint of out_type * out_type

compiler/ml/printtyp.ml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,6 @@ let rec tree_of_typexp ?(printing_context : printing_context option) sch ty =
616616
let args =
617617
List.map
618618
(fun (arg : Types.arg) ->
619-
let lab = string_of_label arg.lbl in
620619
let t1 =
621620
if is_optional arg.lbl then
622621
match (repr arg.typ).desc with
@@ -626,7 +625,7 @@ let rec tree_of_typexp ?(printing_context : printing_context option) sch ty =
626625
| _ -> Otyp_stuff "<hidden>"
627626
else tree_of_typexp ?printing_context sch arg.typ
628627
in
629-
(lab, t1))
628+
(Asttypes.to_noloc arg.lbl, t1))
630629
params
631630
in
632631
Otyp_arrow (args, tree_of_typexp ?printing_context sch ret)

compiler/syntax/src/res_outcome_printer.ml

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -238,37 +238,34 @@ let rec print_out_type_doc (out_type : Outcometree.out_type) =
238238

239239
and print_out_arrow_type typ =
240240
let typ_args, typ = collect_arrow_args typ in
241+
let print_labeled_arg label optional_indicator typ =
242+
Doc.group
243+
(Doc.concat
244+
[
245+
Doc.text ("~" ^ label ^ ": ");
246+
print_out_type_doc typ;
247+
optional_indicator;
248+
])
249+
in
241250
let args =
242251
Doc.join
243252
~sep:(Doc.concat [Doc.comma; Doc.line])
244253
(List.map
245254
(fun (lbl, typ) ->
246-
let lbl_len = String.length lbl in
247-
if lbl_len = 0 then print_out_type_doc typ
248-
else
249-
let lbl, optional_indicator =
250-
(* the ocaml compiler hardcodes the optional label inside the string of the label in printtyp.ml *)
251-
match String.unsafe_get lbl 0 with
252-
| '?' ->
253-
( (String.sub [@doesNotRaise]) lbl 1 (lbl_len - 1),
254-
Doc.text "=?" )
255-
| _ -> (lbl, Doc.nil)
256-
in
257-
Doc.group
258-
(Doc.concat
259-
[
260-
Doc.text ("~" ^ lbl ^ ": ");
261-
print_out_type_doc typ;
262-
optional_indicator;
263-
]))
255+
match lbl with
256+
| Asttypes.Noloc.Nolabel -> print_out_type_doc typ
257+
| Asttypes.Noloc.Labelled label ->
258+
print_labeled_arg label Doc.nil typ
259+
| Asttypes.Noloc.Optional label ->
260+
print_labeled_arg label (Doc.text "=?") typ)
264261
typ_args)
265262
in
266263
let args_doc =
267264
let needs_parens =
268265
match typ_args with
269266
| [(_, (Otyp_tuple _ | Otyp_arrow _))] -> true
270267
(* single argument should not be wrapped *)
271-
| [("", _)] -> false
268+
| [(Asttypes.Noloc.Nolabel, _)] -> false
272269
| _ -> true
273270
in
274271
if needs_parens then

packages/@rescript/runtime/RescriptTools_Docgen.res

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,29 @@ type constructor = {
1717
payload?: constructorPayload,
1818
}
1919

20-
type rec typeInSignature = {
21-
path: string,
22-
genericTypeParameters: array<typeInSignature>,
20+
@tag("kind")
21+
type rec typeInSignature =
22+
| @as("constructor") Constructor({path: string, genericTypeParameters: array<typeInSignature>})
23+
| @as("variable") Variable({name: string, weak: bool})
24+
| @as("tuple") Tuple({elements: array<typeInSignature>})
25+
| @as("function") Function({parameters: array<signatureParameter>, returnType: typeInSignature})
26+
| @as("rendered") Rendered({signature: string})
27+
and signatureParameter = {
28+
label?: string,
29+
optional: bool,
30+
@as("type") type_: typeInSignature,
2331
}
2432

2533
type signatureDetails = {
26-
parameters: array<typeInSignature>,
34+
parameters: array<signatureParameter>,
2735
returnType: typeInSignature,
2836
}
2937

3038
@tag("kind")
3139
type detail =
3240
| @as("record") Record({items: array<field>})
3341
| @as("variant") Variant({items: array<constructor>})
34-
| @as("alias") Signature({details: signatureDetails})
42+
| @as("signature") Signature({details: signatureDetails})
3543

3644
type source = {
3745
filepath: string,

packages/@rescript/runtime/RescriptTools_Docgen.resi

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,21 @@ type constructor = {
1717
payload?: constructorPayload,
1818
}
1919

20-
type rec typeInSignature = {
21-
path: string,
22-
genericTypeParameters: array<typeInSignature>,
20+
@tag("kind")
21+
type rec typeInSignature =
22+
| @as("constructor") Constructor({path: string, genericTypeParameters: array<typeInSignature>})
23+
| @as("variable") Variable({name: string, weak: bool})
24+
| @as("tuple") Tuple({elements: array<typeInSignature>})
25+
| @as("function") Function({parameters: array<signatureParameter>, returnType: typeInSignature})
26+
| @as("rendered") Rendered({signature: string})
27+
and signatureParameter = {
28+
label?: string,
29+
optional: bool,
30+
@as("type") type_: typeInSignature,
2331
}
2432

2533
type signatureDetails = {
26-
parameters: array<typeInSignature>,
34+
parameters: array<signatureParameter>,
2735
returnType: typeInSignature,
2836
}
2937

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
let labeledOptional: (~required: 'a, ~optional: array<'a>=?) => result<'a, string> = (
2+
~required,
3+
~optional=?,
4+
) => {
5+
ignore(optional)
6+
Ok(required)
7+
}
8+
9+
let takesCallback: ('a => string) => bool = _callback => true
10+
11+
let returnsTuple: int => (string, int) = value => ("value", value)
12+
13+
let returnsFunction: int => string => bool = _value => _text => true
14+
15+
let takesVariant: [#enabled | #count(int)] => unit = _variant => ()
16+
17+
let constant = 42
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/** Labeled and optional parameters keep their parameter metadata. */
2+
let labeledOptional: (~required: 'a, ~optional: array<'a>=?) => result<'a, string>
3+
4+
/** A callback remains one parameter instead of becoming outer parameters. */
5+
let takesCallback: ('a => string) => bool
6+
7+
/** A tuple remains one return-type node. */
8+
let returnsTuple: int => (string, int)
9+
10+
/** A returned function remains nested in the return type. */
11+
let returnsFunction: int => string => bool
12+
13+
/** Less common type forms remain visible through an explicit fallback. */
14+
let takesVariant: [#enabled | #count(int)] => unit
15+
16+
/** Non-functions do not receive function signature details. */
17+
let constant: int

tests/tools_tests/src/expected/DocExtraction2.res.json

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,21 @@
2929
"detail": {
3030
"kind": "signature",
3131
"details": {
32-
"parameters": [ { "path": "unit", "genericTypeParameters": [] } ],
33-
"returnType": { "path": "t", "genericTypeParameters": [] }
32+
"parameters": [
33+
{
34+
"optional": false,
35+
"type": {
36+
"kind": "constructor",
37+
"path": "unit",
38+
"genericTypeParameters": []
39+
}
40+
}
41+
],
42+
"returnType": {
43+
"kind": "constructor",
44+
"path": "t",
45+
"genericTypeParameters": []
46+
}
3447
}
3548
}
3649
},
@@ -72,9 +85,20 @@
7285
"kind": "signature",
7386
"details": {
7487
"parameters": [
75-
{ "path": "unit", "genericTypeParameters": [] }
88+
{
89+
"optional": false,
90+
"type": {
91+
"kind": "constructor",
92+
"path": "unit",
93+
"genericTypeParameters": []
94+
}
95+
}
7696
],
77-
"returnType": { "path": "t", "genericTypeParameters": [] }
97+
"returnType": {
98+
"kind": "constructor",
99+
"path": "t",
100+
"genericTypeParameters": []
101+
}
78102
}
79103
}
80104
}
@@ -95,8 +119,17 @@
95119
"detail": {
96120
"kind": "signature",
97121
"details": {
98-
"parameters": [],
99-
"returnType": { "path": "unit", "genericTypeParameters": [] }
122+
"parameters": [
123+
{
124+
"optional": false,
125+
"type": { "kind": "variable", "name": "a", "weak": false }
126+
}
127+
],
128+
"returnType": {
129+
"kind": "constructor",
130+
"path": "unit",
131+
"genericTypeParameters": []
132+
}
100133
}
101134
}
102135
}

0 commit comments

Comments
 (0)