Skip to content

Commit c8ed227

Browse files
authored
Coerce polyvariant to variant (rescript-lang#6981)
* wip coerce polyvariant to variant * fix unboxed catch-all * logic * cleanup * add fixture tests for polyvariant to variant coercion * changelog
1 parent 094fb15 commit c8ed227

15 files changed

+187
-0
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
1313
# 12.0.0-alpha.2 (Unreleased)
1414

15+
#### :rocket: New Feature
16+
17+
- Allow coercing polyvariants to variants when we can guarantee that the runtime representation matches. https://github.com/rescript-lang/rescript-compiler/pull/6981
18+
1519
#### :nail_care: Polish
1620

1721
- Improve formatting in the generated js code. https://github.com/rescript-lang/rescript-compiler/pull/6932
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/variant_coercion_open_polyvariant.res:5:19-30
4+
5+
3 │ let p = #One
6+
4 │
7+
5 │ let v: variant = (p :> variant)
8+
6 │
9+
10+
Type [> #One] is not a subtype of variant
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/variant_coercion_polyvariant_mismatch_as_attribute.res:7:19-30
4+
5+
5 │ let p: poly = #One
6+
6 │
7+
7 │ let v: variant = (p :> variant)
8+
8 │
9+
10+
Type poly = [#One | #Two] is not a subtype of variant
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/variant_coercion_polyvariant_mismatch_as_attribute2.res:7:19-30
4+
5+
5 │ let p: poly = #One
6+
6 │
7+
7 │ let v: variant = (p :> variant)
8+
8 │
9+
10+
Type poly = [#One | #Two] is not a subtype of variant
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/variant_coercion_polyvariant_unmatched_cases.res:7:19-30
4+
5+
5 │ let p: poly = #One
6+
6 │
7+
7 │ let v: variant = (p :> variant)
8+
8 │
9+
10+
Type poly = [#One | #Two] is not a subtype of variant
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/variant_coercion_polyvariant_with_payload.res:7:19-30
4+
5+
5 │ let p: poly = #One
6+
6 │
7+
7 │ let v: variant = (p :> variant)
8+
8 │
9+
10+
Type poly = [#One | #Two(string)] is not a subtype of variant
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type variant = One | Two
2+
3+
let p = #One
4+
5+
let v: variant = (p :> variant)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type poly = [#One | #Two]
2+
3+
type variant = One | @as("two") Two
4+
5+
let p: poly = #One
6+
7+
let v: variant = (p :> variant)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type poly = [#One | #Two]
2+
3+
type variant = One | @as(2) Two
4+
5+
let p: poly = #One
6+
7+
let v: variant = (p :> variant)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type poly = [#One | #Two]
2+
3+
type variant = One
4+
5+
let p: poly = #One
6+
7+
let v: variant = (p :> variant)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type poly = [#One | #Two(string)]
2+
3+
type variant = One | Two
4+
5+
let p: poly = #One
6+
7+
let v: variant = (p :> variant)

jscomp/ml/ctype.ml

+8
Original file line numberDiff line numberDiff line change
@@ -3701,6 +3701,14 @@ let rec subtype_rec env trace t1 t2 cstrs =
37013701
with Exit ->
37023702
(trace, t1, t2, !univar_pairs)::cstrs
37033703
end
3704+
| (Tvariant {row_closed=true; row_fields}, Tconstr (_, [], _))
3705+
when extract_concrete_typedecl_opt env t2 |> Variant_coercion.type_is_variant ->
3706+
(match extract_concrete_typedecl env t2 with
3707+
| (_, _, {type_kind=Type_variant variant_constructors; type_attributes}) ->
3708+
(match Variant_coercion.can_coerce_polyvariant_to_variant ~row_fields ~variant_constructors ~type_attributes with
3709+
| Ok _ -> cstrs
3710+
| Error _ -> (trace, t1, t2, !univar_pairs)::cstrs)
3711+
| _ -> (trace, t1, t2, !univar_pairs)::cstrs)
37043712
| Tvariant v, _ when
37053713
!variant_is_subtype env (row_repr v) t2
37063714
->

jscomp/ml/variant_coercion.ml

+52
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,55 @@ let variant_configuration_can_be_coerced_raises ~is_spread_context ~left_loc
151151
right_loc;
152152
error = TagName {left_tag; right_tag};
153153
}))
154+
155+
let can_coerce_polyvariant_to_variant ~row_fields ~variant_constructors ~type_attributes
156+
=
157+
let polyvariant_runtime_representations =
158+
row_fields
159+
|> List.filter_map (fun (label, (field : Types.row_field)) ->
160+
(* Check that there's no payload in the polyvariant *)
161+
match field with
162+
| Rpresent None -> Some label
163+
| _ -> None)
164+
in
165+
if List.length polyvariant_runtime_representations <> List.length row_fields
166+
then
167+
(* Error: At least one polyvariant constructor has a payload. Cannot have payloads. *)
168+
Error `PolyvariantConstructorHasPayload
169+
else
170+
let is_unboxed = Ast_untagged_variants.has_untagged type_attributes in
171+
if
172+
List.for_all
173+
(fun polyvariant_value ->
174+
variant_constructors
175+
|> List.exists (fun (c : Types.constructor_declaration) ->
176+
let constructor_name = Ident.name c.cd_id in
177+
match
178+
Ast_untagged_variants.process_tag_type c.cd_attributes
179+
with
180+
| Some (String as_runtime_string) ->
181+
(* `@as("")`, does the configured string match the polyvariant value? *)
182+
as_runtime_string = polyvariant_value
183+
| Some _ ->
184+
(* Any other `@as` can't match since it's by definition not a string *)
185+
false
186+
| None ->
187+
(* No `@as` means the runtime representation will be the constructor
188+
name as a string.
189+
190+
However, there's a special case with unboxed types where there's a
191+
string catch-all case. In that case, any polyvariant will match,
192+
since the catch-all case will match any string. *)
193+
(match is_unboxed, c.cd_args with
194+
| true, Cstr_tuple [{desc=Tconstr (p, _, _)}] ->
195+
Path.same p Predef.path_string
196+
| _ -> polyvariant_value = constructor_name)
197+
))
198+
polyvariant_runtime_representations
199+
then Ok ()
200+
else Error `Unknown
201+
202+
let type_is_variant (typ: (Path.t * Path.t * Types.type_declaration) option) =
203+
match typ with
204+
| Some (_, _, {type_kind = Type_variant _; _}) -> true
205+
| _ -> false

jscomp/test/VariantCoercion.js

+12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jscomp/test/VariantCoercion.res

+28
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,31 @@ module CoerceFromBigintToVariant = {
9292
let c = 120n
9393
let cc: mixed = (c :> mixed)
9494
}
95+
96+
module CoerceFromPolyvariantToVariant = {
97+
type simple = [#One | #Two]
98+
type simpleP = One | Two
99+
100+
let simple: simple = #One
101+
let simpleP = (simple :> simpleP)
102+
103+
type withAs = [#One | #two]
104+
type withAsP = One | @as("two") Two
105+
106+
let withAs: withAs = #One
107+
let withAsP = (withAs :> withAsP)
108+
109+
type withMoreVariantConstructors = [#One | #two]
110+
type withMoreVariantConstructorsP = One | @as("two") Two | Three
111+
112+
let withMoreVariantConstructors: withMoreVariantConstructors = #One
113+
let withMoreVariantConstructorsP = (withMoreVariantConstructors :> withMoreVariantConstructorsP)
114+
115+
type withUnboxedCatchAll = [#One | #someOtherThing]
116+
117+
@unboxed
118+
type withUnboxedCatchAllP = One | @as("two") Two | Three | Other(string)
119+
120+
let withUnboxedCatchAll: withUnboxedCatchAll = #One
121+
let withUnboxedCatchAllP = (withUnboxedCatchAll :> withUnboxedCatchAllP)
122+
}

0 commit comments

Comments
 (0)