From 717d266fa345c897e06ef4bbd2cc4cc4c9be13be Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sun, 25 Oct 2026 06:02:19 +0000 Subject: [PATCH] Simplify a boolean other than one to the other boolean Why: `typeof x === "boolean" && x !== b` was simplified to the constant `!b`, not to `x === !b` as its doc comment says. A match on a bool payload beside a bool literal case produces this conjunction, so it never or always took the payload arm (#9035). JS modified (bool payload beside a bool literal): Example: @unboxed type t = | @as(true) LitTrue | AnyBool(bool) | AnyInt(int) let f = (v: t) => switch v { | AnyBool(_) => "bool" | _ => "other" } Before (js): function f(v) { return "other"; } After (js): function f(v) { if (v !== false) { return "other"; } else { return "bool"; } } Fixes #9035 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CMkrYaktctdKc4M41WF36t Signed-off-by: Christoph Knittel --- changes/untagged-bool-payload-match.fix.md | 1 + compiler/core/js_exp_make.ml | 16 ++++++- .../tests/src/untagged_bool_payload_test.mjs | 44 +++++++++++++++++++ .../tests/src/untagged_bool_payload_test.res | 36 +++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 changes/untagged-bool-payload-match.fix.md create mode 100644 tests/tests/src/untagged_bool_payload_test.mjs create mode 100644 tests/tests/src/untagged_bool_payload_test.res diff --git a/changes/untagged-bool-payload-match.fix.md b/changes/untagged-bool-payload-match.fix.md new file mode 100644 index 0000000000..1e0eb099a4 --- /dev/null +++ b/changes/untagged-bool-payload-match.fix.md @@ -0,0 +1 @@ +- Fix matching a bool payload of an untagged variant beside a bool literal case in a match that does not name every case. diff --git a/compiler/core/js_exp_make.ml b/compiler/core/js_exp_make.ml index 31b6a1dd78..f65284cb24 100644 --- a/compiler/core/js_exp_make.ml +++ b/compiler/core/js_exp_make.ml @@ -1059,7 +1059,21 @@ let rec simplify_and_ ~n (e1 : t) (e2 : t) : t option = {expression_desc = Typeof {expression_desc = Var ia}}, {expression_desc = Str "boolean"} ) ) when Js_op_util.same_vident ia ib -> - Some {expression_desc = Bool (not b); comment = None; source_loc = None} + (* A boolean other than [b] is [!b] *) + Some + { + expression_desc = + Bin + ( EqEqEq, + {expression_desc = Var ib; comment = None; source_loc = None}, + { + expression_desc = Bool (not b); + comment = None; + source_loc = None; + } ); + comment = None; + source_loc = None; + } | ( Bin ( EqEqEq, {expression_desc = Typeof {expression_desc = Var ia}}, diff --git a/tests/tests/src/untagged_bool_payload_test.mjs b/tests/tests/src/untagged_bool_payload_test.mjs new file mode 100644 index 0000000000..b00d9f2413 --- /dev/null +++ b/tests/tests/src/untagged_bool_payload_test.mjs @@ -0,0 +1,44 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Mocha from "mocha"; +import * as Test_utils from "./test_utils.mjs"; + +function id(x) { + return x; +} + +function trueKind(v) { + if (v !== false) { + return "other"; + } else { + return "bool"; + } +} + +function falseKind(v) { + if (v !== true) { + return "other"; + } else { + return "bool"; + } +} + +Mocha.describe("Untagged_bool_payload_test", () => { + Mocha.test("beside @as(true)", () => { + Test_utils.eq("File \"untagged_bool_payload_test.res\", line 26, characters 7-14", trueKind(id(false)), "bool"); + Test_utils.eq("File \"untagged_bool_payload_test.res\", line 27, characters 7-14", trueKind(id(true)), "other"); + Test_utils.eq("File \"untagged_bool_payload_test.res\", line 28, characters 7-14", trueKind(id(1)), "other"); + }); + Mocha.test("beside @as(false)", () => { + Test_utils.eq("File \"untagged_bool_payload_test.res\", line 32, characters 7-14", falseKind(id(true)), "bool"); + Test_utils.eq("File \"untagged_bool_payload_test.res\", line 33, characters 7-14", falseKind(id(false)), "other"); + Test_utils.eq("File \"untagged_bool_payload_test.res\", line 34, characters 7-14", falseKind(id(1)), "other"); + }); +}); + +export { + id, + trueKind, + falseKind, +} +/* Not a pure module */ diff --git a/tests/tests/src/untagged_bool_payload_test.res b/tests/tests/src/untagged_bool_payload_test.res new file mode 100644 index 0000000000..88e2ac7064 --- /dev/null +++ b/tests/tests/src/untagged_bool_payload_test.res @@ -0,0 +1,36 @@ +open Mocha +open Test_utils + +// A bool payload receives the booleans that are not literal cases, also in a +// match that does not name every case (#9035) + +@unboxed type withTrue = | @as(true) LitTrue | TrueBool(bool) | TrueInt(int) +@unboxed type withFalse = | @as(false) LitFalse | FalseBool(bool) | FalseInt(int) + +@inline(never) let id = x => x + +let trueKind = (v: withTrue) => + switch v { + | TrueBool(_) => "bool" + | _ => "other" + } + +let falseKind = (v: withFalse) => + switch v { + | FalseBool(_) => "bool" + | _ => "other" + } + +describe(__MODULE__, () => { + test("beside @as(true)", () => { + eq(__LOC__, trueKind(id(TrueBool(false))), "bool") + eq(__LOC__, trueKind(id(LitTrue)), "other") + eq(__LOC__, trueKind(id(TrueInt(1))), "other") + }) + + test("beside @as(false)", () => { + eq(__LOC__, falseKind(id(FalseBool(true))), "bool") + eq(__LOC__, falseKind(id(LitFalse)), "other") + eq(__LOC__, falseKind(id(FalseInt(1))), "other") + }) +})