Skip to content

Commit 4ab9a5f

Browse files
authored
Type spreads of regular variants in patterns (#6721)
* poc of type spreads of regular variants in patterns * cleanup * fix subtype error messages and add fixtures * changelog * refactor to handle type params (not supported) * refactor * add failing test * accidental reformat * snake_case * wip attempt * change approach to expanding variant spread in the parsetree instead of typedtree * cleanup unneeded changes * disable unused subpattern warning when constructors is from variant pattern spread * update error * cleanup * change to use Ppat_type as transfer mechanism for variant spreads instead of Ppat_var * formatting * add example with payloads
1 parent ece0fb9 commit 4ab9a5f

20 files changed

+363
-7
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#### :rocket: New Feature
1616

1717
- Use FORCE_COLOR environmental variable to force colorized output https://github.com/rescript-lang/rescript-compiler/pull/7033
18+
- Allow spreads of variants in patterns (`| ...someVariant as v => `) when the variant spread is a subtype of the variant matched on. https://github.com/rescript-lang/rescript-compiler/pull/6721
1819

1920
#### :bug: Bug fix
2021

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/variant_pattern_type_spreads_not_subtype.res:7:8
4+
5+
5 │ let lookup = (b: b) =>
6+
6 │ switch b {
7+
7 │ | ...c as c => Js.log(c)
8+
8 │ | Four => Js.log("four")
9+
9 │ | Five => Js.log("five")
10+
11+
Type c is not a subtype of b
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/variant_pattern_type_spreads_not_variant.res:7:8
4+
5+
5 │ let lookup = (b: b) =>
6+
6 │ switch b {
7+
7 │ | ...c as c => Js.log(c)
8+
8 │ | Four => Js.log("four")
9+
9 │ | Five => Js.log("five")
10+
11+
The type c
12+
is not a variant type
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
type a = One | Two | Three
2+
type b = | ...a | Four | Five
3+
type c = Six | Seven
4+
5+
let lookup = (b: b) =>
6+
switch b {
7+
| ...c as c => Js.log(c)
8+
| Four => Js.log("four")
9+
| Five => Js.log("five")
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
type a = One | Two | Three
2+
type b = | ...a | Four | Five
3+
type c = {name: string}
4+
5+
let lookup = (b: b) =>
6+
switch b {
7+
| ...c as c => Js.log(c)
8+
| Four => Js.log("four")
9+
| Five => Js.log("five")
10+
}

jscomp/ml/parmatch.ml

+4-2
Original file line numberDiff line numberDiff line change
@@ -2259,11 +2259,13 @@ let check_unused pred casel =
22592259
Location.prerr_warning
22602260
q.pat_loc Warnings.Unused_match
22612261
| Upartial ps ->
2262-
List.iter
2262+
ps
2263+
|> List.filter (fun p ->
2264+
not (Variant_type_spread.is_pat_from_variant_spread_attr p))
2265+
|> List.iter
22632266
(fun p ->
22642267
Location.prerr_warning
22652268
p.pat_loc Warnings.Unused_pat)
2266-
ps
22672269
| Used -> ()
22682270
with Empty | Not_found | NoGuard -> assert false
22692271
end ;

jscomp/ml/typecore.ml

+72-1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ type error =
7474
| Empty_record_literal
7575
| Uncurried_arity_mismatch of type_expr * int * int
7676
| Field_not_optional of string * type_expr
77+
| Type_params_not_supported of Longident.t
7778
exception Error of Location.t * Env.t * error
7879
exception Error_forward of Location.error
7980

@@ -595,6 +596,61 @@ let build_or_pat env loc lid =
595596
pat pats in
596597
(path, rp { r with pat_loc = loc },ty)
597598

599+
let extract_type_from_pat_variant_spread env lid expected_ty =
600+
let path, decl = Typetexp.find_type env lid.loc lid.txt in
601+
match decl with
602+
| {type_kind = Type_variant constructors; type_params} -> (
603+
if List.length type_params > 0 then raise (Error (lid.loc, env, Type_params_not_supported lid.txt));
604+
let ty = newgenty (Tconstr (path, [], ref Mnil)) in
605+
(try
606+
Ctype.subtype env ty expected_ty ()
607+
with
608+
Ctype.Subtype (tr1, tr2) ->
609+
raise(Error(lid.loc, env, Not_subtype(tr1, tr2)))
610+
);
611+
(path, decl, constructors, ty))
612+
| _ -> raise (Error (lid.loc, env, Not_a_variant_type lid.txt))
613+
614+
let build_ppat_or_for_variant_spread pat env expected_ty =
615+
match pat with
616+
| {ppat_desc = Ppat_type lident; ppat_attributes}
617+
when Variant_coercion.has_res_pat_variant_spread_attribute ppat_attributes
618+
->
619+
let _, _, constructors, ty =
620+
extract_type_from_pat_variant_spread !env lident expected_ty
621+
in
622+
let synthetic_or_patterns =
623+
constructors
624+
|> List.map (fun (c : Types.constructor_declaration) ->
625+
Ast_helper.Pat.mk ~attrs:[Variant_type_spread.mk_pat_from_variant_spread_attr ()] ~loc:lident.loc
626+
(Ppat_construct
627+
( Location.mkloc
628+
(Longident.Lident (Ident.name c.cd_id))
629+
lident.loc,
630+
match c.cd_args with
631+
| Cstr_tuple [] -> None
632+
| _ -> Some (Ast_helper.Pat.any ()) )))
633+
|> List.rev
634+
in
635+
let pat =
636+
match synthetic_or_patterns with
637+
| [] -> pat
638+
| pat :: pats ->
639+
List.fold_left (fun p1 p2 -> Ast_helper.Pat.or_ p1 p2) pat pats
640+
in
641+
Some (pat, ty)
642+
| _ -> None
643+
644+
let maybe_expand_variant_spread_in_pattern pattern env expected_ty =
645+
match pattern.Parsetree.ppat_desc with
646+
| Ppat_type _
647+
when Variant_coercion.has_res_pat_variant_spread_attribute
648+
pattern.ppat_attributes -> (
649+
match build_ppat_or_for_variant_spread pattern env expected_ty with
650+
| None -> assert false (* TODO: Fix. *)
651+
| Some (pattern, _) -> pattern)
652+
| _ -> pattern
653+
598654
(* Type paths *)
599655

600656
let rec expand_path env p =
@@ -1051,6 +1107,7 @@ let rec type_pat ~constrs ~labels ~no_existentials ~mode ~explode ~env
10511107

10521108
and type_pat_aux ~constrs ~labels ~no_existentials ~mode ~explode ~env
10531109
sp expected_ty k =
1110+
let sp = maybe_expand_variant_spread_in_pattern sp env expected_ty in
10541111
let mode' = if mode = Splitting_or then Normal else mode in
10551112
let type_pat ?(constrs=constrs) ?(labels=labels) ?(mode=mode')
10561113
?(explode=explode) ?(env=env) =
@@ -1125,10 +1182,22 @@ and type_pat_aux ~constrs ~labels ~no_existentials ~mode ~explode ~env
11251182
| _ -> assert false
11261183
end
11271184
| Ppat_alias(sq, name) ->
1185+
let override_type_from_variant_spread, sq =
1186+
match sq with
1187+
| {ppat_desc = Ppat_type _; ppat_attributes}
1188+
when Variant_coercion.has_res_pat_variant_spread_attribute ppat_attributes
1189+
-> (
1190+
match build_ppat_or_for_variant_spread sq env expected_ty with
1191+
| Some (p, ty) -> (Some ty, p)
1192+
| None -> (None, sq))
1193+
| _ -> (None, sq)
1194+
in
11281195
assert (constrs = None);
11291196
type_pat sq expected_ty (fun q ->
11301197
begin_def ();
1131-
let ty_var = build_as_type !env q in
1198+
let ty_var = (match override_type_from_variant_spread with
1199+
| Some ty -> ty
1200+
| None -> build_as_type !env q) in
11321201
end_def ();
11331202
generalize ty_var;
11341203
let id = enter_variable ~is_as_variable:true loc name ty_var in
@@ -4040,6 +4109,8 @@ let report_error env ppf = function
40404109
fprintf ppf
40414110
"Field @{<info>%s@} is not optional in type %a. Use without ?" name
40424111
type_expr typ
4112+
| Type_params_not_supported lid ->
4113+
fprintf ppf "The type %a@ has type parameters, but type parameters is not supported here." longident lid
40434114
40444115
40454116
let super_report_error_no_wrap_printing_env = report_error

jscomp/ml/typecore.mli

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ type error =
107107
| Empty_record_literal
108108
| Uncurried_arity_mismatch of type_expr * int * int
109109
| Field_not_optional of string * type_expr
110+
| Type_params_not_supported of Longident.t
110111
exception Error of Location.t * Env.t * error
111112
exception Error_forward of Location.error
112113

jscomp/ml/variant_coercion.ml

+7-1
Original file line numberDiff line numberDiff line change
@@ -202,4 +202,10 @@ let can_coerce_polyvariant_to_variant ~row_fields ~variant_constructors ~type_at
202202
let type_is_variant (typ: (Path.t * Path.t * Types.type_declaration) option) =
203203
match typ with
204204
| Some (_, _, {type_kind = Type_variant _; _}) -> true
205-
| _ -> false
205+
| _ -> false
206+
207+
let has_res_pat_variant_spread_attribute attrs =
208+
attrs
209+
|> List.find_opt (fun (({txt}, _) : Parsetree.attribute) ->
210+
txt = "res.patVariantSpread")
211+
|> Option.is_some

jscomp/ml/variant_type_spread.ml

+10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
let mk_constructor_comes_from_spread_attr () : Parsetree.attribute =
22
(Location.mknoloc "res.constructor_from_spread", PStr [])
33

4+
let mk_pat_from_variant_spread_attr () : Parsetree.attribute =
5+
(Location.mknoloc "res.patFromVariantSpread", PStr [])
6+
7+
let is_pat_from_variant_spread_attr pat =
8+
pat.Typedtree.pat_attributes
9+
|> List.exists (fun (a : Parsetree.attribute) ->
10+
match a with
11+
| {txt = "res.patFromVariantSpread"}, PStr [] -> true
12+
| _ -> false)
13+
414
type variant_type_spread_error =
515
| CouldNotFindType
616
| HasTypeParams

jscomp/syntax/src/res_core.ml

+9
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ let suppress_fragile_match_warning_attr =
175175
] )
176176
let make_braces_attr loc = (Location.mkloc "res.braces" loc, Parsetree.PStr [])
177177
let template_literal_attr = (Location.mknoloc "res.template", Parsetree.PStr [])
178+
let make_pat_variant_spread_attr =
179+
(Location.mknoloc "res.patVariantSpread", Parsetree.PStr [])
178180

179181
let tagged_template_literal_attr =
180182
(Location.mknoloc "res.taggedTemplate", Parsetree.PStr [])
@@ -1077,6 +1079,13 @@ let rec parse_pattern ?(alias = true) ?(or_ = true) p =
10771079
match p.Parser.token with
10781080
| Lparen -> parse_constructor_pattern_args p constr start_pos attrs
10791081
| _ -> Ast_helper.Pat.construct ~loc:constr.loc ~attrs constr None)
1082+
| DotDotDot ->
1083+
Parser.next p;
1084+
let ident = parse_value_path p in
1085+
let loc = mk_loc start_pos ident.loc.loc_end in
1086+
Ast_helper.Pat.type_ ~loc
1087+
~attrs:(make_pat_variant_spread_attr :: attrs)
1088+
ident
10801089
| Hash -> (
10811090
Parser.next p;
10821091
if p.Parser.token == DotDotDot then (

jscomp/syntax/src/res_parsetree_viewer.ml

+9-1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ let has_await_attribute attrs =
9797
| _ -> false)
9898
attrs
9999

100+
let has_res_pat_variant_spread_attribute attrs =
101+
List.exists
102+
(function
103+
| {Location.txt = "res.patVariantSpread"}, _ -> true
104+
| _ -> false)
105+
attrs
106+
100107
let collect_array_expressions expr =
101108
match expr.pexp_desc with
102109
| Pexp_array exprs -> (exprs, None)
@@ -219,7 +226,8 @@ let filter_parsing_attrs attrs =
219226
Location.txt =
220227
( "res.arity" | "res.braces" | "ns.braces" | "res.iflet"
221228
| "res.namedArgLoc" | "res.optional" | "res.ternary" | "res.async"
222-
| "res.await" | "res.template" | "res.taggedTemplate" );
229+
| "res.await" | "res.template" | "res.taggedTemplate"
230+
| "res.patVariantSpread" );
223231
},
224232
_ ) ->
225233
false

jscomp/syntax/src/res_parsetree_viewer.mli

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ val process_function_attributes :
2626
Parsetree.attributes -> function_attributes_info
2727

2828
val has_await_attribute : Parsetree.attributes -> bool
29+
val has_res_pat_variant_spread_attribute : Parsetree.attributes -> bool
2930

3031
type if_condition_kind =
3132
| If of Parsetree.expression

jscomp/syntax/src/res_printer.ml

+4
Original file line numberDiff line numberDiff line change
@@ -2404,6 +2404,10 @@ and print_pattern ~state (p : Parsetree.pattern) cmt_tbl =
24042404
]
24052405
in
24062406
Doc.group (Doc.concat [variant_name; args_doc])
2407+
| Ppat_type ident
2408+
when ParsetreeViewer.has_res_pat_variant_spread_attribute
2409+
p.ppat_attributes ->
2410+
Doc.concat [Doc.text "..."; print_ident_path ident cmt_tbl]
24072411
| Ppat_type ident ->
24082412
Doc.concat [Doc.text "#..."; print_ident_path ident cmt_tbl]
24092413
| Ppat_record (rows, open_flag) ->

jscomp/syntax/tests/parsing/grammar/expressions/expected/coerce.res.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ let foo = ((Function$ (fun x -> ((x : t) :> int)))[@res.arity 1])
33
let _ = (x : int)
44
let foo = ((x : int), (y :> float))
55
let foo = ((x : int), (y :> float), (z :> int))
6-
let foo = ((x : int), y, (z :> int))
6+
let foo = ((x : int), y, (z :> int))

jscomp/test/VariantPatternMatchingSpreads.js

+58
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
type a = One | Two | Three
2+
type b = | ...a | Four | Five
3+
type c = Six | Seven
4+
type d = | ...b | ...c
5+
6+
let doWithA = (a: a) => {
7+
switch a {
8+
| One => Js.log("aaa")
9+
| Two => Js.log("twwwoooo")
10+
| Three => Js.log("threeeee")
11+
}
12+
}
13+
14+
let doWithB = (b: b) => {
15+
switch b {
16+
| One => Js.log("aaa")
17+
| _ => Js.log("twwwoooo")
18+
}
19+
}
20+
21+
let lookup = (b: b) =>
22+
switch b {
23+
| ...a as a => doWithA(a)
24+
| Four => Js.log("four")
25+
| Five => Js.log("five")
26+
}
27+
28+
let lookup2 = (d: d) =>
29+
switch d {
30+
| ...a as a => doWithA(a)
31+
| ...b as b => doWithB(b)
32+
| Six | Seven => Js.log("Got rest of d")
33+
}

0 commit comments

Comments
 (0)