Skip to content

Commit f472651

Browse files
cristianocclaude
andcommitted
Make locally abstract value constraints structural in the parsetree
Replace the desugared encoding of [let f: type a. t = e] - a Ptyp_poly pattern constraint plus a Pexp_newtype chain over a Pexp_constraint, with the type stored twice and no AST invariant ensuring that the copies agree - with a structural field on the binding: pvb_constraint: {pvc_newtypes: string loc list; pvc_type: core_type} Only the [type a.] form uses the field; plain constraints and explicit polymorphic annotations keep their existing representation. The type is stored once, and [varify_constructors] now runs in exactly one place, inside the type checker. With functions already carrying their locally abstract type parameters in Pexp_fun.newtypes, this removes the last place where the parser constructs Pexp_newtype. Delete the constructor from the current parsetree, along with the Texp_newtype exp_extra, which had no consumer beyond no-op iterators and the debug printer. The CMT magic number is bumped to Caml1999T024; the CMI format is unchanged. Type checking follows the same design as the function case (and OCaml 5.x): type_let introduces the locally abstract types into scope via type_newtype, types the body against the constraint, and unifies with the pattern's polymorphic type. This preserves the semantics of the former desugaring. The frozen v0 PPX bridge expands the field back into the historical wrapper-chain encoding and recognizes well-formed instances of that encoding on the way in, verified by unit tests. A v0 Pexp_newtype chain that cannot be represented - such as one that does not enclose ReScript's Function$ encoding, or one whose structure was changed by a PPX - now becomes a located ocaml.error extension with an explicit message. This is the only intentional reduction in accepted v0 PPX output. Formatter bug fix covered by syntax fixtures: a trailing comment between the constraint type and [=] is no longer dropped. An end-to-end GADT test checks that refinement still works with the new binding field. Signed-Off-By: Cristiano Calcagno <cristianoc@users.noreply.github.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent ea2e2cf commit f472651

45 files changed

Lines changed: 610 additions & 384 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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
- Fix argument evaluation order when a function call is inlined: the beta reducer stacked argument bindings in reverse parameter order, so the last argument was evaluated first when arguments could not be substituted directly. https://github.com/rescript-lang/rescript/pull/8572
3131
- Preserve parentheses around multiplication, division, and modulo expressions used as exponents. https://github.com/rescript-lang/rescript/pull/8550
3232
- Make a function's locally abstract types (`(type t, x) => ...`) part of the function AST node instead of a chain of wrapper nodes. Fixes the formatter dropping the association of attributes with their `type` group (`(@attr type t, x, @attr2 type s, y)` used to print as `@attr @attr2` on the function) and comments written next to a type parameter migrating onto the following value parameter.
33+
- Preserve trailing comments between the type and `=` in locally abstract value constraints (`let f: type a. t /* comment */ = value`).
3334
- Enforce function arity in interface/module inclusion and type coercion. Previously a curried implementation (e.g. `int => int => int`) could satisfy an uncurried interface (`(int, int) => int`) or be coerced to it, which could miscompile calls made through the interface type. Such mismatches are now compile errors with an explanatory hint. https://github.com/rescript-lang/rescript/pull/8559
3435
- Fix termination-analysis false positives for functions whose progress flows through un-annotated helpers: collecting the callees of a function binding was accidentally disabled in 2024 (the collection guard required a node shape that uncurried code never produces), so helpers calling `@progress` functions were no longer added to the function table. https://github.com/rescript-lang/rescript/pull/8568
3536
- Fix default values of optional parameters being computed at the wrong time for curried functions: in `(~x=default, y) => (~z=default, w) => ...`, `x`'s default was only computed when the *inner* function was applied. Each default is now computed when its own parameter group is applied. https://github.com/rescript-lang/rescript/pull/8568
@@ -49,6 +50,7 @@
4950

5051
- 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
5152
- 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
53+
- Make locally abstract value constraints (`let f: type a. t = value`) structural in the parsetree, remove the obsolete `Pexp_newtype` and `Texp_newtype` wrapper metadata, and keep the old encoding confined to the frozen external-PPX bridge. The CMT magic number is bumped to `Caml1999T024`.
5254
- Eliminate the `Pjs_fn_make`/`Pjs_fn_make_unit` arity-adjustment primitives and the `unsafe_adjust_to_arity` machinery: with structural arity, functions are constructed at their final arity, so the enforcement layer (and the active-pattern currying split it compensated for) is deleted. Generated code improves: no adapter closures for patterns on mutable fields, better constant propagation and name preservation, and recursive modules whose members are plain functions compile statically without the runtime bootstrap. https://github.com/rescript-lang/rescript/pull/8570
5355
- Cleanups enabled by structural arity: remove the unreachable `Too_many_arguments` error and the `?in_function` threading through the type checker that existed only to decorate it; remove the dead `function$`-vs-arrow unification bridge, `Ctype.arity`, and the unused parsetree arity helpers; deduplicate the analysis arrow-flattening helpers. https://github.com/rescript-lang/rescript/pull/8569
5456

analysis/src/completion_front_end.ml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,8 @@ let completion_with_parser1 ~debug ~offset ~pos_cursor ~kind_file
769769
let old_in_jsx_context = !in_jsx_context in
770770
if Utils.is_jsx_component value_binding then in_jsx_context := true;
771771
(match value_binding with
772-
| {pvb_pat = {ppat_desc = Ppat_constraint (_pat, core_type)}; pvb_expr}
772+
| {pvb_pat = {ppat_desc = Ppat_constraint (_, core_type)}; pvb_expr}
773+
| {pvb_constraint = Some {pvc_type = core_type}; pvb_expr}
773774
when loc_has_cursor pvb_expr.pexp_loc -> (
774775
(* Expression with derivable type annotation.
775776
E.g: let x: someRecord = {<com>} *)
@@ -806,9 +807,14 @@ let completion_with_parser1 ~debug ~offset ~pos_cursor ~kind_file
806807
{context_path = CTypeAtPos loc; prefix; nested = List.rev nested})
807808
| _ -> ())
808809
| {
809-
pvb_pat = {ppat_desc = Ppat_constraint (_pat, core_type); ppat_loc};
810-
pvb_expr;
811-
}
810+
pvb_pat = {ppat_desc = Ppat_constraint (_, core_type); ppat_loc};
811+
pvb_expr;
812+
}
813+
| {
814+
pvb_pat = {ppat_loc};
815+
pvb_expr;
816+
pvb_constraint = Some {pvc_type = core_type};
817+
}
812818
when loc_has_cursor value_binding.pvb_loc
813819
&& loc_has_cursor ppat_loc = false
814820
&& loc_has_cursor pvb_expr.pexp_loc = false

analysis/src/dump_ast.ml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,11 +298,25 @@ and print_expr_item expr ~pos ~indentation =
298298
| v -> Printf.sprintf "<unimplemented_pexp_desc: %s>" (Utils.identify_pexp v)
299299

300300
let print_value_binding value ~pos ~indentation =
301+
let constraint_ =
302+
match value.Parsetree.pvb_constraint with
303+
| None -> ""
304+
| Some {pvc_newtypes; pvc_type} ->
305+
"\n"
306+
^ add_indentation indentation
307+
^ "constraint: type "
308+
^ (pvc_newtypes
309+
|> List.map (fun ({Location.txt} as name) ->
310+
(name |> print_loc_denominator_loc ~pos) ^ txt)
311+
|> String.concat " ")
312+
^ ". "
313+
^ print_core_type pvc_type ~pos
314+
in
301315
print_attributes value.Parsetree.pvb_attributes
302316
^ "value" ^ ":\n"
303317
^ add_indentation (indentation + 1)
304318
^ (value.pvb_pat |> print_pattern ~pos ~indentation:(indentation + 1))
305-
^ "\n"
319+
^ constraint_ ^ "\n"
306320
^ add_indentation indentation
307321
^ "expr:\n"
308322
^ add_indentation (indentation + 1)

analysis/src/hint.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ let inlay ~source ~kind_file ~pos ~max_length ~full ~state ~debug =
5656
(match vb with
5757
| {
5858
pvb_pat = {ppat_desc = Ppat_var _};
59+
pvb_constraint = None;
5960
pvb_expr =
6061
{
6162
pexp_desc =
@@ -125,6 +126,7 @@ let code_lens ~source ~kind_file ~full ~debug =
125126
(match vb with
126127
| {
127128
pvb_pat = {ppat_desc = Ppat_var _; ppat_loc};
129+
pvb_constraint = None;
128130
pvb_expr = {pexp_desc = Pexp_fun _};
129131
} ->
130132
push ppat_loc

analysis/src/utils.ml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ let identify_pexp pexp =
111111
| Pexp_letmodule _ -> "Pexp_letmodule"
112112
| Pexp_letexception _ -> "Pexp_letexception"
113113
| Pexp_assert _ -> "Pexp_assert"
114-
| Pexp_newtype _ -> "Pexp_newtype"
115114
| Pexp_pack _ -> "Pexp_pack"
116115
| Pexp_extension _ -> "Pexp_extension"
117116
| Pexp_open _ -> "Pexp_open"

analysis/src/xform.ml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -322,10 +322,13 @@ module Add_type_annotation = struct
322322
match si.pstr_desc with
323323
| Pstr_value (_recFlag, bindings) ->
324324
let process_binding (vb : Parsetree.value_binding) =
325-
(* Can't add a type annotation to a jsx component, or the compiler crashes *)
326-
let is_jsx_component = Utils.is_jsx_component vb in
327-
if not is_jsx_component then process_pattern vb.pvb_pat;
328-
process_function vb.pvb_expr
325+
match vb.pvb_constraint with
326+
| Some _ -> ()
327+
| None ->
328+
(* Can't add a type annotation to a jsx component, or the compiler crashes *)
329+
let is_jsx_component = Utils.is_jsx_component vb in
330+
if not is_jsx_component then process_pattern vb.pvb_pat;
331+
process_function vb.pvb_expr
329332
in
330333
bindings |> List.iter process_binding;
331334
Ast_iterator.default_iterator.structure_item iterator si

compiler/ext/config.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ and ast0_impl_magic_number = "Caml1999M022"
1313

1414
and ast0_intf_magic_number = "Caml1999N022"
1515

16-
and cmt_magic_number = "Caml1999T023"
16+
and cmt_magic_number = "Caml1999T024"
1717

1818
let load_path = ref ([] : string list)

compiler/frontend/ast_tuple_pattern_flatten.ml

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,26 @@ let flattern_tuple_pattern_vb (self : Bs_ast_mapper.mapper)
4545
(vb : Parsetree.value_binding) (acc : Parsetree.value_binding list) :
4646
Parsetree.value_binding list =
4747
let pvb_pat = self.pat self vb.pvb_pat in
48+
let pvb_constraint =
49+
Option.map
50+
(fun {Parsetree.pvc_newtypes; pvc_type} ->
51+
{
52+
Parsetree.pvc_newtypes =
53+
List.map
54+
(fun (name : string Asttypes.loc) ->
55+
{name with loc = self.location self name.loc})
56+
pvc_newtypes;
57+
pvc_type = self.typ self pvc_type;
58+
})
59+
vb.pvb_constraint
60+
in
4861
let pvb_expr = self.expr self vb.pvb_expr in
4962
let pvb_attributes = self.attributes self vb.pvb_attributes in
50-
match (pvb_pat.ppat_desc, pvb_expr.pexp_desc) with
51-
| Ppat_tuple xs, _ when List.for_all is_simple_pattern xs -> (
63+
match (pvb_constraint, pvb_pat.ppat_desc, pvb_expr.pexp_desc) with
64+
| Some _, _, _ ->
65+
{pvb_pat; pvb_expr; pvb_constraint; pvb_loc = vb.pvb_loc; pvb_attributes}
66+
:: acc
67+
| None, Ppat_tuple xs, _ when List.for_all is_simple_pattern xs -> (
5268
match Ast_open_cxt.destruct_open_tuple pvb_expr [] with
5369
| Some (wholes, es, tuple_attributes)
5470
when Ext_list.for_all xs is_simple_pattern && Ext_list.same_length es xs
@@ -59,16 +75,20 @@ let flattern_tuple_pattern_vb (self : Bs_ast_mapper.mapper)
5975
{
6076
pvb_pat = pat;
6177
pvb_expr = Ast_open_cxt.restore_exp exp wholes;
78+
pvb_constraint = None;
6279
pvb_attributes;
6380
pvb_loc = vb.pvb_loc;
6481
}
6582
:: acc)
66-
| _ -> {pvb_pat; pvb_expr; pvb_loc = vb.pvb_loc; pvb_attributes} :: acc)
67-
| Ppat_record (_, _, Some rest), Pexp_pack {pmod_desc = Pmod_ident _} ->
83+
| _ ->
84+
{pvb_pat; pvb_expr; pvb_constraint; pvb_loc = vb.pvb_loc; pvb_attributes}
85+
:: acc)
86+
| None, Ppat_record (_, _, Some rest), Pexp_pack {pmod_desc = Pmod_ident _} ->
6887
Location.raise_errorf ~loc:rest.rest_loc
6988
"Record rest patterns are not supported when destructuring modules. Bind \
7089
the module fields explicitly."
71-
| Ppat_record (lid_pats, _, None), Pexp_pack {pmod_desc = Pmod_ident id} ->
90+
| None, Ppat_record (lid_pats, _, None), Pexp_pack {pmod_desc = Pmod_ident id}
91+
->
7292
Ext_list.map_append lid_pats acc (fun {lid; x = pat} ->
7393
match lid.txt with
7494
| Lident s ->
@@ -77,13 +97,16 @@ let flattern_tuple_pattern_vb (self : Bs_ast_mapper.mapper)
7797
pvb_expr =
7898
Ast_helper.Exp.ident ~loc:lid.loc
7999
{lid with txt = Ldot (id.txt, s)};
100+
pvb_constraint = None;
80101
pvb_attributes = [];
81102
pvb_loc = pat.ppat_loc;
82103
}
83104
| _ ->
84105
Location.raise_errorf ~loc:lid.loc
85106
"Not supported pattern match on modules")
86-
| _ -> {pvb_pat; pvb_expr; pvb_loc = vb.pvb_loc; pvb_attributes} :: acc
107+
| _ ->
108+
{pvb_pat; pvb_expr; pvb_constraint; pvb_loc = vb.pvb_loc; pvb_attributes}
109+
:: acc
87110

88111
let value_bindings_mapper (self : Bs_ast_mapper.mapper)
89112
(vbs : Parsetree.value_binding list) =

compiler/frontend/ast_uncurry_gen.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
open Ast_helper
2626

2727
(* Handling `fun [@this]` used in `object [@bs] end` *)
28-
let to_method_callback ~async loc (self : Bs_ast_mapper.mapper)
28+
let to_method_callback ~async ~newtypes loc (self : Bs_ast_mapper.mapper)
2929
(params : Parsetree.fun_param list) body : Parsetree.expression_desc =
3030
match params with
3131
| [] -> assert false
@@ -53,7 +53,7 @@ let to_method_callback ~async loc (self : Bs_ast_mapper.mapper)
5353
let arity = List.length mapped_params in
5454
let body =
5555
Ast_async.make_function_async ~async
56-
(Ast_helper.Exp.fun_ ~loc ~async mapped_params result)
56+
(Ast_helper.Exp.fun_ ~loc ~async ~newtypes mapped_params result)
5757
in
5858
let arity_s = string_of_int arity in
5959
Stack.pop Js_config.self_stack |> ignore;

compiler/frontend/ast_uncurry_gen.mli

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

2525
val to_method_callback :
2626
async:bool ->
27+
newtypes:(string Asttypes.loc * Parsetree.attributes) list ->
2728
Location.t ->
2829
Bs_ast_mapper.mapper ->
2930
Parsetree.fun_param list ->

0 commit comments

Comments
 (0)