Skip to content

Commit 8840a5f

Browse files
authored
Allow coercing elgible variants to string/int/float (#6311)
* coerce variants to string/int * coerce elgible variants to float * reuse existing compiler logic for as attribute payloads * extract match logic and bake variant matching into the same branch as record matching * restructure guard for variant coercion * format * changelog and small cleanup
1 parent de9b806 commit 8840a5f

18 files changed

+205
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- Untagged variants: consider regexp as an object type. https://github.com/rescript-lang/rescript-compiler/pull/6296
1717
- Semantic-based optimization of code generated for untagged variants https://github.com/rescript-lang/rescript-compiler/issues/6108
1818
- Record type spreads: Allow using type variables in type spreads. Both uninstantiated and instantiated ones https://github.com/rescript-lang/rescript-compiler/pull/6309
19+
- Variants: Allow coercing variants to string/int/float when applicable https://github.com/rescript-lang/rescript-compiler/pull/6311
1920

2021
#### :bug: Bug Fix
2122
- Fix issue where dynamic import of module in the expression https://github.com/rescript-lang/rescript-compiler/pull/6310
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_float.res:5:10-19
4+
5+
3 │ let x = One(true)
6+
4 │
7+
5 │ let y = (x :> float)
8+
6 │
9+
10+
Type x is not a subtype of float
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_float_as.res:5:10-19
4+
5+
3 │ let x = One
6+
4 │
7+
5 │ let y = (x :> float)
8+
6 │
9+
10+
Type x is not a subtype of float
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_int.res:5:10-17
4+
5+
3 │ let x = One(true)
6+
4 │
7+
5 │ let y = (x :> int)
8+
6 │
9+
10+
Type x is not a subtype of int
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_int_as.res:5:10-17
4+
5+
3 │ let x = One
6+
4 │
7+
5 │ let y = (x :> int)
8+
6 │
9+
10+
Type x is not a subtype of int
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_string.res:5:10-20
4+
5+
3 │ let x = One(true)
6+
4 │
7+
5 │ let y = (x :> string)
8+
6 │
9+
10+
Type x is not a subtype of string
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_string_as.res:5:10-20
4+
5+
3 │ let x = One
6+
4 │
7+
5 │ let y = (x :> string)
8+
6 │
9+
10+
Type x is not a subtype of string
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type x = | @as(1.1) One(bool) | @as(2.2) Two
2+
3+
let x = One(true)
4+
5+
let y = (x :> float)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type x = | @as(1.1) One | Two
2+
3+
let x = One
4+
5+
let y = (x :> float)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type x = | @as(1) One(bool) | @as(2) Two
2+
3+
let x = One(true)
4+
5+
let y = (x :> int)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type x = | @as(1) One | Two
2+
3+
let x = One
4+
5+
let y = (x :> int)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type x = One(bool) | Two
2+
3+
let x = One(true)
4+
5+
let y = (x :> string)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type x = One | @as(2) Two
2+
3+
let x = One
4+
5+
let y = (x :> string)

jscomp/ml/ctype.ml

+11
Original file line numberDiff line numberDiff line change
@@ -3951,6 +3951,17 @@ let rec subtype_rec env trace t1 t2 cstrs =
39513951
end
39523952
| (Tconstr(p1, _, _), _) when generic_private_abbrev env p1 ->
39533953
subtype_rec env trace (expand_abbrev_opt env t1) t2 cstrs
3954+
| (Tconstr(_, [], _), Tconstr(path, [], _)) when Variant_coercion.can_coerce_path path &&
3955+
extract_concrete_typedecl env t1 |> Variant_coercion.is_variant_typedecl |> Option.is_some
3956+
->
3957+
(* type coercion for variants *)
3958+
(match Variant_coercion.is_variant_typedecl (extract_concrete_typedecl env t1) with
3959+
| Some constructors ->
3960+
if constructors |> Variant_coercion.can_coerce_variant ~path then
3961+
cstrs
3962+
else
3963+
(trace, t1, t2, !univar_pairs)::cstrs
3964+
| None -> (trace, t1, t2, !univar_pairs)::cstrs)
39543965
| (Tconstr(_, [], _), Tconstr(_, [], _)) -> (* type coercion for records *)
39553966
(match extract_concrete_typedecl env t1, extract_concrete_typedecl env t2 with
39563967
| (_, _, {type_kind=Type_record (fields1, repr1)}), (_, _, {type_kind=Type_record (fields2, repr2)}) ->

jscomp/ml/variant_coercion.ml

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
let find_as_attribute_payload (attributes : Parsetree.attribute list) =
2+
attributes
3+
|> List.find_map (fun (attr : Parsetree.attribute) ->
4+
match attr with
5+
| {txt = "as"}, payload -> Some payload
6+
| _ -> None)
7+
8+
(* TODO: Improve error messages? Say why we can't coerce. *)
9+
10+
let check_constructors (constructors : Types.constructor_declaration list) check
11+
=
12+
List.for_all
13+
(fun (c : Types.constructor_declaration) ->
14+
check c.cd_args (find_as_attribute_payload c.cd_attributes))
15+
constructors
16+
17+
let can_coerce_to_string (constructors : Types.constructor_declaration list) =
18+
check_constructors constructors (fun args payload ->
19+
match (args, payload) with
20+
| Cstr_tuple [], None -> true
21+
| Cstr_tuple [], Some payload
22+
when Ast_payload.is_single_string payload |> Option.is_some ->
23+
true
24+
| _ -> false)
25+
26+
let can_coerce_to_int (constructors : Types.constructor_declaration list) =
27+
check_constructors constructors (fun args payload ->
28+
match (args, payload) with
29+
| Cstr_tuple [], Some payload
30+
when Ast_payload.is_single_int payload |> Option.is_some ->
31+
true
32+
| _ -> false)
33+
34+
let can_coerce_to_float (constructors : Types.constructor_declaration list) =
35+
check_constructors constructors (fun args payload ->
36+
match (args, payload) with
37+
| Cstr_tuple [], Some payload
38+
when Ast_payload.is_single_float payload |> Option.is_some ->
39+
true
40+
| _ -> false)
41+
42+
let can_coerce_path (path : Path.t) =
43+
Path.same path Predef.path_string
44+
|| Path.same path Predef.path_int
45+
|| Path.same path Predef.path_float
46+
47+
let can_coerce_variant ~(path : Path.t)
48+
(constructors : Types.constructor_declaration list) =
49+
if Path.same path Predef.path_string && can_coerce_to_string constructors then
50+
true
51+
else if Path.same path Predef.path_int && can_coerce_to_int constructors then
52+
true
53+
else if Path.same path Predef.path_float && can_coerce_to_float constructors
54+
then true
55+
else false
56+
57+
let is_variant_typedecl
58+
((_, _, typedecl) : Path.t * Path.t * Types.type_declaration) =
59+
match typedecl with
60+
| {type_kind = Type_variant constructors} -> Some constructors
61+
| _ -> None

jscomp/test/VariantCoercion.js

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

jscomp/test/VariantCoercion.res

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
type a = One | @as("two") Two | Three
2+
3+
let a: a = Three
4+
5+
let b = (a :> string)
6+
7+
type onlyInts = | @as(1) One1 | @as(2) Two2 | @as(3) Three3
8+
9+
let i = One1
10+
11+
let d = (i :> int)
12+
13+
type onlyFloats = | @as(1.1) Onef | @as(2.2) Twof | @as(3.3) Threef
14+
15+
let ii = Onef
16+
17+
let dd = (ii :> float)

jscomp/test/build.ninja

+2-1
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)