Repository navigation
Drop leftover conditions and empty branches from if statements - #9024
Conversation
Changelog🐛 Bug fix
💅 Polish
Semantic Diff3 commits · Full page ✨ Keep the operator of a negated float comparison137367c · cknitt + Claude · Oct 24, 2026, 18:32 UTC · new Js_exp_make.not negated a comparison by swapping its operator, turning !(x < 0.0) into x >= 0.0, which is false for NaN. Float comparisons, and comparisons of operands of unknown type, now use their own operators, printed as <, <=, >, >=, which not leaves as !(…). Fixes #9026. JS modified (negated float comparison) let k = (x: float) => !(x < 0.0)
function k(x) {
- return x >= 0.0;
+ return !(x < 0.0);
}Review
Drop a pure condition when both branches of an if are emptyadde988 · cknitt + Claude · Oct 24, 2026, 16:35 UTC A switch whose remaining cases all fall through to the same code left their test behind as a bare statement, such as JS modified (switch with cases that fall through) type rank = Uninitialized | Visited | Ranked(int)
let f3 = (lhs: rank, rhs: rank) =>
switch (lhs, rhs) {
| (Ranked(x), Ranked(y)) => Pervasives.compare(x, y)
| _ => assert(false)
}
-if (typeof lhs !== "object") {
- lhs === "Uninitialized";
-} else {
- if (typeof rhs === "object") {
- return Primitive_int.compare(lhs._0, rhs._0);
- }
- rhs === "Uninitialized";
+if (typeof lhs === "object" && typeof rhs === "object") {
+ return Primitive_int.compare(lhs._0, rhs._0);
}
throw {Review
Leave no empty branch when flattening a conditional statement6e90587 · cknitt + Claude · Oct 24, 2026, 17:09 UTC The flatten pass turned a statement JS modified (switch with an empty case at statement level) let r = ref(0)
let f = (x: int) => {
switch x {
| 1 => r := 2
| _ => ()
}
Console.log(r)
}
-if (x !== 1) {
-
-} else {
+if (x === 1) {
r.contents = 2;
}Review
|
|
@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. Bravo. 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". |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #9024 +/- ##
=======================================
Coverage 81.32% 81.32%
=======================================
Files 456 456
Lines 64017 64042 +25
=======================================
+ Hits 52059 52082 +23
- Misses 11958 11960 +2
🚀 New features to boost your workflow:
|
rescript
@rescript/belt
@rescript/darwin-arm64
@rescript/linux-arm64
@rescript/linux-x64
@rescript/runtime
@rescript/win32-x64
commit: |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f074ff9361
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
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".
|
Developer playground preview: https://rescript-lang.github.io/rescript/dev-playground/?version=pr-9024 |
Js_exp_make.not negated a comparison by swapping its operator, turning !(x < 0.0) into x >= 0.0, which is false for NaN. Float comparisons, and comparisons of operands of unknown type, now use their own operators, printed as <, <=, >, >=, which not leaves as !(…). Fixes #9026. JS modified (negated float comparison): Example: let k = (x: float) => !(x < 0.0) Before (js): function k(x) { return x >= 0.0; } After (js): function k(x) { return !(x < 0.0); } Review: Do int, bigint and string comparisons change? No, they keep the old operators, so negating them still swaps the operator. The only changed output is in caml_compare_bigint_test, whose bigint values are typed as float. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015yE4gCmTmJKVecrVt1PU8C Signed-off-by: Christoph Knittel <ck@cca.io>
A switch whose remaining cases all fall through to the same code left
their test behind as a bare statement, such as `x === null;`, and the
empty block it left in an enclosing if blocked merging the conditions.
JS modified (switch with cases that fall through):
Example:
type rank = Uninitialized | Visited | Ranked(int)
let f3 = (lhs: rank, rhs: rank) =>
switch (lhs, rhs) {
| (Ranked(x), Ranked(y)) => Pervasives.compare(x, y)
| _ => assert(false)
}
Before (js): tests/tests/src/fun_pattern_match.res
if (typeof lhs !== "object") {
lhs === "Uninitialized";
} else {
if (typeof rhs === "object") {
return Primitive_int.compare(lhs._0, rhs._0);
}
rhs === "Uninitialized";
}
throw {
After (js): tests/tests/src/fun_pattern_match.res
if (typeof lhs === "object" && typeof rhs === "object") {
return Primitive_int.compare(lhs._0, rhs._0);
}
throw {
Review: Is a condition with side effects still evaluated? Yes, only
conditions that no_side_effect_expression accepts are dropped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015yE4gCmTmJKVecrVt1PU8C
Signed-off-by: Christoph Knittel <ck@cca.io>
The flatten pass turned a statement `c ? b : e` into an if with `b;` and
`e;` as branches; a pure branch value such as `undefined` was dropped
later and left an empty branch behind.
JS modified (switch with an empty case at statement level):
Example:
let r = ref(0)
let f = (x: int) => {
switch x {
| 1 => r := 2
| _ => ()
}
Console.log(r)
}
Before (js):
if (x !== 1) {
} else {
r.contents = 2;
}
After (js):
if (x === 1) {
r.contents = 2;
}
Review: Why not build the if with S.if_? It folds an if/else of two
expression statements back into a conditional expression, which is
what this pass undoes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015yE4gCmTmJKVecrVt1PU8C
Signed-off-by: Christoph Knittel <ck@cca.io>
f074ff9 to
6e90587
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. What shall we delve into next? 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". |
Brings in the 13 commits on master since the branch started, through rescript-lang#9024. Only compiler/ml/depend.ml and yarn.lock changed on both sides, in separate places: master adds the `finally` of `try` to the dependency walk, and the branch records every prefix of a free path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Ethan Cline <7142767+e-cline@users.noreply.github.com>
Two places in JS generation left an
ifbehind with nothing to run in a branch. Removing those branches negates more conditions, so a negation bug that was already on master is fixed first.1. Negated float comparisons.
Js_exp_make.notnegated a comparison by swapping its operator. That's wrong for floats, because a comparison with NaN is alwaysfalse:Before:
After:
k(NaN)returnedfalseinstead oftrue. Float comparisons (Pfloatcomp) and comparisons of operands of unknown type (Pjscomp, from%unsafe_ltetc.) now use their own operators,Float_lt,Float_le,Float_gtandFloat_ge. They are printed as<,<=,>,>=, andnotleaves them as!(…). Int, bigint and string comparisons are unchanged, since their operands can't be NaN. The only existing output this changes iscaml_compare_bigint_test.mjs, whose values come from@val external bigint: string => floatand so are typed as floats. New test:negated_float_comparison_test.res.Fixes #9026.
2. A bare condition.
Js_stmt_make.if_turned anifwith two empty branches into its condition as a statement. This happened, for example, when the remaining cases of a switch all fall through. The condition is now dropped whenno_side_effect_expressionaccepts it. Empty blocks in the branches are ignored, so an enclosingifcan still merge conditions. The both-branches-pure case inLam_compilenow goes through the same path.Before:
After:
3. An empty branch.
Js_pass_flattenturns a statementc ? b : eintoif (c) { b; } else { e; }. Whenborewas pure, a later pass removed it and left{}. A pure branch is now left out: with an empty then-branch the condition is negated, and with both empty the condition is dropped when it is pure.Before:
After:
In
Stdlib_IteratorTests.mjs, a dropped condition was the only use of alet match = next(it), so the call is now a plain statement and the latermatch$Nnames are renumbered.🤖 Generated with Claude Code
https://claude.ai/code/session_015yE4gCmTmJKVecrVt1PU8C