Skip to content

Commit 761adc2

Browse files
authored
Allow coercing unboxed with payload to primitive when applicable (#6441)
* allow coercing unboxed with payload to primitive when applicable * test * changelog * tests * refactor
1 parent 330b256 commit 761adc2

7 files changed

+85
-20
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
1313
# 11.0.0-rc.5 (Unreleased)
1414

15+
#### :rocket: New Feature
16+
- Allow coercing unboxed variants with only strings (now including with a single payload of string) to the primitive string. https://github.com/rescript-lang/rescript-compiler/pull/6441
17+
1518
#### :bug: Bug Fix
1619

1720
- Fix issue with dynamic import of module in nested expressions https://github.com/rescript-lang/rescript-compiler/pull/6431
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_unboxed.res:6:10-20
4+
5+
4 │ let x = One
6+
5 │
7+
6 │ let y = (x :> string)
8+
7 │
9+
10+
Type x is not a subtype of string
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@unboxed
2+
type x = One | Two | Other(float)
3+
4+
let x = One
5+
6+
let y = (x :> string)

jscomp/ml/ctype.ml

+2-2
Original file line numberDiff line numberDiff line change
@@ -3956,8 +3956,8 @@ let rec subtype_rec env trace t1 t2 cstrs =
39563956
->
39573957
(* type coercion for variants to primitives *)
39583958
(match Variant_coercion.can_try_coerce_variant_to_primitive (extract_concrete_typedecl env t1) with
3959-
| Some constructors ->
3960-
if constructors |> Variant_coercion.can_coerce_variant ~path then
3959+
| Some (constructors, unboxed) ->
3960+
if constructors |> Variant_coercion.variant_has_same_runtime_representation_as_target ~targetPath:path ~unboxed then
39613961
cstrs
39623962
else
39633963
(trace, t1, t2, !univar_pairs)::cstrs

jscomp/ml/variant_coercion.ml

+32-16
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,44 @@ let can_coerce_path (path : Path.t) =
66
|| Path.same path Predef.path_int
77
|| Path.same path Predef.path_float
88

9-
let can_coerce_variant ~(path : Path.t)
10-
(constructors : Types.constructor_declaration list) =
11-
constructors
12-
|> List.for_all (fun (c : Types.constructor_declaration) ->
13-
let args = c.cd_args in
14-
let payload = Ast_untagged_variants.process_tag_type c.cd_attributes in
15-
match args with
16-
| Cstr_tuple [] -> (
17-
match payload with
18-
| None | Some (String _) -> Path.same path Predef.path_string
19-
| Some (Int _) -> Path.same path Predef.path_int
20-
| Some (Float _) -> Path.same path Predef.path_float
21-
| Some (Null | Undefined | Bool _ | Untagged _) -> false)
22-
| _ -> false)
9+
let check_paths_same p1 p2 target_path =
10+
Path.same p1 target_path && Path.same p2 target_path
11+
12+
(* Checks if every case of the variant has the same runtime representation as the target type. *)
13+
let variant_has_same_runtime_representation_as_target ~(targetPath : Path.t)
14+
~unboxed (constructors : Types.constructor_declaration list) =
15+
(* Helper function to check if a constructor has the same runtime representation as the target type *)
16+
let has_same_runtime_representation (c : Types.constructor_declaration) =
17+
let args = c.cd_args in
18+
let asPayload = Ast_untagged_variants.process_tag_type c.cd_attributes in
19+
20+
match args with
21+
| Cstr_tuple [{desc = Tconstr (p, [], _)}] when unboxed ->
22+
let path_same = check_paths_same p targetPath in
23+
(* unboxed String(string) :> string *)
24+
path_same Predef.path_string
25+
|| (* unboxed Number(float) :> float *)
26+
path_same Predef.path_float
27+
| Cstr_tuple [] -> (
28+
(* Check that @as payloads match with the target path to coerce to.
29+
No @as means the default encoding, which is string *)
30+
match asPayload with
31+
| None | Some (String _) -> Path.same targetPath Predef.path_string
32+
| Some (Int _) -> Path.same targetPath Predef.path_int
33+
| Some (Float _) -> Path.same targetPath Predef.path_float
34+
| Some (Null | Undefined | Bool _ | Untagged _) -> false)
35+
| _ -> false
36+
in
37+
38+
List.for_all has_same_runtime_representation constructors
2339

2440
let can_try_coerce_variant_to_primitive
2541
((_, p, typedecl) : Path.t * Path.t * Types.type_declaration) =
2642
match typedecl with
27-
| {type_kind = Type_variant constructors; type_params = []}
43+
| {type_kind = Type_variant constructors; type_params = []; type_attributes}
2844
when Path.name p <> "bool" ->
2945
(* bool is represented as a variant internally, so we need to account for that *)
30-
Some constructors
46+
Some (constructors, type_attributes |> Ast_untagged_variants.has_untagged)
3147
| _ -> None
3248

3349
let variant_representation_matches (c1_attrs : Parsetree.attributes)

jscomp/test/VariantCoercion.js

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

jscomp/test/VariantCoercion.res

+14
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,17 @@ module CoerceVariants = {
3030
let x: x = One({age: 1})
3131
let y: y = (x :> y)
3232
}
33+
34+
module CoerceWithPayload = {
35+
@unboxed type strings = String(string) | First | Second | Third
36+
let a: strings = String("hello")
37+
let aa: strings = First
38+
let b: string = (a :> string)
39+
let bb: string = (aa :> string)
40+
41+
@unboxed type floats = Number(float) | @as(1.) First | @as(2.) Second | @as(3.) Third
42+
let c: floats = Number(100.)
43+
let cc: floats = Second
44+
let d: float = (c :> float)
45+
let dd: float = (cc :> float)
46+
}

0 commit comments

Comments
 (0)