From 0c03e7bc005051f8d1310f8b8f0ea43cd211d7f3 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Thu, 8 Oct 2026 17:50:55 +0000 Subject: [PATCH 1/2] Represent variant spread patterns with a Ppat_variant_spread node A variant spread pattern ...t was Ppat_type, the node of the polymorphic variant pattern #...t, with a res.patVariantSpread attribute telling the two apart. It now has its own node; the v0 bridge keeps the old encoding for PPXs. The type checker expands it as before, and it no longer counts as a polymorphic variant pattern. The printer no longer needs to filter the attribute. Signed-off-by: Christoph Knittel Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + analysis/src/completion_front_end.ml | 2 +- analysis/src/semantic_tokens.ml | 2 +- analysis/src/utils.ml | 1 + compiler/ext/config.ml | 4 +-- compiler/ml/ast_helper.ml | 1 + compiler/ml/ast_helper.mli | 1 + compiler/ml/ast_iterator.ml | 2 +- compiler/ml/ast_mapper.ml | 1 + compiler/ml/ast_mapper_from0.ml | 16 ++++++++- compiler/ml/ast_mapper_to0.ml | 5 +++ compiler/ml/depend.ml | 2 +- compiler/ml/parsetree.ml | 3 +- compiler/ml/pprintast.ml | 1 + compiler/ml/printast.ml | 3 ++ compiler/ml/typecore.ml | 18 ++++------ compiler/ml/variant_coercion.ml | 6 ---- compiler/syntax/src/res_ast_debugger.ml | 5 +++ compiler/syntax/src/res_comments_table.ml | 2 +- compiler/syntax/src/res_core.ml | 6 +--- compiler/syntax/src/res_parsetree_viewer.ml | 14 +------- compiler/syntax/src/res_parsetree_viewer.mli | 1 - compiler/syntax/src/res_printer.ml | 4 +-- tests/ounit_tests/ounit_ast_mapper0_tests.ml | 36 +++++++++++++++++++ .../ast-mapping/VariantSpreadPatterns.res | 10 ++++++ .../expected/VariantSpreadPatterns.res.txt | 10 ++++++ .../pattern/expected/variantSpreads.res.txt | 10 +++--- 27 files changed, 114 insertions(+), 53 deletions(-) create mode 100644 tests/syntax_tests/data/ast-mapping/VariantSpreadPatterns.res create mode 100644 tests/syntax_tests/data/ast-mapping/expected/VariantSpreadPatterns.res.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 7991c353904..0e66a867b54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ #### :house: Internal - Remove the unused `res.iflet` encoding of `if let`, which the parser always rejects with a syntax error suggesting a `switch`. The error is unchanged. https://github.com/rescript-lang/rescript/pull/8741 +- Represent variant spread patterns (`...t`) with a dedicated `Ppat_variant_spread` parsetree node instead of `Ppat_type` with a `res.patVariantSpread` attribute, which remains only in the PPX wire representation. PR_LINK - Remove the obsolete `res.ternary` and `JSX` attributes from the formatter's internal attribute filters and merge the filters into one. https://github.com/rescript-lang/rescript/pull/8740 - Represent `await` on module expressions with a dedicated `Pmod_await` parsetree node instead of a `res.await` attribute, which remains only in the PPX wire representation. https://github.com/rescript-lang/rescript/pull/8738 - Remove dead compiler code: warning 24, the `J.String_index` JS IR node, the `Pisint` and `Parraysets` Lambda primitives with their unused `%` primitive names (`%obj_is_int`, `%obj_dup`, `%obj_field`, `%array_safe_set`, `%null_to_opt`, `%nullable_to_opt`), the no-op `-pp` bsc flag and unread `Clflags` fields (`-unsafe` stays accepted as an explicit no-op), and the unused `unit` slot of `Pexp_coerce`. https://github.com/rescript-lang/rescript/pull/8731 diff --git a/analysis/src/completion_front_end.ml b/analysis/src/completion_front_end.ml index 2dbdb34b818..add09af21a6 100644 --- a/analysis/src/completion_front_end.ml +++ b/analysis/src/completion_front_end.ml @@ -537,7 +537,7 @@ let completion_with_parser1 ~debug ~offset ~pos_cursor ~kind_file scope_pattern ~pattern_path ?context_path:(Type_utils.context_path_from_core_type core_type) p - | Ppat_type _ -> () + | Ppat_type _ | Ppat_variant_spread _ -> () | Ppat_unpack {txt; loc} -> scope := !scope |> Scope.add_module ~name:txt ~loc | Ppat_exception p -> scope_pattern ~pattern_path ?context_path p diff --git a/analysis/src/semantic_tokens.ml b/analysis/src/semantic_tokens.ml index 4b315cfde10..0be8854d713 100644 --- a/analysis/src/semantic_tokens.ml +++ b/analysis/src/semantic_tokens.ml @@ -244,7 +244,7 @@ let command ~debug ~emitter ~source ~kind_file = | Ppat_construct (name, _) -> emitter |> emit_variant ~name ~debug; Ast_iterator.default_iterator.pat iterator p - | Ppat_type {txt = lid; loc} -> + | Ppat_type {txt = lid; loc} | Ppat_variant_spread {txt = lid; loc} -> emitter |> emit_type ~lid ~debug ~loc; Ast_iterator.default_iterator.pat iterator p | _ -> Ast_iterator.default_iterator.pat iterator p diff --git a/analysis/src/utils.ml b/analysis/src/utils.ml index 5e8298f6a74..34df53e0044 100644 --- a/analysis/src/utils.ml +++ b/analysis/src/utils.ml @@ -139,6 +139,7 @@ let identify_ppat pat = | Ppat_or _ -> "Ppat_or" | Ppat_constraint _ -> "Ppat_constraint" | Ppat_type _ -> "Ppat_type" + | Ppat_variant_spread _ -> "Ppat_variant_spread" | Ppat_unpack _ -> "Ppat_unpack" | Ppat_exception _ -> "Ppat_exception" | Ppat_extension _ -> "Ppat_extension" diff --git a/compiler/ext/config.ml b/compiler/ext/config.ml index 1d6ca61af8c..b7dede6436d 100644 --- a/compiler/ext/config.ml +++ b/compiler/ext/config.ml @@ -2,9 +2,9 @@ let cmi_magic_number = "Caml1999I035" (* Magic numbers for marshaled values of the *current* parsetree, whose layout changes across compiler versions. *) -and ast_impl_magic_number = "ResImpl01312" +and ast_impl_magic_number = "ResImpl01313" -and ast_intf_magic_number = "ResIntf01312" +and ast_intf_magic_number = "ResIntf01313" (* Magic numbers of the frozen Parsetree0 (OCaml 4.06) layout used on the external-PPX wire. They must never be written in front of a diff --git a/compiler/ml/ast_helper.ml b/compiler/ml/ast_helper.ml index 94ed0320ba1..bd0bf8b2bae 100644 --- a/compiler/ml/ast_helper.ml +++ b/compiler/ml/ast_helper.ml @@ -142,6 +142,7 @@ module Pat = struct let or_ ?loc ?attrs a b = mk ?loc ?attrs (Ppat_or (a, b)) let constraint_ ?loc ?attrs a b = mk ?loc ?attrs (Ppat_constraint (a, b)) let type_ ?loc ?attrs a = mk ?loc ?attrs (Ppat_type a) + let variant_spread ?loc ?attrs a = mk ?loc ?attrs (Ppat_variant_spread a) let unpack ?loc ?attrs a = mk ?loc ?attrs (Ppat_unpack a) let exception_ ?loc ?attrs a = mk ?loc ?attrs (Ppat_exception a) let extension ?loc ?attrs a = mk ?loc ?attrs (Ppat_extension a) diff --git a/compiler/ml/ast_helper.mli b/compiler/ml/ast_helper.mli index 701c1ed9a53..04657603f2d 100644 --- a/compiler/ml/ast_helper.mli +++ b/compiler/ml/ast_helper.mli @@ -107,6 +107,7 @@ module Pat : sig val or_ : ?loc:loc -> ?attrs:attrs -> pattern -> pattern -> pattern val constraint_ : ?loc:loc -> ?attrs:attrs -> pattern -> core_type -> pattern val type_ : ?loc:loc -> ?attrs:attrs -> lid -> pattern + val variant_spread : ?loc:loc -> ?attrs:attrs -> lid -> pattern val unpack : ?loc:loc -> ?attrs:attrs -> str -> pattern val exception_ : ?loc:loc -> ?attrs:attrs -> pattern -> pattern val extension : ?loc:loc -> ?attrs:attrs -> extension -> pattern diff --git a/compiler/ml/ast_iterator.ml b/compiler/ml/ast_iterator.ml index a267c8ed7c7..1459f19cf18 100644 --- a/compiler/ml/ast_iterator.ml +++ b/compiler/ml/ast_iterator.ml @@ -464,7 +464,7 @@ module P = struct | Ppat_constraint (p, t) -> sub.pat sub p; sub.typ sub t - | Ppat_type s -> iter_loc sub s + | Ppat_type s | Ppat_variant_spread s -> iter_loc sub s | Ppat_unpack s -> iter_loc sub s | Ppat_exception p -> sub.pat sub p | Ppat_extension x -> sub.extension sub x diff --git a/compiler/ml/ast_mapper.ml b/compiler/ml/ast_mapper.ml index 69a6a0152f4..a93a2e66de8 100644 --- a/compiler/ml/ast_mapper.ml +++ b/compiler/ml/ast_mapper.ml @@ -464,6 +464,7 @@ module P = struct | Ppat_constraint (p, t) -> constraint_ ~loc ~attrs (sub.pat sub p) (sub.typ sub t) | Ppat_type s -> type_ ~loc ~attrs (map_loc sub s) + | Ppat_variant_spread s -> variant_spread ~loc ~attrs (map_loc sub s) | Ppat_unpack s -> unpack ~loc ~attrs (map_loc sub s) | Ppat_exception p -> exception_ ~loc ~attrs (sub.pat sub p) | Ppat_extension x -> extension ~loc ~attrs (sub.extension sub x) diff --git a/compiler/ml/ast_mapper_from0.ml b/compiler/ml/ast_mapper_from0.ml index 0cd2e427bf8..38a8bed744a 100644 --- a/compiler/ml/ast_mapper_from0.ml +++ b/compiler/ml/ast_mapper_from0.ml @@ -44,6 +44,17 @@ let extract_internal_loc_attr attr_name attrs = let await_marker_loc ~node_loc (marker_loc : Location.t) = if marker_loc = Location.none then node_loc else marker_loc +(* The [res.patVariantSpread] marker that [Ast_mapper_to0] puts on a variant + spread pattern, with any payload *) +let extract_variant_spread_attr (attrs : Pt.attributes) = + let rec loop rev_acc = function + | [] -> (false, List.rev rev_acc) + | ({txt = "res.patVariantSpread"}, _) :: rest -> + (true, List.rev_append rev_acc rest) + | attr :: rest -> loop (attr :: rev_acc) rest + in + loop [] attrs + let extract_ternary_attr (attrs : Pt.attributes) = let rec loop rev_acc = function | [] -> (false, List.rev rev_acc) @@ -1327,7 +1338,10 @@ module P = struct | Ppat_or (p1, p2) -> or_ ~loc ~attrs (sub.pat sub p1) (sub.pat sub p2) | Ppat_constraint (p, t) -> constraint_ ~loc ~attrs (sub.pat sub p) (sub.typ sub t) - | Ppat_type s -> type_ ~loc ~attrs (map_loc sub s) + | Ppat_type s -> ( + match extract_variant_spread_attr attrs with + | true, attrs -> variant_spread ~loc ~attrs (map_loc sub s) + | false, _ -> type_ ~loc ~attrs (map_loc sub s)) | Ppat_lazy _ -> failwith "Ppat_lazy is no longer present in ReScript" | Ppat_unpack s -> unpack ~loc ~attrs (map_loc sub s) | Ppat_open _ -> failwith "Ppat_open is no longer present in ReScript" diff --git a/compiler/ml/ast_mapper_to0.ml b/compiler/ml/ast_mapper_to0.ml index 23329eaaf55..8c93dba9e06 100644 --- a/compiler/ml/ast_mapper_to0.ml +++ b/compiler/ml/ast_mapper_to0.ml @@ -907,6 +907,11 @@ module P = struct | Ppat_constraint (p, t) -> constraint_ ~loc ~attrs (sub.pat sub p) (sub.typ sub t) | Ppat_type s -> type_ ~loc ~attrs (map_loc sub s) + | Ppat_variant_spread s -> + (* v0 has no variant spread pattern: it's [#...t] with a marker *) + type_ ~loc + ~attrs:((Location.mknoloc "res.patVariantSpread", Pt.PStr []) :: attrs) + (map_loc sub s) | Ppat_unpack s -> unpack ~loc ~attrs (map_loc sub s) | Ppat_exception p -> exception_ ~loc ~attrs (sub.pat sub p) | Ppat_extension x -> extension ~loc ~attrs (sub.extension sub x) diff --git a/compiler/ml/depend.ml b/compiler/ml/depend.ml index 562165c0c9f..ad3c0761785 100644 --- a/compiler/ml/depend.ml +++ b/compiler/ml/depend.ml @@ -189,7 +189,7 @@ let rec add_pattern bv pat = add_pattern bv p; add_type bv ty | Ppat_variant (_, {txt = args}) -> List.iter (add_pattern bv) args - | Ppat_type li -> add bv li + | Ppat_type li | Ppat_variant_spread li -> add bv li | Ppat_unpack id -> pattern_bv := String_map.add id.txt bound !pattern_bv | Ppat_exception p -> add_pattern bv p | Ppat_extension e -> handle_extension e diff --git a/compiler/ml/parsetree.ml b/compiler/ml/parsetree.ml index 4c1c5bc8cd4..0297655b8e2 100644 --- a/compiler/ml/parsetree.ml +++ b/compiler/ml/parsetree.ml @@ -249,7 +249,8 @@ and pattern_desc = | Ppat_array of pattern list (* [| P1; ...; Pn |] *) | Ppat_or of pattern * pattern (* P1 | P2 *) | Ppat_constraint of pattern * core_type (* (P : T) *) - | Ppat_type of Longident.t loc (* #tconst *) + | Ppat_type of Longident.t loc (* #...tconst *) + | Ppat_variant_spread of Longident.t loc (* ...tconst *) | Ppat_unpack of string loc (* (module P) Note: (module P : S) is represented as diff --git a/compiler/ml/pprintast.ml b/compiler/ml/pprintast.ml index 7851338ce91..35351aa46f1 100644 --- a/compiler/ml/pprintast.ml +++ b/compiler/ml/pprintast.ml @@ -486,6 +486,7 @@ and simple_pattern ctxt (f : Format.formatter) (x : pattern) : unit = | Ppat_array l -> pp f "@[<2>[|%a|]@]" (list (pattern1 ctxt) ~sep:";") l | Ppat_unpack s -> pp f "(module@ %s)@ " s.txt | Ppat_type li -> pp f "#%a" longident_loc li + | Ppat_variant_spread li -> pp f "...%a" longident_loc li | Ppat_record (l, closed, rest) -> ( let longident_x_pattern f {lid = li; x = p; opt} = let opt_str = if opt then "?" else "" in diff --git a/compiler/ml/printast.ml b/compiler/ml/printast.ml index 7460d90f60c..e14a6dcabb6 100644 --- a/compiler/ml/printast.ml +++ b/compiler/ml/printast.ml @@ -231,6 +231,9 @@ and pattern i ppf x = | Ppat_type li -> line i ppf "Ppat_type\n"; longident_loc i ppf li + | Ppat_variant_spread li -> + line i ppf "Ppat_variant_spread\n"; + longident_loc i ppf li | Ppat_unpack s -> line i ppf "Ppat_unpack %a\n" fmt_string_loc s | Ppat_exception p -> line i ppf "Ppat_exception\n"; diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 7f4ae21a8a1..acf4e816c3c 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -657,9 +657,7 @@ let extract_type_from_pat_variant_spread env lid expected_ty = let build_ppat_or_for_variant_spread pat env expected_ty = match pat with - | {ppat_desc = Ppat_type lident; ppat_attributes} - when Variant_coercion.has_res_pat_variant_spread_attribute ppat_attributes - -> + | {ppat_desc = Ppat_variant_spread lident} -> let _, _, constructors, ty = extract_type_from_pat_variant_spread !env lident expected_ty in @@ -693,9 +691,7 @@ let build_ppat_or_for_variant_spread pat env expected_ty = let maybe_expand_variant_spread_in_pattern pattern env expected_ty = match pattern.Parsetree.ppat_desc with - | Ppat_type _ - when Variant_coercion.has_res_pat_variant_spread_attribute - pattern.ppat_attributes -> ( + | Ppat_variant_spread _ -> ( match build_ppat_or_for_variant_spread pattern env expected_ty with | None -> assert false (* TODO: Fix. *) | Some (pattern, _) -> pattern) @@ -1331,9 +1327,7 @@ and type_pat_aux ~constrs ~labels ~no_existentials ~mode ~explode ~env sp | Ppat_alias (sq, name) -> let override_type_from_variant_spread, sq = match sq with - | {ppat_desc = Ppat_type _; ppat_attributes} - when Variant_coercion.has_res_pat_variant_spread_attribute - ppat_attributes -> ( + | {ppat_desc = Ppat_variant_spread _} -> ( match build_ppat_or_for_variant_spread sq env expected_ty with | Some (p, ty) -> (Some ty, p) | None -> (None, sq)) @@ -1739,7 +1733,9 @@ and type_pat_aux ~constrs ~labels ~no_existentials ~mode ~explode ~env sp | _ -> {p with pat_type = ty; pat_extra = extra :: p.pat_extra} in k p) - | Ppat_type lid -> + (* [Ppat_variant_spread] is expanded above, except for a variant without + constructors, which is typed (and rejected) like [#...t] *) + | Ppat_type lid | Ppat_variant_spread lid -> let path, p, ty = build_or_pat !env loc lid in unify_pat_types loc !env ty expected_ty; k @@ -2173,7 +2169,7 @@ let contains_variant_either ty = let iter_ppat f p = match p.ppat_desc with | Ppat_any | Ppat_var _ | Ppat_constant _ | Ppat_interval _ | Ppat_extension _ - | Ppat_type _ | Ppat_unpack _ -> + | Ppat_type _ | Ppat_variant_spread _ | Ppat_unpack _ -> () | Ppat_array pats -> List.iter f pats | Ppat_or (p1, p2) -> diff --git a/compiler/ml/variant_coercion.ml b/compiler/ml/variant_coercion.ml index 59c2052ad2e..0bd95ddbfd0 100644 --- a/compiler/ml/variant_coercion.ml +++ b/compiler/ml/variant_coercion.ml @@ -277,9 +277,3 @@ let type_is_variant (typ : (Path.t * Path.t * Types.type_declaration) option) = match typ with | Some (_, _, {type_kind = Type_variant _; _}) -> true | _ -> false - -let has_res_pat_variant_spread_attribute attrs = - attrs - |> List.find_opt (fun (({txt}, _) : Parsetree.attribute) -> - txt = "res.patVariantSpread") - |> Option.is_some diff --git a/compiler/syntax/src/res_ast_debugger.ml b/compiler/syntax/src/res_ast_debugger.ml index e3b51cdf368..dc5bc8851ae 100644 --- a/compiler/syntax/src/res_ast_debugger.ml +++ b/compiler/syntax/src/res_ast_debugger.ml @@ -917,6 +917,11 @@ module Sexp_ast = struct Sexp.list [Sexp.atom "Ppat_constraint"; pattern p; core_type typexpr] | Ppat_type longident_loc -> Sexp.list [Sexp.atom "Ppat_type"; longident longident_loc.Location.txt] + | Ppat_variant_spread longident_loc -> + Sexp.list + [ + Sexp.atom "Ppat_variant_spread"; longident longident_loc.Location.txt; + ] | Ppat_unpack string_loc -> Sexp.list [Sexp.atom "Ppat_unpack"; string string_loc.Location.txt] | Ppat_exception p -> Sexp.list [Sexp.atom "Ppat_exception"; pattern p] diff --git a/compiler/syntax/src/res_comments_table.ml b/compiler/syntax/src/res_comments_table.ml index 1d34e711610..8aeef1ff32e 100644 --- a/compiler/syntax/src/res_comments_table.ml +++ b/compiler/syntax/src/res_comments_table.ml @@ -2064,7 +2064,7 @@ and walk_pattern pat t comments = walk_list (List.map (fun pat -> Pattern pat) pats) t rest | Ppat_variant (_label, {txt = args}) -> walk_list (List.map (fun pat -> Pattern pat) args) t comments - | Ppat_type _ -> () + | Ppat_type _ | Ppat_variant_spread _ -> () | Ppat_record (record_rows, _, rest) -> let nodes = Ext_list.map record_rows (fun {lid; x = p} -> PatternRecordRow (lid, p)) diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml index 4082ceca33e..799cafd0be7 100644 --- a/compiler/syntax/src/res_core.ml +++ b/compiler/syntax/src/res_core.ml @@ -264,8 +264,6 @@ module Error_messages = struct "Spreading JSX children is no longer supported." end -let make_pat_variant_spread_attr = - (Location.mknoloc "res.patVariantSpread", Parsetree.PStr []) let spread_attr = (Location.mknoloc "res.spread", Parsetree.PStr []) let dict_spread_attr = (Location.mknoloc "res.dictSpread", Parsetree.PStr []) @@ -1257,9 +1255,7 @@ let rec parse_pattern ?(alias = true) ?(or_ = true) p = Parser.next p; let ident = parse_value_path p in let loc = mk_loc start_pos ident.loc.loc_end in - Ast_helper.Pat.type_ ~loc - ~attrs:(make_pat_variant_spread_attr :: attrs) - ident + Ast_helper.Pat.variant_spread ~loc ~attrs ident | Hash -> ( Parser.next p; if Parser.peek p == DotDotDot then ( diff --git a/compiler/syntax/src/res_parsetree_viewer.ml b/compiler/syntax/src/res_parsetree_viewer.ml index c8a9387f109..12f22310fbd 100644 --- a/compiler/syntax/src/res_parsetree_viewer.ml +++ b/compiler/syntax/src/res_parsetree_viewer.ml @@ -30,13 +30,6 @@ let expr_is_await e = | Pexp_await _ -> true | _ -> false -let has_res_pat_variant_spread_attribute attrs = - List.exists - (function - | {Location.txt = "res.patVariantSpread"}, _ -> true - | _ -> false) - attrs - let has_dict_pattern_attribute attrs = attrs |> List.find_opt (fun (({txt}, _) : Parsetree.attribute) -> @@ -221,12 +214,7 @@ let rec unwrap_braces expr = (* Attributes the parser adds to encode syntax; they are never printed *) let is_parsing_attr (attr : Parsetree.attribute) = match attr with - | ( { - Location.txt = - "res.patVariantSpread" | "res.dictPattern" | "res.dictSpread"; - }, - _ ) -> - true + | {Location.txt = "res.dictPattern" | "res.dictSpread"}, _ -> true | _ -> false let filter_parsing_attrs attrs = diff --git a/compiler/syntax/src/res_parsetree_viewer.mli b/compiler/syntax/src/res_parsetree_viewer.mli index b771688c181..9f4e521776e 100644 --- a/compiler/syntax/src/res_parsetree_viewer.mli +++ b/compiler/syntax/src/res_parsetree_viewer.mli @@ -16,7 +16,6 @@ val functor_type : * Parsetree.module_type val expr_is_await : Parsetree.expression -> bool -val has_res_pat_variant_spread_attribute : Parsetree.attributes -> bool val has_dict_pattern_attribute : Parsetree.attributes -> bool type dict_expr_part = diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index c652ea29e05..12d84247be0 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -2827,9 +2827,7 @@ and print_pattern ~state (p : Parsetree.pattern) cmt_tbl = in let args_doc = print_pattern_args ~state variant_args cmt_tbl in Doc.group (Doc.concat [variant_name; args_doc]) - | Ppat_type ident - when Parsetree_viewer.has_res_pat_variant_spread_attribute - p.ppat_attributes -> + | Ppat_variant_spread ident -> Doc.concat [Doc.text "..."; print_ident_path ident cmt_tbl] | Ppat_type ident -> Doc.concat [Doc.text "#..."; print_ident_path ident cmt_tbl] diff --git a/tests/ounit_tests/ounit_ast_mapper0_tests.ml b/tests/ounit_tests/ounit_ast_mapper0_tests.ml index be328ccf5b0..28746474ae1 100644 --- a/tests/ounit_tests/ounit_ast_mapper0_tests.ml +++ b/tests/ounit_tests/ounit_ast_mapper0_tests.ml @@ -682,6 +682,40 @@ let map_pat_to0 p = let attr_names attrs = List.map (fun ({Location.txt}, _) -> txt) attrs +(* v0 has no variant spread pattern: [...t] is [#...t] with a + [res.patVariantSpread] marker in front of its own attributes, as the + parser used to produce it *) +let test_variant_spread_pattern_through_ast0 _ = + let spread_loc = source_loc 2 8 in + let lid = located_string ~loc:(source_loc 5 8) (Longident.Lident "t") in + let spread = + Ast_helper.Pat.variant_spread ~loc:spread_loc + ~attrs:[attr "user" (Parsetree.PStr [])] + lid + in + let wire = map_pat_to0 spread in + (match wire.ppat_desc with + | Parsetree0.Ppat_type {txt = Longident.Lident "t"} -> () + | _ -> assert_failure "Expected Ppat_type on the v0 wire"); + OUnit.assert_equal ~printer:(String.concat ", ") + ["res.patVariantSpread"; "user"] + (attr_names wire.ppat_attributes); + (match map_pat0 wire with + | { + ppat_desc = Ppat_variant_spread {txt = Longident.Lident "t"; loc}; + ppat_loc; + ppat_attributes; + } -> + OUnit.assert_equal ~msg:"pattern location" spread_loc ppat_loc; + OUnit.assert_equal ~msg:"type location" lid.loc loc; + OUnit.assert_equal ["user"] (attr_names ppat_attributes) + | _ -> assert_failure "Expected Ppat_variant_spread after the ast0 roundtrip"); + (* [#...t] stays a polymorphic variant type pattern *) + let poly = Ast_helper.Pat.type_ ~loc lid in + match map_pat0 (map_pat_to0 poly) with + | {ppat_desc = Ppat_type _; ppat_attributes = []} -> () + | _ -> assert_failure "Expected Ppat_type without attributes" + let test_attributed_constructor_payloads_through_ast0 _ = let payload_loc = source_loc 10 30 in let payload_attrs = [attr "ppx.payload" (Parsetree0.PStr [])] in @@ -1934,6 +1968,8 @@ let suites = >:: test_nested_awaits_are_dynamic_imports; "module_await_v0_attribute_order" >:: test_module_await_v0_attribute_order; + "variant_spread_pattern_through_ast0" + >:: test_variant_spread_pattern_through_ast0; "inline_record_definition_roundtrips_through_ast0" >:: test_inline_record_definition_roundtrips_through_ast0; "this_on_braced_function_reaches_builtin_ppx" diff --git a/tests/syntax_tests/data/ast-mapping/VariantSpreadPatterns.res b/tests/syntax_tests/data/ast-mapping/VariantSpreadPatterns.res new file mode 100644 index 00000000000..9f42ea8b8ab --- /dev/null +++ b/tests/syntax_tests/data/ast-mapping/VariantSpreadPatterns.res @@ -0,0 +1,10 @@ +let f = x => + switch x { + | ...a => 1 + | ...a as v => v + | @attr ...a => 2 + | Some(...a) => 3 + | ...M.a | ...b => 4 + | #...p => 5 + | @attr #...p => 6 + } diff --git a/tests/syntax_tests/data/ast-mapping/expected/VariantSpreadPatterns.res.txt b/tests/syntax_tests/data/ast-mapping/expected/VariantSpreadPatterns.res.txt new file mode 100644 index 00000000000..9f42ea8b8ab --- /dev/null +++ b/tests/syntax_tests/data/ast-mapping/expected/VariantSpreadPatterns.res.txt @@ -0,0 +1,10 @@ +let f = x => + switch x { + | ...a => 1 + | ...a as v => v + | @attr ...a => 2 + | Some(...a) => 3 + | ...M.a | ...b => 4 + | #...p => 5 + | @attr #...p => 6 + } diff --git a/tests/syntax_tests/data/parsing/grammar/pattern/expected/variantSpreads.res.txt b/tests/syntax_tests/data/parsing/grammar/pattern/expected/variantSpreads.res.txt index 945eafbc2b0..ecc8d54cc28 100644 --- a/tests/syntax_tests/data/parsing/grammar/pattern/expected/variantSpreads.res.txt +++ b/tests/syntax_tests/data/parsing/grammar/pattern/expected/variantSpreads.res.txt @@ -23,17 +23,17 @@ let doWithB [arity:1](b : b) = | _ -> Console.log {js|twwwoooo|js}} let lookup [arity:1](b : b) = match b with - | ((#a)[@res.patVariantSpread ]) as a -> doWithA a + | ...a as a -> doWithA a | Four -> Console.log {js|four|js} | Five -> Console.log {js|five|js} let lookup2 [arity:1](d : d) = match d with - | ((#a)[@res.patVariantSpread ]) as a -> doWithA a - | ((#b)[@res.patVariantSpread ]) as b -> doWithB b + | ...a as a -> doWithA a + | ...b as b -> doWithB b | Six|Seven -> Console.log {js|Got rest of d|js} let lookupOpt [arity:1](b : b option) = match b with - | Some (((#a)[@res.patVariantSpread ]) as a) -> doWithA a + | Some (...a as a) -> doWithA a | Some (Four) -> Console.log {js|four|js} | Some (Five) -> Console.log {js|five|js} | None -> Console.log {js|None|js} @@ -52,5 +52,5 @@ let doWithZ [arity:1](z : Foo.zz) = | Second -> Console.log {js|Second|js} let lookup3 [arity:1](d : Foo.xx) = match d with - | ((#Foo.zz)[@res.patVariantSpread ]) as z -> Console.log z + | ...Foo.zz as z -> Console.log z | Third -> Console.log {js|Third|js} \ No newline at end of file From 30d64b5abe4dd4dc6fa69bd253570df1f2d6cd52 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Thu, 8 Oct 2026 17:51:21 +0000 Subject: [PATCH 2/2] Add CHANGELOG link for #8742 Signed-off-by: Christoph Knittel Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e66a867b54..d3bacc2ab6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,7 +48,7 @@ #### :house: Internal - Remove the unused `res.iflet` encoding of `if let`, which the parser always rejects with a syntax error suggesting a `switch`. The error is unchanged. https://github.com/rescript-lang/rescript/pull/8741 -- Represent variant spread patterns (`...t`) with a dedicated `Ppat_variant_spread` parsetree node instead of `Ppat_type` with a `res.patVariantSpread` attribute, which remains only in the PPX wire representation. PR_LINK +- Represent variant spread patterns (`...t`) with a dedicated `Ppat_variant_spread` parsetree node instead of `Ppat_type` with a `res.patVariantSpread` attribute, which remains only in the PPX wire representation. https://github.com/rescript-lang/rescript/pull/8742 - Remove the obsolete `res.ternary` and `JSX` attributes from the formatter's internal attribute filters and merge the filters into one. https://github.com/rescript-lang/rescript/pull/8740 - Represent `await` on module expressions with a dedicated `Pmod_await` parsetree node instead of a `res.await` attribute, which remains only in the PPX wire representation. https://github.com/rescript-lang/rescript/pull/8738 - Remove dead compiler code: warning 24, the `J.String_index` JS IR node, the `Pisint` and `Parraysets` Lambda primitives with their unused `%` primitive names (`%obj_is_int`, `%obj_dup`, `%obj_field`, `%array_safe_set`, `%null_to_opt`, `%nullable_to_opt`), the no-op `-pp` bsc flag and unread `Clflags` fields (`-unsafe` stays accepted as an explicit no-op), and the unused `unit` slot of `Pexp_coerce`. https://github.com/rescript-lang/rescript/pull/8731