diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b8ee2b84d..4c68d5e690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ #### :rocket: New Feature - 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 +- Allow coercing strings to unboxed variants that has a catch-all unboxed string case. https://github.com/rescript-lang/rescript-compiler/pull/6443 #### :bug: Bug Fix diff --git a/jscomp/build_tests/super_errors/expected/variant_coercion_string_to_variant_no_payload.res.expected b/jscomp/build_tests/super_errors/expected/variant_coercion_string_to_variant_no_payload.res.expected new file mode 100644 index 0000000000..1b60c86925 --- /dev/null +++ b/jscomp/build_tests/super_errors/expected/variant_coercion_string_to_variant_no_payload.res.expected @@ -0,0 +1,10 @@ + + We've found a bug for you! + /.../fixtures/variant_coercion_string_to_variant_no_payload.res:6:10-15 + + 4 │ let x = "one" + 5 │ + 6 │ let y = (x :> x) + 7 │ + + Type string is not a subtype of x \ No newline at end of file diff --git a/jscomp/build_tests/super_errors/fixtures/variant_coercion_string_to_variant_no_payload.res b/jscomp/build_tests/super_errors/fixtures/variant_coercion_string_to_variant_no_payload.res new file mode 100644 index 0000000000..fd92df20c1 --- /dev/null +++ b/jscomp/build_tests/super_errors/fixtures/variant_coercion_string_to_variant_no_payload.res @@ -0,0 +1,6 @@ +@unboxed +type x = One | Two + +let x = "one" + +let y = (x :> x) diff --git a/jscomp/ml/ctype.ml b/jscomp/ml/ctype.ml index 0a3afa2027..cb4595d27b 100644 --- a/jscomp/ml/ctype.ml +++ b/jscomp/ml/ctype.ml @@ -3951,6 +3951,19 @@ 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(path, [], _), Tconstr(_, [], _)) when Path.same path Predef.path_string && + extract_concrete_typedecl env t2 |> Variant_coercion.can_try_coerce_variant_to_primitive |> Option.is_some + -> + (* type coercion for strings to elgible unboxed variants: + - must be unboxed + - must have a constructor case with a string payload *) + (match Variant_coercion.can_try_coerce_variant_to_primitive (extract_concrete_typedecl env t2) with + | Some (constructors, true) -> + if constructors |> Variant_coercion.variant_has_catch_all_string_case then + cstrs + else + (trace, t1, t2, !univar_pairs)::cstrs + | _ -> (trace, t1, t2, !univar_pairs)::cstrs) | (Tconstr(_, [], _), Tconstr(path, [], _)) when Variant_coercion.can_coerce_path path && extract_concrete_typedecl env t1 |> Variant_coercion.can_try_coerce_variant_to_primitive |> Option.is_some -> diff --git a/jscomp/ml/variant_coercion.ml b/jscomp/ml/variant_coercion.ml index 728a326e73..7912c76f0a 100644 --- a/jscomp/ml/variant_coercion.ml +++ b/jscomp/ml/variant_coercion.ml @@ -9,6 +9,17 @@ let can_coerce_path (path : Path.t) = let check_paths_same p1 p2 target_path = Path.same p1 target_path && Path.same p2 target_path +let variant_has_catch_all_string_case (constructors : Types.constructor_declaration list) = + let has_catch_all_string_case (c : Types.constructor_declaration) = + let args = c.cd_args in + match args with + | Cstr_tuple [{desc = Tconstr (p, [], _)}] -> + Path.same p Predef.path_string + | _ -> false + in + + constructors |> List.exists has_catch_all_string_case + (* Checks if every case of the variant has the same runtime representation as the target type. *) let variant_has_same_runtime_representation_as_target ~(targetPath : Path.t) ~unboxed (constructors : Types.constructor_declaration list) = diff --git a/jscomp/test/VariantCoercion.js b/jscomp/test/VariantCoercion.js index de7f98ecaf..6c471714c9 100644 --- a/jscomp/test/VariantCoercion.js +++ b/jscomp/test/VariantCoercion.js @@ -29,7 +29,22 @@ var CoerceWithPayload = { dd: 2 }; -var a$1 = "Three"; +var a$1 = "hello"; + +var aa = "First"; + +var c$1 = "Hi"; + +var CoerceFromStringToVariant = { + a: a$1, + aa: aa, + b: a$1, + bb: aa, + c: c$1, + cc: c$1 +}; + +var a$2 = "Three"; var b = "Three"; @@ -41,7 +56,7 @@ var ii = 1.1; var dd = 1.1; -exports.a = a$1; +exports.a = a$2; exports.b = b; exports.i = i; exports.d = d; @@ -49,4 +64,5 @@ exports.ii = ii; exports.dd = dd; exports.CoerceVariants = CoerceVariants; exports.CoerceWithPayload = CoerceWithPayload; +exports.CoerceFromStringToVariant = CoerceFromStringToVariant; /* No side effect */ diff --git a/jscomp/test/VariantCoercion.res b/jscomp/test/VariantCoercion.res index 90b48d8935..9aee78a3e0 100644 --- a/jscomp/test/VariantCoercion.res +++ b/jscomp/test/VariantCoercion.res @@ -44,3 +44,15 @@ module CoerceWithPayload = { let d: float = (c :> float) let dd: float = (cc :> float) } + +module CoerceFromStringToVariant = { + @unboxed type strings = String(string) | First | Second | Third + let a = "hello" + let aa = "First" + let b: strings = (a :> strings) + let bb: strings = (aa :> strings) + + @unboxed type mixed = String(string) | @as(1) One | @as(null) Null | Two + let c = "Hi" + let cc: mixed = (c :> mixed) +}