Skip to content

Commit 63be65d

Browse files
cristianocclaude
andcommitted
Make functions and arrow types n-ary in the parsetree
Replace the curried one-parameter-per-node encoding with n-ary nodes: Ptyp_arrow of {params: arg list; ret: core_type} Pexp_fun of {params: fun_param list; body: expression; async: bool} where fun_param carries per-parameter attributes, label, default, and pattern. The arity annotation is gone from the parsetree: a function's arity is List.length params, unrepresentable wrong. ast_uncurried.ml is deleted; Ast_helper.Typ.arrow and Exp.fun_ are list-first and assert non-empty parameter lists. The typed layers are unchanged: typetexp folds the params list into the existing curried Tarrow/Ttyp_arrow chains (Some arity on the head, None inside), and typecore peels parameters one at a time, reproducing the legacy per-level type_function calls; synthesized rest-functions carry an internal #res.fun_rest attribute consumed immediately on re-entry. The Parsetree0 bridge re-curries on the way out (byte-identical wire format for external PPXes) and gathers Has_arityN / res.arity groups back into one node on the way in; bare PPX-fabricated v0 funs decode as one-parameter functions instead of the old, mostly unusable arity-None encoding. Attribute contract: in-parens parameter attributes stay on the patterns (as before); arrow-level attributes (@attr (a, b) => ...) live on the function node; p_attrs is populated only by the PPX bridge. Printing is byte-identical across the syntax test corpus; generated JavaScript is byte-identical across the test suite except UncurriedExternals.res, where `@this this => async arg => ...` now honors the written nesting (a method returning an async function) instead of absorbing the nested lambda's parameter into the method - the group-boundary ambiguity this representation removes. Signature help no longer includes the opening paren in the first parameter's highlight range, and completion debug traces lose their synthetic chain-node lines. Also fixes three latent Typ.arrow-on-empty-params paths (zero-argument externals, @deriving(accessors) zero-argument constructors in signatures, parser error recovery) that the old arrows helper silently absorbed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Cristiano Calcagno <cristianoc@users.noreply.github.com>
1 parent 110534a commit 63be65d

67 files changed

Lines changed: 1361 additions & 1060 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

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

4545
- Sync the platform npm package's compiler binaries (`packages/@rescript/<platform>/bin`) via dune promotion on every `dune build`, instead of Makefile/CI copy steps that only ran when make did: a plain `dune build` can no longer leave `cli/*.js` and the test harnesses running a stale compiler. https://github.com/rescript-lang/rescript/pull/8560
4646
- Remove unused compiler IR definitions, modules, helpers, error variants, and Typedtree fields. https://github.com/rescript-lang/rescript/pull/8551 https://github.com/rescript-lang/rescript/pull/8555
47+
- Make functions and arrow types n-ary in the parsetree: `Pexp_fun` carries a parameter list and `Ptyp_arrow` a parameter list, replacing the curried one-parameter-per-node chains with an `arity` annotation on the head. Arity is now structural (`List.length params`) and `ast_uncurried.ml` is deleted. The typed layers, cmt format, printed output, and the external-PPX wire format are unchanged. Generated JavaScript is unchanged with one deliberate exception: `@this this => async arg => ...` now means what it says (a method returning an async function) instead of absorbing the nested parameter into the method; write `@this async (this, arg) => ...` for the old meaning. https://github.com/rescript-lang/rescript/pull/8566
4748
- Give marshaled current-parsetree streams (`-as-pp`, `res_parser -print binary`) their own magic numbers, distinct from the frozen Parsetree0 wire format used for external PPXes. https://github.com/rescript-lang/rescript/pull/8561
4849
- Record the written parameter count in parsed arrow arity for externals with phantom `@as(...) _` arguments. External processing recounts after erasing phantoms, so the parser no longer needs to pre-decrement the arity or the printer to compensate for it. https://github.com/rescript-lang/rescript/pull/8563
4950
- Add the `-check-lam` compiler option, enable Lambda invariant checking in compiler tests, and remove build-profile-dependent checking. https://github.com/rescript-lang/rescript/pull/8534

analysis/src/completion_front_end.ml

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,42 +1601,63 @@ let completion_with_parser1 ~debug ~offset ~pos_cursor ~kind_file
16011601
| Some context_path ->
16021602
set_result (Cpath (CPObj (context_path, label)))
16031603
| None -> ())
1604-
| Pexp_fun
1605-
{arg_label = lbl; default = default_exp_opt; lhs = pat; rhs = e} ->
1604+
| Pexp_fun {params; body = e} ->
16061605
let old_scope = !scope in
16071606
(match (!processing_fun, !current_ctx_path) with
16081607
| None, Some ctx_path -> processing_fun := Some (ctx_path, 0)
16091608
| _ -> ());
1610-
let arg_context_path =
1611-
match !processing_fun with
1612-
| None -> None
1613-
| Some (ctx_path, current_unlabelled_count) ->
1614-
(processing_fun :=
1615-
match lbl with
1616-
| Nolabel -> Some (ctx_path, current_unlabelled_count + 1)
1617-
| _ -> Some (ctx_path, current_unlabelled_count));
1618-
if Debug.verbose () then
1619-
print_endline "[expr_iter] Completing for argument value";
1620-
Some
1621-
(Completable.CArgument
1622-
{
1623-
function_context_path = ctx_path;
1624-
argument_label =
1625-
(match lbl with
1626-
| Nolabel ->
1627-
Unlabelled
1628-
{argument_position = current_unlabelled_count}
1629-
| Optional {txt = name} -> Optional name
1630-
| Labelled {txt = name} -> Labelled name);
1631-
})
1609+
let param_has_cursor ({p_default; p_pat} : Parsetree.fun_param) =
1610+
loc_has_cursor p_pat.ppat_loc
1611+
|| loc_is_empty p_pat.ppat_loc
1612+
||
1613+
match p_default with
1614+
| Some default_exp -> loc_has_cursor default_exp.pexp_loc
1615+
| None -> false
16321616
in
1633-
(match default_exp_opt with
1634-
| None -> ()
1635-
| Some default_exp -> iterator.expr iterator default_exp);
1636-
if loc_has_cursor e.pexp_loc = false then
1637-
complete_pattern ?context_path:arg_context_path pat;
1638-
scope_pattern ?context_path:arg_context_path pat;
1639-
iterator.pat iterator pat;
1617+
let rec iter_params params =
1618+
match params with
1619+
| [] -> ()
1620+
| (({p_lbl = lbl; p_default = default_exp_opt; p_pat = pat} :
1621+
Parsetree.fun_param) as param)
1622+
:: rest ->
1623+
let arg_context_path =
1624+
match !processing_fun with
1625+
| None -> None
1626+
| Some (ctx_path, current_unlabelled_count) ->
1627+
(processing_fun :=
1628+
match lbl with
1629+
| Nolabel -> Some (ctx_path, current_unlabelled_count + 1)
1630+
| _ -> Some (ctx_path, current_unlabelled_count));
1631+
if Debug.verbose () then
1632+
print_endline "[expr_iter] Completing for argument value";
1633+
Some
1634+
(Completable.CArgument
1635+
{
1636+
function_context_path = ctx_path;
1637+
argument_label =
1638+
(match lbl with
1639+
| Nolabel ->
1640+
Unlabelled
1641+
{argument_position = current_unlabelled_count}
1642+
| Optional {txt = name} -> Optional name
1643+
| Labelled {txt = name} -> Labelled name);
1644+
})
1645+
in
1646+
(match default_exp_opt with
1647+
| None -> ()
1648+
| Some default_exp -> iterator.expr iterator default_exp);
1649+
(* Only complete the pattern if the cursor is not in a later
1650+
part of the function: a following parameter or the body. *)
1651+
if
1652+
loc_has_cursor e.pexp_loc = false
1653+
&& (param_has_cursor param
1654+
|| not (List.exists param_has_cursor rest))
1655+
then complete_pattern ?context_path:arg_context_path pat;
1656+
scope_pattern ?context_path:arg_context_path pat;
1657+
iterator.pat iterator pat;
1658+
iter_params rest
1659+
in
1660+
iter_params params;
16401661
iterator.expr iterator e;
16411662
scope := old_scope;
16421663
processed := true

analysis/src/dump_ast.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ and print_expr_item expr ~pos ~indentation =
242242
| None -> ""
243243
| Some expr -> "," ^ print_expr_item expr ~pos ~indentation)
244244
^ ")"
245-
| Pexp_fun {arg_label = arg; lhs = pattern; rhs = next_expr} ->
245+
| Pexp_fun {params = {p_lbl = arg; p_pat = pattern} :: _; body = next_expr} ->
246246
"Pexp_fun(\n"
247247
^ add_indentation (indentation + 1)
248248
^ "arg: "

analysis/src/hint.ml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,7 @@ let inlay ~source ~kind_file ~pos ~max_length ~full ~state ~debug =
6262
( Pexp_constant _ | Pexp_tuple _ | Pexp_record _ | Pexp_variant _
6363
| Pexp_apply _ | Pexp_match _ | Pexp_construct _ | Pexp_ifthenelse _
6464
| Pexp_array _ | Pexp_ident _ | Pexp_try _ | Pexp_send _
65-
| Pexp_field _ | Pexp_open _
66-
| Pexp_fun {arity = Some _} );
65+
| Pexp_field _ | Pexp_open _ | Pexp_fun _ );
6766
};
6867
} ->
6968
push vb.pvb_pat.ppat_loc Type

analysis/src/signature_help.ml

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -103,24 +103,27 @@ let find_function_type ~debug ~source ~kind_file ~pos ~full ~state =
103103
Some (args, docstring, type_expr, package, env, file)
104104
| _ -> None))
105105

106-
(* Extracts all parameters from a parsed function signature *)
106+
(* Extracts all parameters from a parsed function signature. The result type
107+
is not entered: a returned function's parameters cannot be passed at this
108+
call site. *)
107109
let extract_parameters ~signature ~type_str_for_parser ~label_prefix_len =
108110
match signature with
109-
| [{Parsetree.psig_desc = Psig_value {pval_type = expr}}]
110-
when match expr.ptyp_desc with
111-
| Ptyp_arrow _ -> true
112-
| _ -> false ->
113-
let rec extract_params expr params =
114-
match expr with
115-
| {
116-
(* Gotcha: functions with multiple arugments are modelled as a series of single argument functions. *)
117-
Parsetree.ptyp_desc = Ptyp_arrow {arg; ret = next_function_expr};
118-
ptyp_loc;
119-
} ->
111+
| [
112+
{
113+
Parsetree.psig_desc =
114+
Psig_value {pval_type = {ptyp_desc = Ptyp_arrow {params = args}}};
115+
};
116+
] ->
117+
List.map
118+
(fun (arg : Parsetree.arg) ->
119+
let start_loc =
120+
(* For a labeled argument the label precedes the type. *)
121+
match arg.lbl with
122+
| Asttypes.Labelled {loc} | Optional {loc} -> loc |> Loc.start
123+
| Nolabel -> arg.typ.ptyp_loc |> Loc.start
124+
in
120125
let start_offset =
121-
ptyp_loc |> Loc.start
122-
|> Pos.position_to_offset type_str_for_parser
123-
|> Option.get
126+
start_loc |> Pos.position_to_offset type_str_for_parser |> Option.get
124127
in
125128
let end_offset =
126129
arg.typ.ptyp_loc |> Loc.end_
@@ -133,18 +136,12 @@ let extract_parameters ~signature ~type_str_for_parser ~label_prefix_len =
133136
| Asttypes.Optional _ -> end_offset + 2
134137
| _ -> end_offset
135138
in
136-
extract_params next_function_expr
137-
(params
138-
@ [
139-
( arg.lbl,
140-
(* Remove the label prefix offset here, since we're not showing
141-
that to the end user. *)
142-
start_offset - label_prefix_len,
143-
end_offset - label_prefix_len );
144-
])
145-
| _ -> params
146-
in
147-
extract_params expr []
139+
( arg.lbl,
140+
(* Remove the label prefix offset here, since we're not
141+
showing that to the end user. *)
142+
start_offset - label_prefix_len,
143+
end_offset - label_prefix_len ))
144+
args
148145
| _ -> []
149146

150147
(* Finds what parameter is active, if any *)

analysis/src/xform.ml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ module Add_braces_to_fn = struct
261261
| _ -> false
262262
in
263263
(match e.pexp_desc with
264-
| Pexp_fun {rhs = body_expr}
264+
| Pexp_fun {body = body_expr}
265265
when Loc.has_pos ~pos body_expr.pexp_loc
266266
&& is_braced_expr body_expr = false
267267
&& is_function body_expr = false ->
@@ -303,18 +303,18 @@ module Add_type_annotation = struct
303303
result := Some (if is_unlabeled_only_arg then WithParens else Plain)
304304
| _ -> ()
305305
in
306-
let rec process_function ~arg_num (e : Parsetree.expression) =
306+
let process_function (e : Parsetree.expression) =
307307
match e.pexp_desc with
308-
| Pexp_fun {arg_label; lhs = pat; rhs = e} ->
309-
let is_unlabeled_only_arg =
310-
arg_num = 1 && arg_label = Nolabel
311-
&&
312-
match e.pexp_desc with
313-
| Pexp_fun _ -> false
314-
| _ -> true
308+
| Pexp_fun {params} ->
309+
let single_param =
310+
match params with
311+
| [_] -> true
312+
| _ -> false
315313
in
316-
process_pattern ~is_unlabeled_only_arg pat;
317-
process_function ~arg_num:(arg_num + 1) e
314+
params
315+
|> List.iter (fun ({p_lbl; p_pat} : Parsetree.fun_param) ->
316+
let is_unlabeled_only_arg = single_param && p_lbl = Nolabel in
317+
process_pattern ~is_unlabeled_only_arg p_pat)
318318
| _ -> ()
319319
in
320320
let structure_item (iterator : Ast_iterator.iterator)
@@ -327,7 +327,7 @@ module Add_type_annotation = struct
327327
if not is_jsx_component then process_pattern vb.pvb_pat;
328328
process_function vb.pvb_expr
329329
in
330-
bindings |> List.iter (process_binding ~arg_num:1);
330+
bindings |> List.iter process_binding;
331331
Ast_iterator.default_iterator.structure_item iterator si
332332
| _ -> Ast_iterator.default_iterator.structure_item iterator si
333333
in

compiler/frontend/ast_compatible.ml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,16 @@ let app2 ?(loc = default_loc) ?(attrs = []) fn arg1 arg2 : expression =
7373
};
7474
}
7575

76-
let fun_ ?(loc = default_loc) ?(attrs = []) ?(async = false) ~arity pat exp =
76+
let fun_ ?(loc = default_loc) ?(attrs = []) ?(async = false) pat exp =
7777
{
7878
pexp_loc = loc;
7979
pexp_attributes = attrs;
8080
pexp_desc =
8181
Pexp_fun
8282
{
83-
arg_label = Nolabel;
84-
default = None;
85-
lhs = pat;
86-
rhs = exp;
87-
arity;
83+
params =
84+
[{p_attrs = []; p_lbl = Nolabel; p_default = None; p_pat = pat}];
85+
body = exp;
8886
async;
8987
};
9088
}

compiler/frontend/ast_compatible.mli

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ val fun_ :
5959
?loc:Location.t ->
6060
?attrs:attrs ->
6161
?async:bool ->
62-
arity:int option ->
6362
pattern ->
6463
expression ->
6564
expression

compiler/frontend/ast_core_type.ml

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ let from_labels ~loc arity labels : t =
9797
Ext_list.map2 labels tyvars (fun label tyvar ->
9898
{Parsetree.attrs = []; lbl = Asttypes.Labelled label; typ = tyvar})
9999
in
100-
Typ.arrows ~loc args result_type
100+
match args with
101+
| [] -> result_type
102+
| _ -> Typ.arrow ~loc args result_type
101103

102104
let make_obj ~loc xs = Typ.object_ ~loc xs Closed
103105

@@ -108,40 +110,14 @@ let make_obj ~loc xs = Typ.object_ ~loc xs Closed
108110
{[ 'a -> ('a. 'a -> 'b) ]}
109111
110112
*)
111-
let rec get_uncurry_arity_aux (ty : t) acc =
112-
match ty.ptyp_desc with
113-
| Ptyp_arrow {ret = new_ty} -> get_uncurry_arity_aux new_ty (succ acc)
114-
| Ptyp_poly (_, ty) -> get_uncurry_arity_aux ty acc
115-
| _ -> acc
116-
117-
(**
118-
{[ unit -> 'b ]} return arity 1
119-
{[ unit -> 'a1 -> a2']} arity 2
120-
{[ 'a1 -> 'a2 -> ... 'aN -> 'b ]} return arity N
121-
*)
122113
let get_curry_arity (ty : t) =
123114
match ty.ptyp_desc with
124-
| Ptyp_arrow {arity = Some arity} -> arity
125-
| _ -> get_uncurry_arity_aux ty 0
115+
| Ptyp_arrow {params} -> List.length params
116+
| _ -> 0
126117

127118
let is_arity_one ty = get_curry_arity ty = 1
128119

129120
let list_of_arrow (ty : t) : t * Parsetree.arg list =
130-
let rec aux (ty : t) acc =
131-
match ty.ptyp_desc with
132-
| Ptyp_arrow {arg; ret; arity} when arity = None || acc = [] ->
133-
aux ret (arg :: acc)
134-
| Ptyp_poly _ ->
135-
(* unreachable: [list_of_arrow] only recurses into an arrow's return
136-
(and is only ever called on an external's type annotation), so to get
137-
here a [Ptyp_poly] would have to sit in an external's arg/return
138-
position. The external type — and every arrow arg/return — is parsed
139-
by [parse_typ_expr], which never routes to [parse_poly_type_expr]; an
140-
inline `'a. …` there is a plain syntax error ("Did you forget a `=`").
141-
[Ptyp_poly] is produced only for record/object field types and
142-
signature `val` descriptions, and a field-nested poly is a non-arrow
143-
leaf that [list_of_arrow] stops at, never the recursed return. *)
144-
assert false
145-
| _ -> (ty, List.rev acc)
146-
in
147-
aux ty []
121+
match ty.ptyp_desc with
122+
| Ptyp_arrow {params; ret} -> (ret, params)
123+
| _ -> (ty, [])

compiler/frontend/ast_core_type_class_type.ml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,18 @@ let default_typ_mapper = Bs_ast_mapper.default_mapper.typ
6767
let typ_mapper (self : Bs_ast_mapper.mapper) (ty : Parsetree.core_type) =
6868
let loc = ty.ptyp_loc in
6969
match ty.ptyp_desc with
70-
| Ptyp_arrow {arity}
70+
| Ptyp_arrow {params = _}
7171
(* let it go without regard label names,
7272
it will report error later when the label is not empty
7373
*)
7474
-> (
7575
match fst (Ast_attributes.process_attributes_rev ty.ptyp_attributes) with
76-
| Meth_callback _ ->
77-
Ast_typ_uncurry.to_method_callback_type loc self ~arity ty
76+
| Meth_callback _ -> (
77+
match ty.ptyp_desc with
78+
| Ptyp_arrow {params} ->
79+
Ast_typ_uncurry.to_method_callback_type loc self
80+
~arity:(List.length params) ty
81+
| _ -> assert false)
7882
| Nothing -> Bs_ast_mapper.default_mapper.typ self ty)
7983
| Ptyp_object (methods, closed_flag) ->
8084
let ( +> ) attr (typ : Parsetree.core_type) =
@@ -100,7 +104,7 @@ let typ_mapper (self : Bs_ast_mapper.mapper) (ty : Parsetree.core_type) =
100104
| Meth_callback attr, attrs -> (attrs, attr +> ty)
101105
in
102106
Ast_compatible.object_field name attrs
103-
(Ast_helper.Typ.arrows ~loc
107+
(Ast_helper.Typ.arrow ~loc
104108
[{attrs = []; lbl = Nolabel; typ = self.typ self core_type}]
105109
(Ast_literal.type_unit ~loc ()))
106110
in

0 commit comments

Comments
 (0)