Skip to content
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

Allow coercing elgible variants to string/int/float #6311

Merged
merged 7 commits into from
Jun 27, 2023
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- Untagged variants: consider regexp as an object type. https://github.com/rescript-lang/rescript-compiler/pull/6296
- Semantic-based optimization of code generated for untagged variants https://github.com/rescript-lang/rescript-compiler/issues/6108
- Record type spreads: Allow using type variables in type spreads. Both uninstantiated and instantiated ones https://github.com/rescript-lang/rescript-compiler/pull/6309
- Variants: Allow coercing variants to string/int/float when applicable https://github.com/rescript-lang/rescript-compiler/pull/6311

# 11.0.0-beta.2

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

We've found a bug for you!
/.../fixtures/variant_coercion_float.res:5:10-19

3 │ let x = One(true)
4 │
5 │ let y = (x :> float)
6 │

Type x is not a subtype of float
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

We've found a bug for you!
/.../fixtures/variant_coercion_float_as.res:5:10-19

3 │ let x = One
4 │
5 │ let y = (x :> float)
6 │

Type x is not a subtype of float
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

We've found a bug for you!
/.../fixtures/variant_coercion_int.res:5:10-17

3 │ let x = One(true)
4 │
5 │ let y = (x :> int)
6 │

Type x is not a subtype of int
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

We've found a bug for you!
/.../fixtures/variant_coercion_int_as.res:5:10-17

3 │ let x = One
4 │
5 │ let y = (x :> int)
6 │

Type x is not a subtype of int
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

We've found a bug for you!
/.../fixtures/variant_coercion_string.res:5:10-20

3 │ let x = One(true)
4 │
5 │ let y = (x :> string)
6 │

Type x is not a subtype of string
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

We've found a bug for you!
/.../fixtures/variant_coercion_string_as.res:5:10-20

3 │ let x = One
4 │
5 │ let y = (x :> string)
6 │

Type x is not a subtype of string
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type x = | @as(1.1) One(bool) | @as(2.2) Two

let x = One(true)

let y = (x :> float)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type x = | @as(1.1) One | Two

let x = One

let y = (x :> float)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type x = | @as(1) One(bool) | @as(2) Two

let x = One(true)

let y = (x :> int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type x = | @as(1) One | Two

let x = One

let y = (x :> int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type x = One(bool) | Two

let x = One(true)

let y = (x :> string)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type x = One | @as(2) Two

let x = One

let y = (x :> string)
11 changes: 11 additions & 0 deletions jscomp/ml/ctype.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3951,6 +3951,17 @@ let rec subtype_rec env trace t1 t2 cstrs =
end
| (Tconstr(p1, _, _), _) when generic_private_abbrev env p1 ->
subtype_rec env trace (expand_abbrev_opt env t1) t2 cstrs
| (Tconstr(_, [], _), Tconstr(path, [], _)) when Variant_coercion.can_coerce_path path &&
extract_concrete_typedecl env t1 |> Variant_coercion.is_variant_typedecl |> Option.is_some
->
(* type coercion for variants *)
(match Variant_coercion.is_variant_typedecl (extract_concrete_typedecl env t1) with
| Some constructors ->
if constructors |> Variant_coercion.can_coerce_variant ~path then
cstrs
else
(trace, t1, t2, !univar_pairs)::cstrs
| None -> (trace, t1, t2, !univar_pairs)::cstrs)
| (Tconstr(_, [], _), Tconstr(_, [], _)) -> (* type coercion for records *)
(match extract_concrete_typedecl env t1, extract_concrete_typedecl env t2 with
| (_, _, {type_kind=Type_record (fields1, repr1)}), (_, _, {type_kind=Type_record (fields2, repr2)}) ->
Expand Down
61 changes: 61 additions & 0 deletions jscomp/ml/variant_coercion.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
let find_as_attribute_payload (attributes : Parsetree.attribute list) =
attributes
|> List.find_map (fun (attr : Parsetree.attribute) ->
match attr with
| {txt = "as"}, payload -> Some payload
| _ -> None)

(* TODO: Improve error messages? Say why we can't coerce. *)

let check_constructors (constructors : Types.constructor_declaration list) check
=
List.for_all
(fun (c : Types.constructor_declaration) ->
check c.cd_args (find_as_attribute_payload c.cd_attributes))
constructors

let can_coerce_to_string (constructors : Types.constructor_declaration list) =
check_constructors constructors (fun args payload ->
match (args, payload) with
| Cstr_tuple [], None -> true
| Cstr_tuple [], Some payload
when Ast_payload.is_single_string payload |> Option.is_some ->
true
| _ -> false)

let can_coerce_to_int (constructors : Types.constructor_declaration list) =
check_constructors constructors (fun args payload ->
match (args, payload) with
| Cstr_tuple [], Some payload
when Ast_payload.is_single_int payload |> Option.is_some ->
true
| _ -> false)

let can_coerce_to_float (constructors : Types.constructor_declaration list) =
check_constructors constructors (fun args payload ->
match (args, payload) with
| Cstr_tuple [], Some payload
when Ast_payload.is_single_float payload |> Option.is_some ->
true
| _ -> false)

let can_coerce_path (path : Path.t) =
Path.same path Predef.path_string
|| Path.same path Predef.path_int
|| Path.same path Predef.path_float

let can_coerce_variant ~(path : Path.t)
(constructors : Types.constructor_declaration list) =
if Path.same path Predef.path_string && can_coerce_to_string constructors then
true
else if Path.same path Predef.path_int && can_coerce_to_int constructors then
true
else if Path.same path Predef.path_float && can_coerce_to_float constructors
then true
else false

let is_variant_typedecl
((_, _, typedecl) : Path.t * Path.t * Types.type_declaration) =
match typedecl with
| {type_kind = Type_variant constructors} -> Some constructors
| _ -> None
23 changes: 23 additions & 0 deletions jscomp/test/VariantCoercion.js

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

17 changes: 17 additions & 0 deletions jscomp/test/VariantCoercion.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
type a = One | @as("two") Two | Three

let a: a = Three

let b = (a :> string)

type onlyInts = | @as(1) One1 | @as(2) Two2 | @as(3) Three3

let i = One1

let d = (i :> int)

type onlyFloats = | @as(1.1) Onef | @as(2.2) Twof | @as(3.3) Threef

let ii = Onef

let dd = (ii :> float)
3 changes: 2 additions & 1 deletion jscomp/test/build.ninja

Large diffs are not rendered by default.