Skip to content

PoC: Type spreads of regular variants in patterns #6720

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

We've found a bug for you!
/.../fixtures/variant_pattern_type_spreads_not_subtype.res:7:5-13

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")

Type c is not a subtype of b
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

We've found a bug for you!
/.../fixtures/variant_pattern_type_spreads_not_variant.res:7:8

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")

The type c
is not a variant type
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type a = One | Two | Three
type b = | ...a | Four | Five
type c = Six | Seven

let lookup = (b: b) =>
switch b {
| ...c as c => Js.log(c)
| Four => Js.log("four")
| Five => Js.log("five")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type a = One | Two | Three
type b = | ...a | Four | Five
type c = {name: string}

let lookup = (b: b) =>
switch b {
| ...c as c => Js.log(c)
| Four => Js.log("four")
| Five => Js.log("five")
}
83 changes: 83 additions & 0 deletions jscomp/ml/typecore.ml
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,62 @@ let build_or_pat env loc lid =
pat pats in
(path, rp { r with pat_loc = loc },ty)

let build_or_pat_for_variant_spread env loc lid expected_ty =
let path, decl = Typetexp.find_type env lid.loc lid.txt in
match decl with
| {type_kind = Type_variant constructors} -> (
(* TODO: Probably problematic that we don't account for type params here? *)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole fn would be good to get your eyes on @cristianoc . It's mostly copied and adapted to work on regular variants from the existing build_or_pat, that operates on polyvariants.

let ty = newty (Tconstr (path, [], ref Mnil)) in
let gloc = {loc with Location.loc_ghost = true} in
let pats =
constructors
|> List.map
(fun (c : Types.constructor_declaration) : Typedtree.pattern ->
let lid = Longident.Lident (Ident.name c.cd_id) in
{
pat_desc =
Tpat_construct
( {loc = Location.none; txt = lid},
Env.lookup_constructor ~loc:c.cd_loc lid env,
match c.cd_args with
| Cstr_tuple [] -> []
| _ ->
[
{
pat_desc = Tpat_any;
pat_loc = Location.none;
pat_env = env;
pat_type = expected_ty;
pat_extra = [];
pat_attributes = [];
};
] );
pat_loc = Location.none;
pat_extra = [];
pat_type = expected_ty;
pat_env = env;
pat_attributes = [];
})
in
match pats with
| [] -> raise (Error (lid.loc, env, Not_a_variant_type lid.txt))
| pat :: pats ->
let r =
List.fold_left
(fun pat pat0 ->
{
Typedtree.pat_desc = Tpat_or (pat0, pat, None);
pat_extra = [];
pat_loc = gloc;
pat_env = env;
pat_type = expected_ty;
pat_attributes = [];
})
pat pats
in
(path, rp {r with pat_loc = loc}, ty))
| _ -> raise (Error (lid.loc, env, Not_a_variant_type lid.txt))

(* Type paths *)

let rec expand_path env p =
Expand Down Expand Up @@ -1111,6 +1167,23 @@ and type_pat_aux ~constrs ~labels ~no_existentials ~mode ~explode ~env
}
| _ -> assert false
end
| Ppat_alias({ppat_desc=Ppat_type lid; ppat_attributes}, name) when Variant_coercion.has_res_pat_variant_spread_attribute ppat_attributes ->
let (_, p, ty) = build_or_pat_for_variant_spread !env loc lid expected_ty in
(try
Ctype.subtype !env ty expected_ty ()
with
Ctype.Subtype (tr1, tr2) ->
raise(Error(loc, !env, Not_subtype(tr1, tr2)))
);
assert (constrs = None);

let id = enter_variable ~is_as_variable:true loc name ty in
rp k {
pat_desc = Tpat_alias(p, id, name);
pat_loc = loc; pat_extra=[];
pat_type = expected_ty;
pat_attributes = sp.ppat_attributes;
pat_env = !env }
| Ppat_alias(sq, name) ->
assert (constrs = None);
type_pat sq expected_ty (fun q ->
Expand Down Expand Up @@ -1435,6 +1508,16 @@ and type_pat_aux ~constrs ~labels ~no_existentials ~mode ~explode ~env
| _ -> {p with pat_type = ty;
pat_extra = extra :: p.pat_extra}
in k p)
| Ppat_type lid when Variant_coercion.has_res_pat_variant_spread_attribute sp.ppat_attributes ->
let (path, p, ty) = build_or_pat_for_variant_spread !env loc lid expected_ty in
(try
Ctype.subtype !env ty expected_ty ()
with
Ctype.Subtype (tr1, tr2) ->
raise(Error(loc, !env, Not_subtype(tr1, tr2)))
);
k { p with pat_extra =
(Tpat_type (path, lid), loc, sp.ppat_attributes) :: p.pat_extra }
| Ppat_type lid ->
let (path, p,ty) = build_or_pat !env loc lid in
unify_pat_types loc !env ty expected_ty;
Expand Down
6 changes: 6 additions & 0 deletions jscomp/ml/variant_coercion.ml
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,9 @@ let variant_configuration_can_be_coerced_raises ~is_spread_context ~left_loc
right_loc;
error = TagName {left_tag; right_tag};
}))

let has_res_pat_variant_spread_attribute attrs =
attrs
|> List.find_opt (fun (({txt}, _) : Parsetree.attribute) ->
txt = "res.patVariantSpread")
|> Option.is_some
7 changes: 7 additions & 0 deletions jscomp/syntax/src/res_core.ml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ let suppressFragileMatchWarningAttr =
] )
let makeBracesAttr loc = (Location.mkloc "res.braces" loc, Parsetree.PStr [])
let templateLiteralAttr = (Location.mknoloc "res.template", Parsetree.PStr [])
let makePatVariantSpreadAttr =
(Location.mknoloc "res.patVariantSpread", Parsetree.PStr [])

let taggedTemplateLiteralAttr =
(Location.mknoloc "res.taggedTemplate", Parsetree.PStr [])
Expand Down Expand Up @@ -1077,6 +1079,11 @@ let rec parsePattern ?(alias = true) ?(or_ = true) p =
match p.Parser.token with
| Lparen -> parseConstructorPatternArgs p constr startPos attrs
| _ -> Ast_helper.Pat.construct ~loc:constr.loc ~attrs constr None)
| DotDotDot ->
Parser.next p;
let ident = parseValuePath p in
let loc = mkLoc startPos ident.loc.loc_end in
Ast_helper.Pat.type_ ~loc ~attrs:(makePatVariantSpreadAttr :: attrs) ident
| Hash -> (
Parser.next p;
if p.Parser.token == DotDotDot then (
Expand Down
9 changes: 8 additions & 1 deletion jscomp/syntax/src/res_parsetree_viewer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ let hasAwaitAttribute attrs =
| _ -> false)
attrs

let hasResPatVariantSpreadAttribute attrs =
List.exists
(function
| {Location.txt = "res.patVariantSpread"}, _ -> true
| _ -> false)
attrs

let collectArrayExpressions expr =
match expr.pexp_desc with
| Pexp_array exprs -> (exprs, None)
Expand Down Expand Up @@ -225,7 +232,7 @@ let filterParsingAttrs attrs =
( "bs" | "res.uapp" | "res.arity" | "res.braces" | "ns.braces"
| "res.iflet" | "res.namedArgLoc" | "res.optional" | "res.ternary"
| "res.async" | "res.await" | "res.template"
| "res.taggedTemplate" );
| "res.taggedTemplate" | "res.patVariantSpread" );
},
_ ) ->
false
Expand Down
1 change: 1 addition & 0 deletions jscomp/syntax/src/res_parsetree_viewer.mli
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type functionAttributesInfo = {
val processFunctionAttributes : Parsetree.attributes -> functionAttributesInfo

val hasAwaitAttribute : Parsetree.attributes -> bool
val hasResPatVariantSpreadAttribute : Parsetree.attributes -> bool

type ifConditionKind =
| If of Parsetree.expression
Expand Down
7 changes: 6 additions & 1 deletion jscomp/syntax/src/res_printer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2328,7 +2328,12 @@ and printPattern ~state (p : Parsetree.pattern) cmtTbl =
in
Doc.group (Doc.concat [variantName; argsDoc])
| Ppat_type ident ->
Doc.concat [Doc.text "#..."; printIdentPath ident cmtTbl]
let prefix =
if ParsetreeViewer.hasResPatVariantSpreadAttribute p.ppat_attributes
then ""
else "#"
in
Doc.concat [Doc.text (prefix ^ "..."); printIdentPath ident cmtTbl]
| Ppat_record (rows, openFlag) ->
Doc.group
(Doc.concat
Expand Down
59 changes: 59 additions & 0 deletions jscomp/test/VariantPatternMatchingSpreads.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions jscomp/test/VariantPatternMatchingSpreads.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
type a = One | Two | Three
type b = | ...a | Four | Five

let doWithA = (a: a) => {
switch a {
| One => Js.log("aaa")
| Two => Js.log("twwwoooo")
| Three => Js.log("threeeee")
}
}

let doWithB = (b: b) => {
switch b {
| One => Js.log("aaa")
| _ => Js.log("twwwoooo")
}
}

let lookup = (b: b) =>
switch b {
| ...a as a => doWithA(a)
| Four => Js.log("four")
| Five => Js.log("five")
}

let lookup2 = (b: b) =>
switch b {
| ...a => Js.log("spread")
| Four => Js.log("four")
| Five => Js.log("five")
}
10 changes: 9 additions & 1 deletion jscomp/test/build.ninja

Large diffs are not rendered by default.