diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e77acfd55..81c8720b6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - 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 - Preserve parentheses around multiplication, division, and modulo expressions used as exponents. https://github.com/rescript-lang/rescript/pull/8550 +- 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. https://github.com/rescript-lang/rescript/pull/8574 - 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 - 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 - 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 diff --git a/compiler/frontend/bs_ast_mapper.ml b/compiler/frontend/bs_ast_mapper.ml index 9edf483334..07287bfded 100644 --- a/compiler/frontend/bs_ast_mapper.ml +++ b/compiler/frontend/bs_ast_mapper.ml @@ -325,8 +325,12 @@ module E = struct sub vbs) (sub.expr sub e) (* #end *) - | Pexp_fun {params; body; async} -> + | Pexp_fun {newtypes; params; body; async} -> fun_ ~loc ~attrs ~async + ~newtypes: + (List.map + (fun (name, attrs) -> (map_loc sub name, sub.attributes sub attrs)) + newtypes) (List.map (fun (param : Parsetree.fun_param) -> { diff --git a/compiler/frontend/bs_builtin_ppx.ml b/compiler/frontend/bs_builtin_ppx.ml index 6157cea22a..24a8d7adb7 100644 --- a/compiler/frontend/bs_builtin_ppx.ml +++ b/compiler/frontend/bs_builtin_ppx.ml @@ -95,7 +95,7 @@ let expr_mapper ~async_context ~in_function_def (self : mapper) | Pexp_newtype (s, body) -> let res = self.expr self body in {e with pexp_desc = Pexp_newtype (s, res)} - | Pexp_fun {params; body; async} -> ( + | Pexp_fun {newtypes; params; body; async} -> ( match Ast_attributes.process_attributes_rev e.pexp_attributes with | Nothing, _ -> (* Handle @async x => y => ... is in async context *) @@ -116,19 +116,29 @@ let expr_mapper ~async_context ~in_function_def (self : mapper) mapper did (GH #7974). *) let body = self.expr self body in in_function_def := saved_in_function_def; + let newtypes = + Ext_list.map newtypes (fun (name, nt_attrs) -> + (name, self.attributes self nt_attrs)) + in let mapped = - Ast_helper.Exp.fun_ ~loc:e.pexp_loc ~attrs ~async params body + Ast_helper.Exp.fun_ ~loc:e.pexp_loc ~attrs ~async ~newtypes params body in Ast_async.make_function_async ~async mapped | Meth_callback _, pexp_attributes -> (* FIXME: does it make sense to have a label for [this] ? *) async_context := false; - { - e with - pexp_desc = - Ast_uncurry_gen.to_method_callback ~async e.pexp_loc self params body; - pexp_attributes; - }) + let callback = + { + e with + pexp_desc = + Ast_uncurry_gen.to_method_callback ~async e.pexp_loc self params + body; + pexp_attributes; + } + in + (* Keep the locally abstract types in scope around the callback. *) + Ext_list.fold_right newtypes callback (fun (name, nt_attrs) acc -> + Ast_helper.Exp.newtype ~loc:e.pexp_loc ~attrs:nt_attrs name acc)) | Pexp_apply _ -> Ast_exp_apply.app_exp_mapper e self | Pexp_match ( b, diff --git a/compiler/ml/ast_helper.ml b/compiler/ml/ast_helper.ml index ed29a89a78..bb19e9b1e1 100644 --- a/compiler/ml/ast_helper.ml +++ b/compiler/ml/ast_helper.ml @@ -158,9 +158,9 @@ module Exp = struct let ident ?loc ?attrs a = mk ?loc ?attrs (Pexp_ident a) let constant ?loc ?attrs a = mk ?loc ?attrs (Pexp_constant a) let let_ ?loc ?attrs a b c = mk ?loc ?attrs (Pexp_let (a, b, c)) - let fun_ ?loc ?attrs ?(async = false) params body = + let fun_ ?loc ?attrs ?(async = false) ?(newtypes = []) params body = assert (params <> []); - mk ?loc ?attrs (Pexp_fun {params; body; async}) + mk ?loc ?attrs (Pexp_fun {newtypes; params; body; async}) let fun_param ?(attrs = []) ?default lbl pat = {p_attrs = attrs; p_lbl = lbl; p_default = default; p_pat = pat} diff --git a/compiler/ml/ast_helper.mli b/compiler/ml/ast_helper.mli index 789b3d669a..652d248ab5 100644 --- a/compiler/ml/ast_helper.mli +++ b/compiler/ml/ast_helper.mli @@ -134,6 +134,7 @@ module Exp : sig ?loc:loc -> ?attrs:attrs -> ?async:bool -> + ?newtypes:(str * attrs) list -> fun_param list -> expression -> expression diff --git a/compiler/ml/ast_iterator.ml b/compiler/ml/ast_iterator.ml index 80bd5b78cb..46777f25df 100644 --- a/compiler/ml/ast_iterator.ml +++ b/compiler/ml/ast_iterator.ml @@ -289,7 +289,12 @@ module E = struct | Pexp_let (_r, vbs, e) -> List.iter (sub.value_binding sub) vbs; sub.expr sub e - | Pexp_fun {params; body} -> + | Pexp_fun {newtypes; params; body} -> + List.iter + (fun (name, attrs) -> + iter_loc sub name; + sub.attributes sub attrs) + newtypes; List.iter (fun {p_default; p_pat} -> iter_opt (sub.expr sub) p_default; diff --git a/compiler/ml/ast_mapper.ml b/compiler/ml/ast_mapper.ml index 5749aa11c8..cde7ccfa34 100644 --- a/compiler/ml/ast_mapper.ml +++ b/compiler/ml/ast_mapper.ml @@ -288,8 +288,12 @@ module E = struct | Pexp_constant x -> constant ~loc ~attrs x | Pexp_let (r, vbs, e) -> let_ ~loc ~attrs r (List.map (sub.value_binding sub) vbs) (sub.expr sub e) - | Pexp_fun {params; body; async} -> + | Pexp_fun {newtypes; params; body; async} -> fun_ ~loc ~attrs ~async + ~newtypes: + (List.map + (fun (name, attrs) -> (map_loc sub name, sub.attributes sub attrs)) + newtypes) (List.map (fun param -> { diff --git a/compiler/ml/ast_mapper_from0.ml b/compiler/ml/ast_mapper_from0.ml index d41715f92c..2f4166b86a 100644 --- a/compiler/ml/ast_mapper_from0.ml +++ b/compiler/ml/ast_mapper_from0.ml @@ -706,7 +706,8 @@ module E = struct in { e1 with - pexp_desc = Pexp_fun {params; body; async = f.async}; + pexp_desc = + Pexp_fun {newtypes = []; params; body; async = f.async}; pexp_attributes = e1.pexp_attributes @ node_attrs; }) | _ -> exp1) @@ -779,8 +780,55 @@ module E = struct | Pexp_lazy _ -> failwith "Pexp_lazy is no longer present in ReScript" | Pexp_poly _ -> failwith "Pexp_poly is no longer present in ReScript" | Pexp_object () -> assert false - | Pexp_newtype (s, e) -> - newtype ~loc ~attrs (map_loc sub s) (sub.expr sub e) + | Pexp_newtype (s, e) -> ( + (* Fuse a chain of newtype wrappers over a Function$ node into the + function's [newtypes] field. Each wrapper's attributes are its + newtype's attributes, except on this outermost wrapper: + attributes before the internal [_res.newtype_attrs] marker (or + all of them, when there is no marker) are function-node + attributes, the ones after the marker belong to the first + newtype. Chains over anything else (e.g. the + [let f: type t. ...] sugar) keep their [Pexp_newtype] nodes. *) + let node_attrs, first_nt_attrs = + let rec split acc = function + | ({txt = "_res.newtype_attrs"}, _) :: rest -> (List.rev acc, rest) + | a :: rest -> split (a :: acc) rest + | [] -> (List.rev acc, []) + in + split [] attrs + in + let rec gather acc (e0 : Parsetree0.expression) = + match e0.pexp_desc with + | Pexp_newtype (s1, body) -> + gather + ((map_loc sub s1, sub.attributes sub e0.pexp_attributes) :: acc) + body + | Pexp_construct ({txt = Longident.Lident "Function$"}, Some _) -> + Some (List.rev acc, e0) + | _ -> None + in + match gather [(map_loc sub s, first_nt_attrs)] e with + | Some (newtypes, base) -> ( + let base1 = sub.expr sub base in + match base1.pexp_desc with + | Pexp_fun ({newtypes = []} as f) -> + { + Pt.pexp_desc = Pexp_fun {f with newtypes}; + pexp_attributes = base1.pexp_attributes @ node_attrs; + pexp_loc = loc; + } + | _ -> ( + (* PPX-mangled Function$: keep the wrapper chain as-is. *) + match newtypes with + | [] -> assert false + | (n0, _) :: rest -> + let inner = + List.fold_right + (fun (n, a) acc -> newtype ~loc ~attrs:a n acc) + rest base1 + in + newtype ~loc ~attrs n0 inner)) + | None -> newtype ~loc ~attrs (map_loc sub s) (sub.expr sub e)) | Pexp_pack me -> pack ~loc ~attrs (sub.module_expr sub me) | Pexp_open (ovf, lid, e) -> open_ ~loc ~attrs ovf (map_loc sub lid) (sub.expr sub e) diff --git a/compiler/ml/ast_mapper_to0.ml b/compiler/ml/ast_mapper_to0.ml index 65a7a8ca59..649cae98c6 100644 --- a/compiler/ml/ast_mapper_to0.ml +++ b/compiler/ml/ast_mapper_to0.ml @@ -405,11 +405,12 @@ module E = struct | Pexp_constant x -> constant ~loc ~attrs (map_constant x) | Pexp_let (r, vbs, e) -> let_ ~loc ~attrs r (List.map (sub.value_binding sub) vbs) (sub.expr sub e) - | Pexp_fun {params; body; async} -> + | Pexp_fun {newtypes; params; body; async} -> ( (* Re-curry the n-ary function into the v0 chain of unary funs, and wrap it in Function$ carrying the arity as a res.arity attribute. - The head carries the function node's own attributes (and the - res.async marker), matching what the old parser produced. + Without newtypes the head carries the function node's own + attributes (and the res.async marker), matching what the old parser + produced; with newtypes they travel on the newtype wrapper instead. v0 fun nodes have a single attribute slot for what the current parsetree splits into node attributes and parameter attributes. @@ -433,7 +434,10 @@ module E = struct :: param_attrs in if is_head then - let base = attrs @ marked_param_attrs in + let base = + if newtypes = [] then attrs @ marked_param_attrs + else marked_param_attrs + in if async then ({txt = "res.async"; loc = Location.none}, Pt.PStr []) :: base else base @@ -457,9 +461,41 @@ module E = struct (Pconst_integer (string_of_int arity, None))); ] ) in - Ast_helper0.Exp.construct ~attrs:[arity_attr] - (Location.mkloc (Longident.Lident "Function$") e.pexp_loc) - (Some e) + let fn = + Ast_helper0.Exp.construct ~attrs:[arity_attr] + (Location.mkloc (Longident.Lident "Function$") e.pexp_loc) + (Some e) + in + (* Expand the newtypes back into the v0 wrapper chain around the + Function$ node. Each wrapper carries its own newtype's attributes. + The outermost wrapper is the whole expression in v0, so it also + carries the function node's attributes: when the first newtype has + attributes of its own, an internal [_res.newtype_attrs] marker + separates node attributes (before) from the first newtype's + attributes (after); without a marker every attribute on the + outermost wrapper is a function-node attribute, which is also how + wrapper attributes behaved before newtypes became a field. *) + match newtypes with + | [] -> fn + | (first_name, first_attrs) :: rest_newtypes -> + let inner = + List.fold_right + (fun (name, nt_attrs) acc -> + Ast_helper0.Exp.newtype ~loc + ~attrs:(sub.attributes sub nt_attrs) + (map_loc sub name) acc) + rest_newtypes fn + in + let first_attrs = sub.attributes sub first_attrs in + let outer_attrs = + if first_attrs = [] then attrs + else + attrs + @ ({txt = "_res.newtype_attrs"; loc = Location.none}, Pt.PStr []) + :: first_attrs + in + Ast_helper0.Exp.newtype ~loc ~attrs:outer_attrs (map_loc sub first_name) + inner) | Pexp_apply {funct = e; args; partial} -> let e = match (e.pexp_desc, args) with diff --git a/compiler/ml/parsetree.ml b/compiler/ml/parsetree.ml index 8a24070102..7a41fc196e 100644 --- a/compiler/ml/parsetree.ml +++ b/compiler/ml/parsetree.ml @@ -232,10 +232,18 @@ and expression_desc = (* let P1 = E1 and ... and Pn = EN in E (flag = Nonrecursive) let rec P1 = E1 and ... and Pn = EN in E (flag = Recursive) *) - | Pexp_fun of {params: fun_param list; body: expression; async: bool} - (* (P1, ~l:P2, ?l:P3=E0) => E n-ary uncurried function. + | Pexp_fun of { + newtypes: (string loc * attributes) list; + params: fun_param list; + body: expression; + async: bool; + } + (* (type t, P1, ~l:P2, ?l:P3=E0) => E n-ary uncurried function. The function's arity is [List.length params]; a function returning another function is a nested [Pexp_fun] in [body]. + [newtypes] are the function's locally abstract types, each with its + own attributes; the parser hoists them in front of the value + parameters. Notes: - A default expression is only allowed on Optional parameters. diff --git a/compiler/ml/pprintast.ml b/compiler/ml/pprintast.ml index c98a43ef6e..df2599b1be 100644 --- a/compiler/ml/pprintast.ml +++ b/compiler/ml/pprintast.ml @@ -627,17 +627,23 @@ and expression ctxt f x = | (Pexp_let _ | Pexp_letmodule _ | Pexp_open _ | Pexp_letexception _) when ctxt.semi -> paren true (expression reset_ctxt) f x - | Pexp_fun {params; body; async} -> + | Pexp_fun {newtypes; params; body; async} -> let arity_str = "[arity:" ^ string_of_int (List.length params) ^ "]" in let async_str = if async then "async " else "" in + let rec pp_newtypes f = function + | [] -> () + | ((name : string Location.loc), nt_attrs) :: rest -> + pp f "%a(type %s)@;" (attributes ctxt) nt_attrs name.txt; + pp_newtypes f rest + in let rec pp_params f = function | [] -> () | {p_lbl; p_default; p_pat} :: rest -> pp f "%a" (label_exp ctxt) (p_lbl, p_default, p_pat); pp_params f rest in - pp f "@[<2>%sfun@;%s%a->@;%a@]" async_str arity_str pp_params params - (expression ctxt) body + pp f "@[<2>%sfun@;%a%s%a->@;%a@]" async_str pp_newtypes newtypes arity_str + pp_params params (expression ctxt) body | Pexp_match (e, l) -> pp f "@[@[@[<2>match %a@]@ with@]%a@]" (expression reset_ctxt) e (case_list ctxt) l @@ -1062,9 +1068,15 @@ and binding ctxt f {pvb_pat = p; pvb_expr = x; _} = if x.pexp_attributes <> [] then pp f "=@;%a" (expression ctxt) x else match x.pexp_desc with - | Pexp_fun {params; body; async} -> + | Pexp_fun {newtypes; params; body; async} -> let arity_str = "[arity:" ^ string_of_int (List.length params) ^ "]" in let async_str = if async then "async " else "" in + let rec pp_newtypes f = function + | [] -> () + | ((name : string Location.loc), nt_attrs) :: rest -> + pp f "%a(type@ %s)@ " (attributes ctxt) nt_attrs name.txt; + pp_newtypes f rest + in let pp_param f {p_lbl; p_default; p_pat} = if p_lbl = Nolabel then simple_pattern ctxt f p_pat else label_exp ctxt f (p_lbl, p_default, p_pat) @@ -1075,8 +1087,8 @@ and binding ctxt f {pvb_pat = p; pvb_expr = x; _} = pp f "%a@ " pp_param param; pp_params f rest in - pp f "%s%s%a%a" async_str arity_str pp_params params - pp_print_pexp_function body + pp f "%s%a%s%a%a" async_str pp_newtypes newtypes arity_str pp_params + params pp_print_pexp_function body | Pexp_newtype (str, e) -> pp f "(type@ %s)@ %a" str.txt pp_print_pexp_function e | _ -> pp f "=@;%a" (expression ctxt) x diff --git a/compiler/ml/printast.ml b/compiler/ml/printast.ml index 3d78c88418..a92f386eee 100644 --- a/compiler/ml/printast.ml +++ b/compiler/ml/printast.ml @@ -249,10 +249,15 @@ and expression i ppf x = line i ppf "Pexp_let %a\n" fmt_rec_flag rf; list i value_binding ppf l; expression i ppf e - | Pexp_fun {params; body; async} -> + | Pexp_fun {newtypes; params; body; async} -> line i ppf "Pexp_fun\n"; let () = if async then line i ppf "async\n" in line i ppf "arity:%d\n" (List.length params); + List.iter + (fun ((name : string loc), attrs) -> + attributes i ppf attrs; + line i ppf "newtype \"%s\"\n" name.txt) + newtypes; List.iter (fun {p_attrs; p_lbl; p_default; p_pat} -> attributes i ppf p_attrs; diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 902dc84281..cdc0913255 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -2466,7 +2466,29 @@ and type_expect_ ?deprecated_context ~context ?(recarg = Rejected) env sexp exp_attributes = sexp.pexp_attributes; exp_env = env; } - | Pexp_fun {params; body = sfun_body; async} -> + | Pexp_fun {newtypes = _ :: _ as newtypes; params; body = sfun_body; async} -> + (* Bring the function's locally abstract types into scope, innermost + last, typing the newtype-free function inside all of them - the same + nesting a chain of [Pexp_newtype] wrappers produced. Each group's + attributes open a warning scope over everything within its scope, + as the attributes on the former wrapper nodes did. The function + node's own attributes stay on the inner dispatch only, so its + warning scope is entered once, not once per newtype. *) + let rec peel env = function + | [] -> + (* The newtype-free function, typed directly against a fresh + expectation (the caller's expected type is unified outside the + newtype scopes, below): dispatching through [type_exp] instead + would enter the node's warning scope a second time. *) + type_function ~async loc sexp.pexp_attributes env (newvar ()) params + sfun_body + | (name, nt_attrs) :: rest -> + Builtin_attributes.warning_scope nt_attrs (fun () -> + type_newtype ~loc ~env ~name:name.Asttypes.txt ~attrs:nt_attrs + (fun new_env -> peel new_env rest)) + in + rue (peel env newtypes) + | Pexp_fun {newtypes = []; params; body = sfun_body; async} -> type_function ~async loc sexp.pexp_attributes env ty_expected params sfun_body | Pexp_apply {funct = sfunct; args = sargs; partial; transformed_jsx} -> @@ -3394,61 +3416,9 @@ and type_expect_ ?deprecated_context ~context ?(recarg = Rejected) env sexp exp_env = env; } | Pexp_newtype ({txt = name}, sbody) -> - let ty = newvar () in - (* remember original level *) - begin_def (); - (* Create a fake abstract type declaration for name. *) - let level = get_current_level () in - let decl = - { - type_params = []; - type_arity = 0; - type_kind = Type_abstract; - type_private = Public; - type_manifest = None; - type_variance = []; - type_newtype_level = Some (level, level); - type_loc = loc; - type_attributes = []; - type_immediate = false; - type_unboxed = unboxed_false_default_false; - type_inlined_types = []; - } - in - Ident.set_current_time ty.level; - let id, new_env = Env.enter_type name decl env in - Ctype.init_def (Ident.current_time ()); - - let body = type_exp ~context:None new_env sbody in - (* Replace every instance of this type constructor in the resulting - type. *) - let seen = Hashtbl.create 8 in - let rec replace t = - if Hashtbl.mem seen t.id then () - else ( - Hashtbl.add seen t.id (); - match t.desc with - | Tconstr (Path.Pident id', _, _) when id == id' -> link_type t ty - | _ -> Btype.iter_type_expr replace t) - in - let ety = Subst.type_expr Subst.identity body.exp_type in - replace ety; - (* back to original level *) - end_def (); - - (* lower the levels of the result type *) - (* unify_var env ty ety; *) - - (* non-expansive if the body is non-expansive, so we don't introduce - any new extra node in the typed AST. *) rue - { - body with - exp_loc = loc; - exp_type = ety; - exp_extra = - (Texp_newtype name, loc, sexp.pexp_attributes) :: body.exp_extra; - } + (type_newtype ~loc ~env ~name ~attrs:sexp.pexp_attributes (fun new_env -> + type_exp ~context:None new_env sbody)) | Pexp_pack m -> let p, nl = match Ctype.expand_head env (instance env ty_expected) with @@ -3507,6 +3477,68 @@ and type_expect_ ?deprecated_context ~context ?(recarg = Rejected) env sexp | Pexp_jsx_element _ -> raise (Error (sexp.pexp_loc, Env.empty, Jsx_not_enabled)) +(* Type [type_body] with the locally abstract type [name] in scope: a + fresh abstract type constructor is entered into the environment, and + every occurrence of it in the result type is replaced by a type + variable afterwards. Used both for [Pexp_newtype] nodes and for the + [newtypes] of a function. The result still needs to be unified with + the expected type by the caller. *) +and type_newtype ~loc ~env ~name ~attrs + (type_body : Env.t -> Typedtree.expression) = + let ty = newvar () in + (* remember original level *) + begin_def (); + (* Create a fake abstract type declaration for name. *) + let level = get_current_level () in + let decl = + { + type_params = []; + type_arity = 0; + type_kind = Type_abstract; + type_private = Public; + type_manifest = None; + type_variance = []; + type_newtype_level = Some (level, level); + type_loc = loc; + type_attributes = []; + type_immediate = false; + type_unboxed = unboxed_false_default_false; + type_inlined_types = []; + } + in + Ident.set_current_time ty.level; + let id, new_env = Env.enter_type name decl env in + Ctype.init_def (Ident.current_time ()); + + let body = type_body new_env in + (* Replace every instance of this type constructor in the resulting + type. *) + let seen = Hashtbl.create 8 in + let rec replace t = + if Hashtbl.mem seen t.id then () + else ( + Hashtbl.add seen t.id (); + match t.desc with + | Tconstr (Path.Pident id', _, _) when id == id' -> link_type t ty + | _ -> Btype.iter_type_expr replace t) + in + let ety = Subst.type_expr Subst.identity body.exp_type in + replace ety; + (* back to original level *) + end_def (); + + (* lower the levels of the result type *) + (* unify_var env ty ety; *) + + (* non-expansive if the body is non-expansive, so we don't introduce + any new extra node in the typed AST. *) + { + body with + exp_loc = loc; + exp_type = ety; + exp_extra = (Texp_newtype name, loc, attrs) :: body.exp_extra; + } + and type_function ~async loc attrs env ty_expected_ (sparams : Parsetree.fun_param list) sbody = (* Desugar optional-parameter defaults: the parameter becomes a fresh diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index f678c56cff..a71a03073d 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -241,11 +241,13 @@ let make_props_record_type_sig ~core_type_of_attr ~external_ let rec recursively_transform_named_args_for_make expr args newtypes core_type = match expr.pexp_desc with - | Pexp_fun {params; body} -> + | Pexp_fun {newtypes = fun_newtypes; params; body} -> + (* Collected newtypes are accumulated in reverse source order. *) + let newtypes = List.rev_append fun_newtypes newtypes in transform_params_for_make ~expr ~body params args newtypes core_type | Pexp_newtype (label, expression) -> recursively_transform_named_args_for_make expression args - (label :: newtypes) core_type + ((label, []) :: newtypes) core_type | Pexp_constraint (expression, core_type) -> recursively_transform_named_args_for_make expression args newtypes (Some core_type) @@ -793,12 +795,10 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name binding = | [] -> [] | _ -> [Typ.any ()])))) in - Exp.fun_ ~async:is_async (props_param :: nolabel_params) expression - in - let expression = - (* Add new tupes (type a,b,c) to make's definition *) - newtypes - |> List.fold_left (fun e newtype -> Exp.newtype newtype e) expression + (* Add the collected newtypes (type a b c) to make's definition *) + Exp.fun_ ~async:is_async ~newtypes:(List.rev newtypes) + (props_param :: nolabel_params) + expression in (* let make = ({id, name, ...}: props<'id, 'name, ...>) => { ... } *) let binding = diff --git a/compiler/syntax/src/res_ast_debugger.ml b/compiler/syntax/src/res_ast_debugger.ml index dbc5e70f35..3f2eb9a949 100644 --- a/compiler/syntax/src/res_ast_debugger.ml +++ b/compiler/syntax/src/res_ast_debugger.ml @@ -558,10 +558,16 @@ module Sexp_ast = struct Sexp.list (map_empty ~f:value_binding vbs); expression expr; ] - | Pexp_fun {params; body} -> + | Pexp_fun {newtypes; params; body} -> Sexp.list [ Sexp.atom "Pexp_fun"; + Sexp.list + (map_empty + ~f:(fun ((name : string Location.loc), attrs) -> + Sexp.list + [Sexp.atom "newtype"; string name.txt; attributes attrs]) + newtypes); Sexp.list (map_empty ~f:(fun {p_lbl; p_default; p_pat} -> diff --git a/compiler/syntax/src/res_comments_table.ml b/compiler/syntax/src/res_comments_table.ml index 40a9633515..841640bb01 100644 --- a/compiler/syntax/src/res_comments_table.ml +++ b/compiler/syntax/src/res_comments_table.ml @@ -359,6 +359,21 @@ let functor_type modtype = let fun_expr expr = let open Parsetree in + (* For simplicity reason each newtype gets converted to a Nolabel + * parameter with a fake pattern variable carrying the name's own + * location, otherwise this function would need to return a variant: + * | NormalParamater(...) + * | NewType(...) + * This complicates printing with an extra variant/boxing/allocation for a code-path + * that is not often used. Lets just keep it simple for now *) + let newtype_params newtypes = + (* One fake parameter per newtype, carrying the name's own location: + comments attach to exactly the identifier locations the printer + looks up when printing a "type a b" group. *) + newtypes + |> List.map (fun ((name : string Location.loc), attrs) -> + (attrs, Asttypes.Nolabel, None, Ast_helper.Pat.var ~loc:name.loc name)) + in (* Turns (type t, type u, type z) into "type t u z" *) let rec collect_new_types acc return_expr = match return_expr with @@ -379,17 +394,19 @@ let fun_expr expr = in (Location.mkloc txt loc, return_expr) in - (* For simplicity reason Pexp_newtype gets converted to a Nolabel parameter, - * otherwise this function would need to return a variant: - * | NormalParamater(...) - * | NewType(...) - * This complicates printing with an extra variant/boxing/allocation for a code-path - * that is not often used. Lets just keep it simple for now *) let params_of params = params |> List.map (fun {p_attrs; p_lbl; p_default; p_pat} -> (p_attrs, p_lbl, p_default, p_pat)) in + (* Comments are attached by walking the parameters in source order, so + the newtype groups are interleaved back at their original positions. *) + let in_source_order params = + List.stable_sort + (fun (_, _, _, (p1 : Parsetree.pattern)) (_, _, _, p2) -> + compare p1.ppat_loc.loc_start.pos_cnum p2.ppat_loc.loc_start.pos_cnum) + params + in match expr with | {pexp_desc = Pexp_newtype (string_loc, rest); pexp_attributes = attrs} -> ( let var, return_expr = collect_new_types [string_loc] rest in @@ -397,11 +414,14 @@ let fun_expr expr = (attrs, Asttypes.Nolabel, None, Ast_helper.Pat.var ~loc:string_loc.loc var) in match return_expr with - | {pexp_desc = Pexp_fun {params; body}; pexp_attributes = []} -> - ([], newtype_param :: params_of params, body) + | {pexp_desc = Pexp_fun {newtypes; params; body}; pexp_attributes = []} -> + ( [], + newtype_param + :: in_source_order (newtype_params newtypes @ params_of params), + body ) | return_expr -> ([], [newtype_param], return_expr)) - | {pexp_desc = Pexp_fun {params; body}; pexp_attributes = attrs} -> - (attrs, params_of params, body) + | {pexp_desc = Pexp_fun {newtypes; params; body}; pexp_attributes = attrs} -> + (attrs, in_source_order (newtype_params newtypes @ params_of params), body) | expr -> ([], [], expr) let rec is_block_expr expr = diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml index 708dfd7585..8e99345b19 100644 --- a/compiler/syntax/src/res_core.ml +++ b/compiler/syntax/src/res_core.ml @@ -316,15 +316,12 @@ type typ_def_or_ext = type fundef_type_param = { attrs: Parsetree.attributes; locs: string Location.loc list; - p_pos: Lexing.position; } type fundef_term_param = { - attrs: Parsetree.attributes; p_label: Asttypes.arg_label; expr: Parsetree.expression option; pat: Parsetree.pattern; - p_pos: Lexing.position; } (* Single parameter of a function definition (type a b, x, ~y) *) @@ -339,27 +336,17 @@ type record_pattern_item = type context = OrdinaryExpr | TernaryTrueBranchExpr | WhenExpr -(* Extracts type and term parameters from a list of function definition parameters, combining all type parameters into one *) -let rec extract_fundef_params ~(type_acc : fundef_type_param option) +(* Extracts type and term parameters from a list of function definition + parameters, keeping the type parameter groups in source order *) +let rec extract_fundef_params ~(type_acc : fundef_type_param list) ~(term_acc : fundef_term_param list) (params : fundef_parameter list) : - fundef_type_param option * fundef_term_param list = + fundef_type_param list * fundef_term_param list = match params with | TermParameter tp :: rest -> extract_fundef_params ~type_acc ~term_acc:(tp :: term_acc) rest | TypeParameter tp :: rest -> - let type_acc = - match type_acc with - | Some tpa -> - Some - { - attrs = tpa.attrs @ tp.attrs; - locs = tpa.locs @ tp.locs; - p_pos = tpa.p_pos; - } - | None -> Some tp - in - extract_fundef_params ~type_acc ~term_acc rest - | [] -> (type_acc, List.rev term_acc) + extract_fundef_params ~type_acc:(tp :: type_acc) ~term_acc rest + | [] -> (List.rev type_acc, List.rev term_acc) let get_closing_token = function | Token.Lparen -> Token.Rparen @@ -624,13 +611,10 @@ let lident_of_path longident = | [] -> "" | ident :: _ -> ident -let make_newtypes ~attrs ~loc newtypes exp = - let expr = - List.fold_right - (fun newtype exp -> Ast_helper.Exp.mk ~loc (Pexp_newtype (newtype, exp))) - newtypes exp - in - {expr with pexp_attributes = attrs} +let make_newtypes ~loc newtypes exp = + List.fold_right + (fun newtype exp -> Ast_helper.Exp.mk ~loc (Pexp_newtype (newtype, exp))) + newtypes exp (* locally abstract types syntax sugar * Transforms @@ -640,8 +624,7 @@ let make_newtypes ~attrs ~loc newtypes exp = *) let wrap_type_annotation ~loc newtypes core_type body = let exp = - make_newtypes ~attrs:[] ~loc newtypes - (Ast_helper.Exp.constraint_ ~loc body core_type) + make_newtypes ~loc newtypes (Ast_helper.Exp.constraint_ ~loc body core_type) in let typ = Ast_helper.Typ.poly ~loc newtypes @@ -1841,8 +1824,8 @@ and parse_ternary_expr left_operand p = true_branch (Some false_branch) | _ -> left_operand -and parse_es6_arrow_expression ?(arrow_attrs = []) ?(arrow_start_pos = None) - ?context ?term_parameters ~async p = +and parse_es6_arrow_expression ?(arrow_attrs = []) ?context ?term_parameters + ~async p = let start_pos = p.Parser.start_pos in Parser.leave_breadcrumb p Grammar.Es6ArrowExpr; (* Parsing function parameters and attributes: @@ -1851,35 +1834,9 @@ and parse_es6_arrow_expression ?(arrow_attrs = []) ?(arrow_start_pos = None) labeled, optional or nolabeled. *) let parameters = match term_parameters with - | Some params -> (None, params) + | Some params -> ([], params) | None -> parse_parameters p in - let parameters = - let update_attrs attrs = arrow_attrs @ attrs in - let update_pos pos = - match arrow_start_pos with - | Some start_pos -> start_pos - | None -> pos - in - match parameters with - | None, termp :: rest -> - ( None, - { - termp with - attrs = update_attrs termp.attrs; - p_pos = update_pos termp.p_pos; - } - :: rest ) - | Some (tpa : fundef_type_param), term_params -> - ( Some - { - tpa with - attrs = update_attrs tpa.attrs; - p_pos = update_pos tpa.p_pos; - }, - term_params ) - | _ -> parameters - in let return_type = match p.Parser.token with | Colon -> @@ -1899,11 +1856,9 @@ and parse_es6_arrow_expression ?(arrow_attrs = []) ?(arrow_start_pos = None) in Parser.eat_breadcrumb p; let end_pos = p.prev_end_pos in - let type_param_opt, term_parameters = parameters in + let type_groups, term_parameters = parameters in (* In-parens attributes are already attached to the parameter patterns by - [parse_parameter]; the [attrs] field of a term parameter carries - arrow-level attributes (merged into the first parameter above), which - belong on the function node itself. *) + [parse_parameter]. *) let fun_params = List.map (fun {p_label = lbl; expr = default_expr; pat} -> @@ -1915,22 +1870,24 @@ and parse_es6_arrow_expression ?(arrow_attrs = []) ?(arrow_start_pos = None) }) term_parameters in - let fun_attrs = - List.concat_map (fun (p : fundef_term_param) -> p.attrs) term_parameters - in - let loc = - match term_parameters with - | {p_pos = start_pos} :: _ -> mk_loc start_pos end_pos - | [] -> mk_loc start_pos end_pos - in - let arrow_expr = - Ast_helper.Exp.fun_ ~loc ~attrs:fun_attrs ~async fun_params body + (* Attributes written in front of the arrow belong to the function node + itself, independently of any type parameter groups. *) + let fun_attrs = arrow_attrs in + let loc = mk_loc start_pos end_pos in + let newtypes = + (* Each type parameter group carries its attributes on its first + newtype; the printer starts a new group at each attribute-bearing + newtype, so grouping and attributes round-trip. *) + List.concat_map + (fun {attrs; locs} -> + match locs with + | [] -> [] + | first :: rest -> + (first, attrs) :: List.map (fun name -> (name, [])) rest) + type_groups in let arrow_expr = - match type_param_opt with - | None -> arrow_expr - | Some {attrs; locs = newtypes; p_pos = start_pos} -> - make_newtypes ~attrs ~loc:(mk_loc start_pos end_pos) newtypes arrow_expr + Ast_helper.Exp.fun_ ~loc ~attrs:fun_attrs ~async ~newtypes fun_params body in {arrow_expr with pexp_loc = {arrow_expr.pexp_loc with loc_start = start_pos}} @@ -1967,9 +1924,9 @@ and parse_parameter p = if p.Parser.token = Typ then ( Parser.next p; let lidents = parse_lident_list p in - Some (TypeParameter {attrs; locs = lidents; p_pos = start_pos})) + Some (TypeParameter {attrs; locs = lidents})) else - let attrs, lbl, lbl_loc, pat = + let lbl, lbl_loc, pat = match p.Parser.token with | Tilde -> ( Parser.next p; @@ -1977,8 +1934,7 @@ and parse_parameter p = match p.Parser.token with | Comma | Equal | Rparen -> let loc = mk_loc start_pos p.prev_end_pos in - ( [], - Asttypes.Labelled {txt = lbl_name; loc = lbl_loc}, + ( Asttypes.Labelled {txt = lbl_name; loc = lbl_loc}, lbl_loc, Ast_helper.Pat.var ~attrs ~loc (Location.mkloc lbl_name loc) ) | Colon -> @@ -1991,26 +1947,24 @@ and parse_parameter p = let loc = mk_loc start_pos p.prev_end_pos in Ast_helper.Pat.constraint_ ~attrs ~loc pat typ in - ([], Asttypes.Labelled {txt = lbl_name; loc = lbl_loc}, lbl_loc, pat) + (Asttypes.Labelled {txt = lbl_name; loc = lbl_loc}, lbl_loc, pat) | As -> Parser.next p; let pat = let pat = parse_constrained_pattern p in {pat with ppat_attributes = attrs @ pat.ppat_attributes} in - ([], Asttypes.Labelled {txt = lbl_name; loc = lbl_loc}, lbl_loc, pat) + (Asttypes.Labelled {txt = lbl_name; loc = lbl_loc}, lbl_loc, pat) | t -> Parser.err p (Diagnostics.unexpected t p.breadcrumbs); let loc = mk_loc start_pos p.prev_end_pos in - ( [], - Asttypes.Labelled {txt = lbl_name; loc = lbl_loc}, + ( Asttypes.Labelled {txt = lbl_name; loc = lbl_loc}, lbl_loc, Ast_helper.Pat.var ~attrs ~loc (Location.mkloc lbl_name loc) )) | _ -> let pattern = parse_constrained_pattern p in let attrs = List.concat [pattern.ppat_attributes; attrs] in - ( [], - Asttypes.Nolabel, + ( Asttypes.Nolabel, Location.none, {pattern with ppat_attributes = attrs} ) in @@ -2035,19 +1989,11 @@ and parse_parameter p = match p.Parser.token with | Question -> Parser.next p; - Some - (TermParameter - {attrs; p_label = lbl; expr = None; pat; p_pos = start_pos}) + Some (TermParameter {p_label = lbl; expr = None; pat}) | _ -> let expr = parse_constrained_or_coerced_expr p in - Some - (TermParameter - {attrs; p_label = lbl; expr = Some expr; pat; p_pos = start_pos}) - ) - | _ -> - Some - (TermParameter - {attrs; p_label = lbl; expr = None; pat; p_pos = start_pos})) + Some (TermParameter {p_label = lbl; expr = Some expr; pat})) + | _ -> Some (TermParameter {p_label = lbl; expr = None; pat})) else None and parse_parameter_list p = @@ -2056,7 +2002,7 @@ and parse_parameter_list p = ~f:parse_parameter ~closing:Rparen p in Parser.expect Rparen p; - extract_fundef_params ~type_acc:None ~term_acc:[] parameters + extract_fundef_params ~type_acc:[] ~term_acc:[] parameters (* parameters ::= * | _ @@ -2065,7 +2011,7 @@ and parse_parameter_list p = * | (.) (* deprecated uncurried syntax *) * | ( parameter {, parameter} [,] ) *) -and parse_parameters p : fundef_type_param option * fundef_term_param list = +and parse_parameters p : fundef_type_param list * fundef_term_param list = let start_pos = p.Parser.start_pos in let unit_term_parameter () = let loc = mk_loc start_pos p.Parser.prev_end_pos in @@ -2074,39 +2020,29 @@ and parse_parameters p : fundef_type_param option * fundef_term_param list = (Location.mkloc (Longident.Lident "()") loc) None in - { - attrs = []; - p_label = Asttypes.Nolabel; - expr = None; - pat = unit_pattern; - p_pos = start_pos; - } + {p_label = Asttypes.Nolabel; expr = None; pat = unit_pattern} in match p.Parser.token with | Lident ident -> Parser.next p; let loc = mk_loc start_pos p.Parser.prev_end_pos in - ( None, + ( [], [ { - attrs = []; p_label = Asttypes.Nolabel; expr = None; pat = Ast_helper.Pat.var ~loc (Location.mkloc ident loc); - p_pos = start_pos; }; ] ) | Underscore -> Parser.next p; let loc = mk_loc start_pos p.Parser.prev_end_pos in - ( None, + ( [], [ { - attrs = []; p_label = Asttypes.Nolabel; expr = None; pat = Ast_helper.Pat.any ~loc (); - p_pos = start_pos; }; ] ) | Lparen -> @@ -2122,7 +2058,7 @@ and parse_parameters p : fundef_type_param option * fundef_term_param list = (type_params, term_params) | token -> Parser.err p (Diagnostics.unexpected token p.breadcrumbs); - (None, []) + ([], []) and parse_coerced_expr ~(expr : Parsetree.expression) p = Parser.expect ColonGreaterThan p; @@ -3412,11 +3348,9 @@ and parse_braced_or_record_expr p = ~term_parameters: [ { - attrs = []; p_label = Nolabel; expr = None; pat = Ast_helper.Pat.var ~loc:ident.loc ident; - p_pos = start_pos; }; ] p @@ -3778,10 +3712,8 @@ and parse_expr_block ?first p = over_parse_constrained_or_coerced_or_arrow_expression p block_expr and parse_async_arrow_expression ?(arrow_attrs = []) p = - let start_pos = p.Parser.start_pos in Parser.expect (Lident "async") p; - parse_es6_arrow_expression ~async:true ~arrow_attrs - ~arrow_start_pos:(Some start_pos) p + parse_es6_arrow_expression ~async:true ~arrow_attrs p and parse_await_expression p = let await_loc = mk_loc p.Parser.start_pos p.end_pos in diff --git a/compiler/syntax/src/res_parsetree_viewer.ml b/compiler/syntax/src/res_parsetree_viewer.ml index 5b00112fba..fe63d5b988 100644 --- a/compiler/syntax/src/res_parsetree_viewer.ml +++ b/compiler/syntax/src/res_parsetree_viewer.ml @@ -185,6 +185,19 @@ type fun_param_kind = } | NewTypes of {attrs: Parsetree.attributes; locs: string Asttypes.loc list} +(* Turns the function's newtypes into printable groups: a new group starts + at each attribute-bearing newtype, matching how the parser distributes + group attributes. *) +let group_newtypes newtypes = + List.fold_left + (fun groups ((name : string Asttypes.loc), attrs) -> + match groups with + | (gattrs, locs) :: rest when attrs = [] -> + (gattrs, locs @ [name]) :: rest + | _ -> (attrs, [name]) :: groups) + [] newtypes + |> List.rev + let fun_expr expr_ = let params_of_fun params = List.map @@ -193,6 +206,10 @@ let fun_expr expr_ = {attrs = p_attrs; lbl = p_lbl; default_expr = p_default; pat = p_pat}) params in + let newtype_params newtypes = + group_newtypes newtypes + |> List.map (fun (attrs, locs) -> NewTypes {attrs; locs}) + in (* Turns (type t, type u, type z) into "type t u z". An attribute on a nested node (only constructible via PPX) stops the merge so the attribute is printed on the node carrying it instead of dropped. *) @@ -205,14 +222,21 @@ let fun_expr expr_ = in match expr_ with | {pexp_desc = Pexp_newtype (string_loc, rest)} -> ( + (* PPX-authored wrapper chains; the parser puts a function's newtypes + in the [newtypes] field instead. *) let string_locs, return_expr = collect_new_types [string_loc] rest in let newtype_param = NewTypes {attrs = []; locs = string_locs} in match return_expr with - | {pexp_desc = Pexp_fun {params; body; async}; pexp_attributes = []} -> - (async, newtype_param :: params_of_fun params, body) + | { + pexp_desc = Pexp_fun {newtypes; params; body; async}; + pexp_attributes = []; + } -> + ( async, + (newtype_param :: newtype_params newtypes) @ params_of_fun params, + body ) | _ -> (false, [newtype_param], return_expr)) - | {pexp_desc = Pexp_fun {params; body; async}} -> - (async, params_of_fun params, body) + | {pexp_desc = Pexp_fun {newtypes; params; body; async}} -> + (async, newtype_params newtypes @ params_of_fun params, body) | _ -> (false, [], expr_) let process_braces_attr expr = diff --git a/compiler/syntax/src/res_parsetree_viewer.mli b/compiler/syntax/src/res_parsetree_viewer.mli index 10797277be..e546abfa07 100644 --- a/compiler/syntax/src/res_parsetree_viewer.mli +++ b/compiler/syntax/src/res_parsetree_viewer.mli @@ -51,6 +51,12 @@ type fun_param_kind = } | NewTypes of {attrs: Parsetree.attributes; locs: string Asttypes.loc list} +(* Groups a function's newtypes into printable groups: a new group starts + at each attribute-bearing newtype. *) +val group_newtypes : + (string Asttypes.loc * Parsetree.attributes) list -> + (Parsetree.attributes * string Asttypes.loc list) list + val fun_expr : Parsetree.expression -> bool * fun_param_kind list * Parsetree.expression diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index 612b4adc38..0ed2a1724b 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -2355,7 +2355,8 @@ and print_value_binding ~state ~rec_flag (vb : Parsetree.value_binding) cmt_tbl ppat_desc = Ppat_constraint (pattern, ({ptyp_desc = Ptyp_poly _} as pat_typ)); }; - pvb_expr = {pexp_desc = Pexp_newtype _} as expr; + pvb_expr = + {pexp_desc = Pexp_newtype _ | Pexp_fun {newtypes = _ :: _}} as expr; } -> ( let _, parameters, return_expr = Parsetree_viewer.fun_expr expr in let abstract_type = diff --git a/tests/build_tests/super_errors/expected/newtype_fun_ppwarning.res.expected b/tests/build_tests/super_errors/expected/newtype_fun_ppwarning.res.expected new file mode 100644 index 0000000000..efc7101f05 --- /dev/null +++ b/tests/build_tests/super_errors/expected/newtype_fun_ppwarning.res.expected @@ -0,0 +1,17 @@ + + Warning number 22 + /.../fixtures/newtype_fun_ppwarning.res:1:20-33 + + 1 │ let f = @ppwarning("emitted-once") (type a b, x: a) => x + 2 │ + + emitted-once + + + Warning number 34 + /.../fixtures/newtype_fun_ppwarning.res:1:36-56 + + 1 │ let f = @ppwarning("emitted-once") (type a b, x: a) => x + 2 │ + + unused type b. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/newtype_group_warning_scope.res.expected b/tests/build_tests/super_errors/expected/newtype_group_warning_scope.res.expected new file mode 100644 index 0000000000..c6bdf128bc --- /dev/null +++ b/tests/build_tests/super_errors/expected/newtype_group_warning_scope.res.expected @@ -0,0 +1,16 @@ + + Warning number 26 + /.../fixtures/newtype_group_warning_scope.res:6:7-12 + + 4 │ } + 5 │ let unsuppressed = (type a, x: a) => { + 6 │ let unused = 1 + 7 │ x + 8 │ } + + unused variable unused. + +Fix this by: +- Deleting the variable if it's not used anymore. +- Prepending the variable name with `_` (like `_unused`) to ignore that the variable is unused. +- Using the variable somewhere. \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/newtype_fun_ppwarning.res b/tests/build_tests/super_errors/fixtures/newtype_fun_ppwarning.res new file mode 100644 index 0000000000..2ed5b52a8a --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/newtype_fun_ppwarning.res @@ -0,0 +1 @@ +let f = @ppwarning("emitted-once") (type a b, x: a) => x diff --git a/tests/build_tests/super_errors/fixtures/newtype_group_warning_scope.res b/tests/build_tests/super_errors/fixtures/newtype_group_warning_scope.res new file mode 100644 index 0000000000..8aaa40872e --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/newtype_group_warning_scope.res @@ -0,0 +1,8 @@ +let suppressed = (@warning("-26") type a, x: a) => { + let unused = 1 + x +} +let unsuppressed = (type a, x: a) => { + let unused = 1 + x +} diff --git a/tests/syntax_tests/data/ast-mapping/FunctionsAndArrows.res b/tests/syntax_tests/data/ast-mapping/FunctionsAndArrows.res index 782392229d..25936d6042 100644 --- a/tests/syntax_tests/data/ast-mapping/FunctionsAndArrows.res +++ b/tests/syntax_tests/data/ast-mapping/FunctionsAndArrows.res @@ -48,3 +48,6 @@ external phantom: (~a: int, @as(json`false`) _, ~c: string) => unit = "phantom" // external with uncurried callback argument @val external onEvent: (string, (~event: string) => unit) => unit = "on" + +// attributed newtype groups: attribute ownership must survive the v0 bridge +let grouped = @fn (@one type a b, x: a, @two type c, y: c) => (x, y) diff --git a/tests/syntax_tests/data/ast-mapping/expected/FunctionsAndArrows.res.txt b/tests/syntax_tests/data/ast-mapping/expected/FunctionsAndArrows.res.txt index 782392229d..7605b4147e 100644 --- a/tests/syntax_tests/data/ast-mapping/expected/FunctionsAndArrows.res.txt +++ b/tests/syntax_tests/data/ast-mapping/expected/FunctionsAndArrows.res.txt @@ -48,3 +48,6 @@ external phantom: (~a: int, @as(json`false`) _, ~c: string) => unit = "phantom" // external with uncurried callback argument @val external onEvent: (string, (~event: string) => unit) => unit = "on" + +// attributed newtype groups: attribute ownership must survive the v0 bridge +let grouped = @fn (@one type a b, @two type c, x: a, y: c) => (x, y) diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/async.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/async.res.txt index 0c04ab839d..f726e44a17 100644 --- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/async.res.txt +++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/async.res.txt @@ -28,17 +28,17 @@ let ex2 = (await 3) ** (await 4) let ex3 = await (foo -> (bar ~arg)) let ex4 = await ((foo.bar).baz) let attr1 = ((async fun [arity:1]x -> x + 1)[@a ]) -let attr2 = ((fun (type a) -> - async fun [arity:1]() -> fun (type b) -> fun (type c) -> - fun [arity:1]x -> 3) +let attr2 = + ((async fun (type a) [arity:1]() -> fun (type b) (type c) [arity:1]x -> 3) [@a ]) -let attr3 = ((fun (type a) -> - fun [arity:1]() -> fun (type b) -> fun (type c) -> - async fun [arity:1]x -> 3) +let attr3 = + ((fun (type a) [arity:1]() -> async fun (type b) (type c) [arity:1]x -> 3) [@a ]) -let attr4 = ((fun (type a) -> - fun [arity:1]() -> ((fun (type b) -> fun (type c) -> - async fun [arity:1]x -> 3)[@b ])) +let attr4 = + ((fun (type a) [arity:1]() -> + ((async fun (type b) (type c) [arity:1]x -> 3)[@b ])) [@a ]) -let (attr5 : int) = ((fun (type a) -> fun (type b) -> fun (type c) -> - async fun [arity:1]() -> fun [arity:1](x : a) -> x)[@a ][@b ]) \ No newline at end of file +let (attr5 : int) = + ((async fun (type a) (type b) (type c) [arity:1]() -> + fun [arity:1](x : a) -> x) + [@a ][@b ]) \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/locallyAbstractTypes.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/locallyAbstractTypes.res.txt index 24693d89e3..a8966415fc 100644 --- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/locallyAbstractTypes.res.txt +++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/locallyAbstractTypes.res.txt @@ -3,16 +3,14 @@ let f (type t) (type s) [arity:2](xs : t list) (ys : s list) = () let f (type t) (type u) (type v) [arity:1](xs : (t * u * v) list) = () let f (type t) (type u) (type v) (type s) (type w) (type z) [arity:2](xs : (t * u * v) list) (ys : (s * w * z) list) = () -let f = ((fun (type t) -> fun (type u) -> fun (type v) -> fun (type s) -> fun - (type w) -> fun (type z) -> - fun [arity:2](xs : (t * u * v) list) (ys : (s * w * z) list) -> ()) - [@attr ][@attr2 ]) -let f = ((fun (type t) -> fun (type s) -> fun (type u) -> fun (type v) -> fun - (type w) -> fun [arity:2](xs : (t * s) list) (ys : (u * v * w) list) -> ()) - [@attr ][@attr ][@attr ][@attr ]) +let f = + ((fun (type t) (type u) (type v) [@attr2 ](type s) (type w) (type z) + [arity:2](xs : (t * u * v) list) (ys : (s * w * z) list) -> ()) + [@attr ]) +let f [@attr ](type t) [@attr ](type s) [@attr ](type u) [@attr ](type v) + (type w) [arity:2](xs : (t * s) list) (ys : (u * v * w) list) = () let cancel_and_collect_callbacks : 'a 'u 'c . packed_callbacks list -> ('a, 'u, 'c) promise -> packed_callbacks list (a:2) - = fun (type x) -> - fun [arity:2]callbacks_accumulator (p : (_, _, c) promise) -> () \ No newline at end of file + = fun (type x) [arity:2]callbacks_accumulator (p : (_, _, c) promise) -> () \ No newline at end of file diff --git a/tests/syntax_tests/data/printer/comments/expected/expr.res.txt b/tests/syntax_tests/data/printer/comments/expected/expr.res.txt index 0b346bfe3c..9196ebdd69 100644 --- a/tests/syntax_tests/data/printer/comments/expected/expr.res.txt +++ b/tests/syntax_tests/data/printer/comments/expected/expr.res.txt @@ -237,9 +237,9 @@ let f = ( let multiply = (type /* c-2 */ t /* c-1 */, /* c0 */ m1 /* c1 */, /* c2 */ m2 /* c3 */) => () let multiply = ( - type /* c-4 */ t /* c-3 */ s, + type /* c-4 */ t /* c-3 */ /* c-2 */ s /* c-1 */, /* c0 */ m1 /* c1 */, - /* c-2 */ /* c-1 */ /* c2 */ m2 /* c3 */, + /* c2 */ m2 /* c3 */, ) => () f( diff --git a/tests/syntax_tests/data/printer/expr/expected/newtype.res.txt b/tests/syntax_tests/data/printer/expr/expected/newtype.res.txt index e97ff990e0..e8480471a6 100644 --- a/tests/syntax_tests/data/printer/expr/expected/newtype.res.txt +++ b/tests/syntax_tests/data/printer/expr/expected/newtype.res.txt @@ -1,12 +1,19 @@ let f = (type t, xs: list) => () let f = @attr (type t, xs: list) => () let f = (type t s, xs: list, ys: list) => () -let f = @attr @attr2 (type t s, xs: list, ys: list) => () +let f = @attr (type t, @attr2 type s, xs: list, ys: list) => () let f = (type t u v, xs: list<(t, u, v)>) => () let f = @attr (type t u v, xs: list<(t, u, v)>) => () let f = (type t u v s w z, xs: list<(t, u, v)>, ys: list<(s, w, z)>) => () -let f = @attr @attr2 (type t u v s w z, xs: list<(t, u, v)>, ys: list<(s, w, z)>) => () -let f = @attr @attr @attr @attr (type t s u v w, xs: list<(t, s)>, ys: list<(u, v, w)>) => () +let f = @attr (type t u v, @attr2 type s w z, xs: list<(t, u, v)>, ys: list<(s, w, z)>) => () +let f = ( + @attr type t, + @attr type s, + @attr type u, + @attr type v w, + xs: list<(t, s)>, + ys: list<(u, v, w)>, +) => () let mk_formatting_gen: type a b c d e f. formatting_gen => Parsetree.expression = @@ -18,3 +25,8 @@ let mk_formatting_gen: let cancel_and_collect_callbacks: 'a 'u 'c. (list, promise<'a, 'u, 'c>) => list = (type x, callbacks_accumulator, p: promise<_, _, c>) => () + +// type parameters written between term parameters are hoisted to the front +let g = (type t, x, y: t) => y +let g = (@attr type t u v, x: int, y: t, z: v) => (y, z) +let g = (type /* c1 */ t /* c2 */, /* before */ x, y: t) => y diff --git a/tests/syntax_tests/data/printer/expr/newtype.res b/tests/syntax_tests/data/printer/expr/newtype.res index c8e30bd68e..a5b6b62eba 100644 --- a/tests/syntax_tests/data/printer/expr/newtype.res +++ b/tests/syntax_tests/data/printer/expr/newtype.res @@ -20,3 +20,8 @@ let cancel_and_collect_callbacks: (list, promise<'a, 'u, 'c>) => list = (type x, callbacks_accumulator, p: promise<_, _, c>) => (); + +// type parameters written between term parameters are hoisted to the front +let g = (x, type t, y: t) => y +let g = (x: int, @attr type t u, y: t, type v, z: v) => (y, z) +let g = (/* before */ x, type /* c1 */ t /* c2 */, y: t) => y diff --git a/tools/src/transforms.ml b/tools/src/transforms.ml index 901cc9c215..da3e07f94a 100644 --- a/tools/src/transforms.ml +++ b/tools/src/transforms.ml @@ -3,12 +3,13 @@ let labelled_to_unlabelled_arguments_in_fn_definition (e : Parsetree.expression) (* `(~a, ~b, ~c) => ...` to `(a, b, c) => ...` *) let rec drop_labels (e : Parsetree.expression) : Parsetree.expression = match e.pexp_desc with - | Pexp_fun {params; body; async} -> + | Pexp_fun {newtypes; params; body; async} -> { e with pexp_desc = Pexp_fun { + newtypes; params = List.map (fun (p : Parsetree.fun_param) -> {p with p_lbl = Nolabel})