Repository navigation
Keep both checks in x !== true && x !== false - #9113
Conversation
Why:
The JS simplifier rewrote `x !== true && x !== false` to `false` (and
`x === true || x === false` to `true`) as if `x` were a boolean. In an
untagged variant `x` can be any payload, so matches with both boolean
literal cases, or a `bool` payload beside one, lost their other checks
(#9098). The boolean-only rule is removed; the general constant rules
below it still fold every case that holds for any `x`.
JS modified (untagged match with both boolean literals):
Example:
@unboxed type t = D(Date.t) | @as(true) Lt | @as(false) Lf | R(RegExp.t)
let g = (v: t) => switch v { | D(_) => 2 | _ => 4 }
let h = (v: t) => switch v { | Lt | Lf => 1 | _ => 3 }
Before (js):
function g(v) {
return 4;
}
function h(v) {
return 1;
}
After (js):
function g(v) {
if (v === true || v === false || !(v instanceof Date)) {
return 4;
} else {
return 2;
}
}
function h(v) {
if (v === true || v === false) {
return 1;
} else {
return 3;
}
}
JS modified (bool payload beside a boolean literal):
Example:
@unboxed type t = Bo(bool) | S(string) | @as(true) Lt
let f = (v: t) => switch v { | Bo(false) => 2 | _ => 6 }
Before (js):
if (v === true) {
After (js):
if (v !== false) {
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Kbf3gGkPma9LAZimuQjnrP
Signed-off-by: Christoph Knittel <[email protected]>
Changelog🐛 Bug fix
Semantic Diff1 commit · Full page Keep both boolean checks in x !== true && x !== falsef61e918 · cknitt + Claude · Oct 28, 2026, 15:27 UTC
JS modified (untagged match with both boolean literals) @unboxed type t = D(Date.t) | @as(true) Lt | @as(false) Lf | R(RegExp.t)
let g = (v: t) => switch v { | D(_) => 2 | _ => 4 }
let h = (v: t) => switch v { | Lt | Lf => 1 | _ => 3 }
function g(v) {
- return 4;
+ if (v === true || v === false || !(v instanceof Date)) {
+ return 4;
+ } else {
+ return 2;
+ }
}
function h(v) {
- return 1;
+ if (v === true || v === false) {
+ return 1;
+ } else {
+ return 3;
+ }
}JS modified (bool payload beside a boolean literal) @unboxed type t = Bo(bool) | S(string) | @as(true) Lt
let f = (v: t) => switch v { | Bo(false) => 2 | _ => 6 }
-if (v === true) {
+if (v !== false) { |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #9113 +/- ##
==========================================
+ Coverage 81.46% 81.47% +0.01%
==========================================
Files 460 460
Lines 64675 64668 -7
==========================================
+ Hits 52689 52691 +2
+ Misses 11986 11977 -9
🚀 New features to boost your workflow:
|
rescript
@rescript/belt
@rescript/darwin-arm64
@rescript/linux-arm64
@rescript/linux-x64
@rescript/runtime
@rescript/win32-x64
commit: |
|
Developer playground preview: https://rescript-lang.github.io/rescript/dev-playground/?version=pr-9113 |
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex Review: Didn't find any major issues. 🎉 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
cristianoc
left a comment
There was a problem hiding this comment.
Looks good. Removing the boolean-only rule preserves the non-boolean payload checks, and the regression tests cover both examples from #9098 plus the combined literal arm. CI is green. The committed JS tests also pass in a minimal runtime harness; I did not rebuild the compiler locally.
Untagged variants with boolean literal cases could be miscompiled: a match with both
@as(true)and@as(false)cases that doesn't name them dropped its other checks (switch v { | D(_) => 2 | _ => 4 }compiled toreturn 4), and aboolpayload beside one boolean literal checked the wrong thing (Bo(false)matchedS("hello")).The cause is a rule in
Js_exp_make.simplify_and_that foldedx === b1 && x === b2andx !== b1 && x !== b2on boolean constants as ifxwere a boolean, sox !== true && x !== falsebecamefalse(and, through negation insimplify_or_,x === true || x === falsebecametrue). In an untagged variantxcan be any payload. The rule is removed. The general constant rules that follow it already fold the combinations that hold for everyx(equal constants,x === a && x === bwith different constants, mixed===/!==), so the only change in output is that the unsound case is kept.The new test
tests/tests/src/untagged_bool_literals_test.resruns both programs from the issue and a match withLt | Lfin one case, on every constructor; all three tests fail with the previous compiler. No other generated test output changes.Fixes #9098
🤖 Generated with Claude Code
https://claude.ai/code/session_01Kbf3gGkPma9LAZimuQjnrP