Skip to content
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 @@ -24,6 +24,7 @@

- Support Windows 11 on ARM by installing the x64 binaries, which Windows runs through its x64 emulation. https://github.com/rescript-lang/rescript/pull/8776
- Allow `$` in identifiers, e.g. `let count$ = signal(0)`. https://github.com/rescript-lang/rescript/pull/8791
- Allow an untagged variant to have both a boolean literal case and a boolean payload case, e.g. `@unboxed type t = | @as(true) A | B(bool)`, like for strings and numbers. https://github.com/rescript-lang/rescript/pull/8777
Comment thread
cknitt marked this conversation as resolved.

#### :bug: Bug fix

Expand Down
25 changes: 19 additions & 6 deletions compiler/ml/ast_untagged_variants.ml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type untagged_error =
| AtMostOneNumber
| AtMostOneBigint
| AtMostOneBoolean
| UnreachableBoolean of string
| DuplicateLiteral of string
| ConstructorMoreThanOneArg of string
type error =
Expand Down Expand Up @@ -50,6 +51,10 @@ let report_error ppf =
| AtMostOneFunction -> "At most one case can be a function type."
| AtMostOneString -> "At most one case can be a string type."
| AtMostOneBoolean -> "At most one case can be a boolean type."
| UnreachableBoolean name ->
"Case " ^ name
^ " can never match: both true and false are already cases of this \
variant."
| AtMostOneNumber ->
"At most one case can be a number type (int or float)."
| AtMostOneBigint -> "At most one case can be a bigint type."
Expand Down Expand Up @@ -259,11 +264,6 @@ let check_invariant ~is_untagged_def ~(consts : (Location.t * tag) list)
raise (Error (loc, InvalidUntaggedVariantDefinition AtMostOneBigint));
if !boolean_types > 1 then
raise (Error (loc, InvalidUntaggedVariantDefinition AtMostOneBoolean));
if
!boolean_types > 0
&& (String_set.mem "true" !nonstring_literals_consts
|| String_set.mem "false" !nonstring_literals_consts)
then raise (Error (loc, InvalidUntaggedVariantDefinition AtMostOneBoolean));
()
in
let check_literal ~is_const ~loc (tag : tag) =
Expand Down Expand Up @@ -296,7 +296,20 @@ let check_invariant ~is_untagged_def ~(consts : (Location.t * tag) list)
| FunctionType -> incr function_types
| IntType | FloatType -> incr number_types
| BigintType -> incr bigint_types
| BooleanType -> incr boolean_types
| BooleanType ->
(* Literal cases are matched before payload cases, so a boolean
payload only receives the booleans that are not literal cases.
A partial overlap is allowed, like for strings and numbers. *)
if
String_set.mem "true" !nonstring_literals_consts
&& String_set.mem "false" !nonstring_literals_consts
then
raise
(Error
( loc,
InvalidUntaggedVariantDefinition
(UnreachableBoolean tag.name) ));
incr boolean_types
| StringType -> incr string_types);
invariant loc tag.name
| Tagged _ -> ())
Expand Down
7 changes: 6 additions & 1 deletion compiler/ml/variant_runtime.ml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,12 @@ type matching_facts = {
(** Runtime shapes of constructors represented directly by their
payload. Tagged object constructors do not appear here. *)
literal_tags: literal_tag list;
(** Runtime values of all nullary constructors. *)
(** Runtime values of all nullary constructors. In an untagged variant
these may overlap the values of a payload constructor, as in
[@as("a") A | S(string)]. Literals are always tested first, so the
payload constructor only receives values that are not literals, and
[S("a")] matches as [A]: constructor identity is not observable at
runtime for such values. *)
has_null: bool;
has_undefined: bool;
has_other_literal: bool;
Expand Down
1 change: 1 addition & 0 deletions tests/ERROR_VARIANTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ Untagged-variant validation errors. Source: `type untagged_error` and `type erro
| `AtMostOneNumber` | ✓ | `UntaggedAtMostOneNumber.res` | `int` and `float` payloads collide on the number runtime check. |
| `AtMostOneBigint` | ✓ | `UntaggedAtMostOneBigint.res` | Two bigint payloads. |
| `AtMostOneBoolean` | ✓ | `UntaggedAtMostOneBoolean.res` | Two boolean payloads. |
| `UnreachableBoolean` | ✓ | `UntaggedUnreachableBoolean.res` | Boolean payload alongside both `@as(true)` and `@as(false)` cases. |
| `DuplicateLiteral` | ✓ | `UntaggedDuplicateLiteral.res` | `@as("x")` on two different constructors. |
| `ConstructorMoreThanOneArg` | ✓ | `UntaggedConstructorMoreThanOneArg.res`, `UntaggedConstructorAttributeMoreThanOneArg.res` | Multiple payload arguments with type-level or constructor-level `@unboxed`. |

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

We've found a bug for you!
/.../fixtures/UntaggedUnreachableBoolean.res:2:42-53

1 │ @unboxed
2 │ type t = | @as(true) Yes | @as(false) No | Flag(bool)
3 │

This untagged variant definition is invalid: Case Flag can never match: both true and false are already cases of this variant.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@unboxed
type t = | @as(true) Yes | @as(false) No | Flag(bool)
2 changes: 1 addition & 1 deletion tests/build_tests/unboxed_bool_with_const/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const stderr = normalizeNewlines(stripVTControlCharacters(out.stderr));
assert.ok(stderr.includes("Main.res:3:3-14"));
assert.ok(
stderr.includes(
"This untagged variant definition is invalid: At most one case can be a boolean type.",
"This untagged variant definition is invalid: Case Bool can never match: both true and false are already cases of this variant.",
),
);
assert.ok(stderr.includes("Failed to Compile"));
Expand Down
145 changes: 94 additions & 51 deletions tests/tests/src/unboxed_variant_fold_test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,34 @@ function flagName(v) {
}
}

function answerName(v) {
if (v === true) {
return "yes";
} else if (v) {
return "answer true";
} else {
return "answer false";
}
}

function mixedName(v) {
if (v === false || v === null) {
if (v === false) {
return "off";
} else {
return "unset";
}
} else if (typeof v === "boolean") {
if (v) {
return "toggle true";
} else {
return "toggle false";
}
} else {
return v;
}
}

function numName(v) {
if (v === 1) {
return "one";
Expand Down Expand Up @@ -135,75 +163,88 @@ function makeRecord(value) {

Mocha.describe("Unboxed_variant_fold_test", () => {
Mocha.test("constructors preserve dynamic payloads and effects", () => {
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 142, characters 7-14", colorName(makeColor("primary")), "not Color");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 143, characters 7-14", colorName(makeColor("blue")), "blue");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 144, characters 7-14", recName(makeRecord(1)), "rec");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 165, characters 7-14", colorName(makeColor("primary")), "not Color");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 166, characters 7-14", colorName(makeColor("blue")), "blue");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 167, characters 7-14", recName(makeRecord(1)), "rec");
let calls = 0;
calls = calls + 1 | 0;
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 150, characters 7-14", colorName("primary"), "not Color");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 151, characters 7-14", calls, 1);
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 173, characters 7-14", colorName("primary"), "not Color");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 174, characters 7-14", calls, 1);
});
Mocha.test("folding agrees with runtime dispatch", () => {
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 154, characters 7-14", "not Color", colorName(id("primary")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 155, characters 7-14", "not Color", colorName(id("secondary")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 156, characters 7-14", "blue", colorName(id("blue")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 157, characters 7-14", "not Color", colorName(id("primary")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 158, characters 7-14", "one", numberName(id(1)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 159, characters 7-14", "number", numberName(id(2)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 160, characters 7-14", "one", numberName(id(1)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 161, characters 7-14", "int", pureName(id(1)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 162, characters 7-14", "string", pureName(id("x")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 163, characters 7-14", "obj", optName(id({
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 177, characters 7-14", "not Color", colorName(id("primary")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 178, characters 7-14", "not Color", colorName(id("secondary")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 179, characters 7-14", "blue", colorName(id("blue")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 180, characters 7-14", "not Color", colorName(id("primary")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 181, characters 7-14", "one", numberName(id(1)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 182, characters 7-14", "number", numberName(id(2)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 183, characters 7-14", "one", numberName(id(1)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 184, characters 7-14", "int", pureName(id(1)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 185, characters 7-14", "string", pureName(id("x")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 186, characters 7-14", "obj", optName(id({
x: 1
})));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 164, characters 7-14", "null", optName(id(null)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 165, characters 7-14", "undef", optName(id(undefined)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 166, characters 7-14", "str", flagName(id("true")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 167, characters 7-14", "yes", flagName(id(true)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 168, characters 7-14", "one", numName(id(1.0)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 169, characters 7-14", "float", numName(id(2.5)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 170, characters 7-14", "one", numName(id(1)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 171, characters 7-14", "rec", recName(id({
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 187, characters 7-14", "null", optName(id(null)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 188, characters 7-14", "undef", optName(id(undefined)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 189, characters 7-14", "str", flagName(id("true")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 190, characters 7-14", "yes", flagName(id(true)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 191, characters 7-14", "yes", answerName(id(true)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 192, characters 7-14", "answer false", answerName(id(false)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 193, characters 7-14", "yes", answerName(id(true)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 194, characters 7-14", "toggle true", mixedName(id(true)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 195, characters 7-14", "off", mixedName(id(false)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 196, characters 7-14", "off", mixedName(id(false)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 197, characters 7-14", "unset", mixedName(id(null)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 198, characters 7-14", "false", mixedName(id("false")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 199, characters 7-14", "one", numName(id(1.0)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 200, characters 7-14", "float", numName(id(2.5)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 201, characters 7-14", "one", numName(id(1)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 202, characters 7-14", "rec", recName(id({
y: 1
})));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 172, characters 7-14", "empty", recName(id("empty")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 173, characters 7-14", "lit", pick(id("a")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 174, characters 7-14", "z", pick(id("z")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 175, characters 7-14", "lit", pick(id("a")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 176, characters 7-14", "w", outerName(id("primary")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 177, characters 7-14", "x", outerName(id("x")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 178, characters 7-14", "x", outerName(id("x")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 179, characters 7-14", "primary", boxedName(id({
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 203, characters 7-14", "empty", recName(id("empty")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 204, characters 7-14", "lit", pick(id("a")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 205, characters 7-14", "z", pick(id("z")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 206, characters 7-14", "lit", pick(id("a")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 207, characters 7-14", "w", outerName(id("primary")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 208, characters 7-14", "x", outerName(id("x")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 209, characters 7-14", "x", outerName(id("x")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 210, characters 7-14", "primary", boxedName(id({
TAG: "BoxedColor",
_0: "primary"
})));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 180, characters 7-14", "not Color", boxedName(id("primary")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 181, characters 7-14", "tuple", objectName(id([
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 211, characters 7-14", "not Color", boxedName(id("primary")));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 212, characters 7-14", "tuple", objectName(id([
1,
2
])));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 182, characters 7-14", "record", objectName(id({
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 213, characters 7-14", "record", objectName(id({
x: 1
})));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 183, characters 7-14", "zero", listName(id(/* [] */0)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 184, characters 7-14", "list", listName(id({hd: 1, tl: /* [] */0})));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 185, characters 7-14", bigintName(10n), bigintName(id(10n)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 186, characters 7-14", "one", wideName(id(1)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 214, characters 7-14", "zero", listName(id(/* [] */0)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 215, characters 7-14", "list", listName(id({hd: 1, tl: /* [] */0})));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 216, characters 7-14", bigintName(10n), bigintName(id(10n)));
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 217, characters 7-14", "one", wideName(id(1)));
});
Mocha.test("the folded answers themselves are correct", () => {
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 190, characters 7-14", "not Color", "not Color");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 191, characters 7-14", "blue", "blue");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 192, characters 7-14", "one", "one");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 193, characters 7-14", "number", "number");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 194, characters 7-14", "int", "int");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 195, characters 7-14", "one", "one");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 196, characters 7-14", "lit", "lit");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 197, characters 7-14", "x", "x");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 198, characters 7-14", "primary", "primary");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 199, characters 7-14", "tuple", "tuple");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 200, characters 7-14", "zero", "zero");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 201, characters 7-14", bigintName(10n), "one");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 202, characters 7-14", "one", "one");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 221, characters 7-14", "not Color", "not Color");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 222, characters 7-14", "blue", "blue");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 223, characters 7-14", "one", "one");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 224, characters 7-14", "number", "number");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 225, characters 7-14", "int", "int");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 226, characters 7-14", "yes", "yes");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 227, characters 7-14", "answer false", "answer false");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 228, characters 7-14", "off", "off");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 229, characters 7-14", "toggle true", "toggle true");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 230, characters 7-14", "false", "false");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 231, characters 7-14", "one", "one");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 232, characters 7-14", "lit", "lit");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 233, characters 7-14", "x", "x");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 234, characters 7-14", "primary", "primary");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 235, characters 7-14", "tuple", "tuple");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 236, characters 7-14", "zero", "zero");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 237, characters 7-14", bigintName(10n), "one");
Test_utils.eq("File \"unboxed_variant_fold_test.res\", line 238, characters 7-14", "one", "one");
});
});

Expand All @@ -213,6 +254,8 @@ export {
pureName,
optName,
flagName,
answerName,
mixedName,
numName,
recName,
pick,
Expand Down
Loading
Loading