Skip to content

Commit 487cea6

Browse files
committed
poc of allowing ad hoc aliasing of an or pattern of literals into a polyvariant
1 parent 7f094ec commit 487cea6

File tree

12 files changed

+222
-10
lines changed

12 files changed

+222
-10
lines changed

compiler/ml/typecore.ml

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,14 @@ type error =
9595
| Type_params_not_supported of Longident.t
9696
| Field_access_on_dict_type
9797
| Jsx_not_enabled
98+
| AliasPolyFromLiterals_NonLiteralOrPattern
99+
| AliasPolyFromLiterals_UnsupportedBaseType of type_expr
98100

99101
exception Error of Location.t * Env.t * error
100102
exception Error_forward of Location.error
101103

104+
module StringSet = Set.Make (String)
105+
102106
(* Forward declaration, to be filled in by Typemod.type_module *)
103107

104108
let type_module =
@@ -608,6 +612,48 @@ let build_or_pat env loc lid =
608612
in
609613
(path, rp {r with pat_loc = loc}, ty)
610614

615+
let maybe_closed_polyvariant_from_literal_or ~env ~loc ~attrs ~typed_pat
616+
~base_ty =
617+
let has_attr =
618+
List.exists (function
619+
| {Location.txt = "res.asPolyVariantFromLiterals"}, _ -> true
620+
| _ -> false)
621+
in
622+
if not (has_attr attrs) then None
623+
else
624+
let invalid = ref false in
625+
let rec gather (p : pattern) (acc : string list) =
626+
match p.pat_desc with
627+
| Tpat_or (p1, p2, _) -> gather p2 (gather p1 acc)
628+
| Tpat_constant (Const_string (s, _)) -> s :: acc
629+
| Tpat_constant (Const_int i) -> string_of_int i :: acc
630+
| _ ->
631+
invalid := true;
632+
acc
633+
in
634+
let literals =
635+
gather typed_pat [] |> List.rev |> StringSet.of_list |> StringSet.elements
636+
in
637+
if !invalid || literals = [] then
638+
raise (Error (loc, env, AliasPolyFromLiterals_NonLiteralOrPattern))
639+
else
640+
match (repr (expand_head env base_ty)).desc with
641+
| Tconstr (p, [], _)
642+
when Path.same p Predef.path_string || Path.same p Predef.path_int ->
643+
let row =
644+
{
645+
row_fields = List.map (fun l -> (l, Rpresent None)) literals;
646+
row_more = newvar ();
647+
row_closed = true;
648+
row_fixed = false;
649+
row_name = None;
650+
}
651+
in
652+
Some (newty (Tvariant row))
653+
| _ ->
654+
raise
655+
(Error (loc, env, AliasPolyFromLiterals_UnsupportedBaseType base_ty))
656+
611657
let extract_type_from_pat_variant_spread env lid expected_ty =
612658
let path, decl = Typetexp.find_type env lid.loc lid.txt in
613659
match decl with
@@ -1290,7 +1336,14 @@ and type_pat_aux ~constrs ~labels ~no_existentials ~mode ~explode ~env sp
12901336
let ty_var =
12911337
match override_type_from_variant_spread with
12921338
| Some ty -> ty
1293-
| None -> build_as_type !env q
1339+
| None -> (
1340+
let base_ty = build_as_type !env q in
1341+
match
1342+
maybe_closed_polyvariant_from_literal_or ~env:!env ~loc
1343+
~attrs:sp.ppat_attributes ~typed_pat:q ~base_ty
1344+
with
1345+
| Some ty -> ty
1346+
| None -> base_ty)
12941347
in
12951348
end_def ();
12961349
generalize ty_var;
@@ -4719,6 +4772,16 @@ let report_error env loc ppf error =
47194772
fprintf ppf
47204773
"Cannot compile JSX expression because JSX support is not enabled. Add \
47214774
\"jsx\" settings to rescript.json to enable JSX support."
4775+
| AliasPolyFromLiterals_NonLiteralOrPattern ->
4776+
fprintf ppf
4777+
"@[The alias form @{<info>as #...id@} can only be used when matching an \
4778+
or-pattern made solely of string or int literals, e.g. \
4779+
@{<info>\"a\"|\"b\"@} or @{<info>1|2@}.@]"
4780+
| AliasPolyFromLiterals_UnsupportedBaseType ty ->
4781+
fprintf ppf
4782+
"@[The alias form @{<info>as #...id@} requires the alias to have type \
4783+
@{<info>string@} or @{<info>int@}, but here it has type@ %a@]"
4784+
type_expr ty
47224785
47234786
let report_error env loc ppf err =
47244787
Printtyp.wrap_printing_env env (fun () -> report_error env loc ppf err)

compiler/ml/typecore.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ type error =
127127
| Type_params_not_supported of Longident.t
128128
| Field_access_on_dict_type
129129
| Jsx_not_enabled
130+
| AliasPolyFromLiterals_NonLiteralOrPattern
131+
| AliasPolyFromLiterals_UnsupportedBaseType of type_expr
130132

131133
exception Error of Location.t * Env.t * error
132134
exception Error_forward of Location.error

compiler/syntax/src/res_core.ml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ let template_literal_attr = (Location.mknoloc "res.template", Parsetree.PStr [])
199199
let make_pat_variant_spread_attr =
200200
(Location.mknoloc "res.patVariantSpread", Parsetree.PStr [])
201201

202+
let make_alias_polyvariant_from_literals_attr =
203+
(Location.mknoloc "res.asPolyVariantFromLiterals", Parsetree.PStr [])
204+
202205
let tagged_template_literal_attr =
203206
(Location.mknoloc "res.taggedTemplate", Parsetree.PStr [])
204207

@@ -1318,11 +1321,19 @@ and parse_alias_pattern ~attrs pattern p =
13181321
match p.Parser.token with
13191322
| As ->
13201323
Parser.next p;
1324+
let alias_attrs =
1325+
match p.Parser.token with
1326+
| Hash ->
1327+
Parser.next p;
1328+
Parser.expect DotDotDot p;
1329+
make_alias_polyvariant_from_literals_attr :: attrs
1330+
| _ -> attrs
1331+
in
13211332
let name, loc = parse_lident p in
13221333
let name = Location.mkloc name loc in
13231334
Ast_helper.Pat.alias
13241335
~loc:{pattern.ppat_loc with loc_end = p.prev_end_pos}
1325-
~attrs pattern name
1336+
~attrs:alias_attrs pattern name
13261337
| _ -> pattern
13271338

13281339
(* or ::= pattern | pattern

compiler/syntax/src/res_parsetree_viewer.ml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ let has_res_pat_variant_spread_attribute attrs =
8080
| _ -> false)
8181
attrs
8282

83+
let has_attribute_with_name ~name attrs =
84+
List.exists
85+
(function
86+
| {Location.txt}, _ when txt = name -> true
87+
| _ -> false)
88+
attrs
89+
8390
let has_dict_pattern_attribute attrs =
8491
attrs
8592
|> List.find_opt (fun (({txt}, _) : Parsetree.attribute) ->
@@ -201,7 +208,8 @@ let filter_parsing_attrs attrs =
201208
( "meth" | "res.braces" | "ns.braces" | "res.iflet"
202209
| "res.ternary" | "res.await" | "res.template"
203210
| "res.taggedTemplate" | "res.patVariantSpread"
204-
| "res.dictPattern" | "res.inlineRecordDefinition" );
211+
| "res.asPolyVariantFromLiterals" | "res.dictPattern"
212+
| "res.inlineRecordDefinition" );
205213
},
206214
_ ) ->
207215
false
@@ -543,7 +551,8 @@ let is_printable_attribute attr =
543551
| ( {
544552
Location.txt =
545553
( "res.iflet" | "res.braces" | "ns.braces" | "JSX" | "res.await"
546-
| "res.template" | "res.ternary" | "res.inlineRecordDefinition" );
554+
| "res.template" | "res.ternary" | "res.inlineRecordDefinition"
555+
| "res.asPolyVariantFromLiterals" );
547556
},
548557
_ ) ->
549558
false

compiler/syntax/src/res_parsetree_viewer.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ val expr_is_await : Parsetree.expression -> bool
1616
val has_await_attribute : Parsetree.attributes -> bool
1717
val has_inline_record_definition_attribute : Parsetree.attributes -> bool
1818
val has_res_pat_variant_spread_attribute : Parsetree.attributes -> bool
19+
val has_attribute_with_name : name:string -> Parsetree.attributes -> bool
1920
val has_dict_pattern_attribute : Parsetree.attributes -> bool
2021

2122
type if_condition_kind =

compiler/syntax/src/res_printer.ml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2611,18 +2611,24 @@ and print_pattern ~state (p : Parsetree.pattern) cmt_tbl =
26112611
(Doc.concat docs)
26122612
| Ppat_extension ext ->
26132613
print_extension ~state ~at_module_lvl:false ext cmt_tbl
2614-
| Ppat_alias (p, alias_loc) ->
2614+
| Ppat_alias (inner_p, alias_loc) ->
26152615
let needs_parens =
2616-
match p.ppat_desc with
2616+
match inner_p.ppat_desc with
26172617
| Ppat_or (_, _) | Ppat_alias (_, _) -> true
26182618
| _ -> false
26192619
in
26202620
let rendered_pattern =
2621-
let p = print_pattern ~state p cmt_tbl in
2621+
let p = print_pattern ~state inner_p cmt_tbl in
26222622
if needs_parens then Doc.concat [Doc.text "("; p; Doc.text ")"] else p
26232623
in
2624-
Doc.concat
2625-
[rendered_pattern; Doc.text " as "; print_string_loc alias_loc cmt_tbl]
2624+
let alias_doc =
2625+
if
2626+
ParsetreeViewer.has_attribute_with_name
2627+
~name:"res.asPolyVariantFromLiterals" p.ppat_attributes
2628+
then Doc.concat [Doc.text "#..."; print_string_loc alias_loc cmt_tbl]
2629+
else print_string_loc alias_loc cmt_tbl
2630+
in
2631+
Doc.concat [rendered_pattern; Doc.text " as "; alias_doc]
26262632
(* Note: module(P : S) is represented as *)
26272633
(* Ppat_constraint(Ppat_unpack, Ptyp_package) *)
26282634
| Ppat_constraint
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/alias_poly_nonliteral.res:3:5-22
4+
5+
1 │ let f = x =>
6+
2 │ switch x {
7+
3 │ | ("a" | _) as #...f => 1
8+
4 │ | _ => 0
9+
5 │ }
10+
11+
The alias form as #...id can only be used when matching an or-pattern made solely of string or int literals, e.g. "a"|"b" or 1|2.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
let f = x =>
2+
switch x {
3+
| ("a" | _) as #...f => 1
4+
| _ => 0
5+
}

tests/syntax_tests/data/parsing/grammar/pattern/expected/or.res.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@
44
| Blue as c1|Red as c2 -> ()
55
| Blue as c1|Red as c2 -> ()
66
| exception Exit|exception Continue -> ()
7-
| exception (Exit|exception Continue) -> ()
7+
| exception (Exit|exception Continue) -> ()
8+
| (({js|a|js}|{js|b|js}|{js|c|js} as f)[@res.asPolyVariantFromLiterals ])
9+
-> ()
10+
| ((1|2 as n)[@res.asPolyVariantFromLiterals ]) -> ()

tests/syntax_tests/data/parsing/grammar/pattern/or.res

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ switch x {
55
| (Blue as c1) | (Red as c2) => ()
66
| exception Exit | exception Continue => ()
77
| exception (Exit | exception Continue) => ()
8+
| ("a" | "b" | "c") as #...f => ()
9+
| (1 | 2) as #...n => ()
810
}

0 commit comments

Comments
 (0)