Repository navigation
Compare literals by value when simplifying equality conjunctions - #9114
Conversation
…== c2
Why:
The simplifier read "not eq_expression" as "different constants", but
eq_expression never equates float literals, nor an int with a float. So
`x === 2.5 && x === 2.5` became `false`, and an untagged case whose
payload literal equals a literal tag (`@as(1) L1 | N(float)`, `N(1.0)`)
went to the wrong arm. Js_analyzer.eq_literal now decides literal
equality as `===` does, and the rules apply only when it knows.
JS modified (and-simplification of literal comparisons):
Example:
let a = (x: float) => x == 2.5 && x == 2.5
let b = (x: float) => x != 2.5 && x == 2.5
@unboxed type t = @as(1) L1 | N(float) | S(string)
let f = (v: t) => switch v { | N(1.0) => 4 | _ => 6 }
Before (js):
function a(x) {
return false;
}
function b(x) {
return x === 2.5;
}
function f(v) {
if (v !== 1.0) {
return 6;
} else {
return 4;
}
}
After (js):
function a(x) {
return x === 2.5;
}
function b(x) {
return false;
}
function f(v) {
return 6;
}
Fixes: #9099
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 Compare literals by JavaScript value when simplifying x === c1 && x === c2518b26e · cknitt + Claude · Oct 28, 2026, 15:31 UTC
JS modified (and-simplification of literal comparisons) let a = (x: float) => x == 2.5 && x == 2.5
let b = (x: float) => x != 2.5 && x == 2.5
@unboxed type t = @as(1) L1 | N(float) | S(string)
let f = (v: t) => switch v { | N(1.0) => 4 | _ => 6 }
function a(x) {
- return false;
+ return x === 2.5;
}
function b(x) {
- return x === 2.5;
+ return false;
}
function f(v) {
- if (v !== 1.0) {
- return 6;
- } else {
- return 4;
- }
+ return 6;
} |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #9114 +/- ##
==========================================
- Coverage 81.46% 81.45% -0.01%
==========================================
Files 460 460
Lines 64675 64695 +20
==========================================
+ Hits 52689 52699 +10
- Misses 11986 11996 +10
🚀 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-9114 |
|
@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. Delightful! 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. Comparing literal values separately from structural expression equality fixes the float and int/float cases in #9099, with a conservative fallback when parsing fails. CI is green. The committed JS regression tests also pass in a minimal runtime harness; I did not rebuild the compiler locally.
x == 2.5 && x == 2.5on a float compiled tofalse, andx != 2.5 && x == 2.5tox === 2.5. The same mistake sent untagged-variant matches to the wrong arm: with@unboxed type t = @as(1) L1 | N(float) | S(string),switch v { | N(1.0) => 4 | _ => 6 }returned4forL1, although the compiler warns that1always matches asL1.The and-simplifier's rules for
x === c1 && x === c2andx !== c1 && x === c2treated "notJs_analyzer.eq_expression" as "different constants".eq_expressionis a conservative structural check: it never equates two float literals, nor an int literal with a float literal. The rules now use a newJs_analyzer.eq_literal, which compares literals the way===does (numbers by value, so1equals1.0; NaN differs from itself; bigints never equal numbers;undefinedequalsundefinedwhatever its unit flag). It answersNonefor a float literal it cannot parse, and the rules then leave the expression alone.New test
tests/tests/src/number_literal_simplify_test.reschecks at run time equal and different float literals under==/!=, and the two untagged cases from the issue (an int tag and a float tag equal to the payload literal). The arguments go through an opaque identity so the calls are not folded to constants. The test fails without the fix.Fixes #9099
🤖 Generated with Claude Code
https://claude.ai/code/session_01Kbf3gGkPma9LAZimuQjnrP