Semantic Diff

#9024 Drop leftover conditions and empty branches from if statements

3 commits

188e24e0e1dcb624df7b32cf76257667f9c0933b..pr-9024

137367c0a · cknitt + Claude · Oct 24, 2026, 18:32 UTC

Keep the operator of a negated float comparison

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.

modifiedJS negated float comparison

let k = (x: float) => !(x < 0.0)
js
Before
After
function k(x) {function k(x) { return x >= 0.0; 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.
adde988da · cknitt + Claude · Oct 24, 2026, 16:35 UTC

Drop a pure condition when both branches of an if are empty

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.

modifiedJS 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)  }
js
Before tests/tests/src/fun_pattern_match.res
After 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";if (typeof lhs === "object" && typeof rhs === "object") { return Primitive_int.compare(lhs._0, rhs._0); }}throw {throw {

Review

  • Is a condition with side effects still evaluated? Yes, only conditions that no_side_effect_expression accepts are dropped.
6e9058762 · cknitt + Claude · Oct 24, 2026, 17:09 UTC

Leave no empty branch when flattening a conditional statement

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.

modifiedJS switch with an empty case at statement level

let r = ref(0)let f = (x: int) => {  switch x {  | 1 => r := 2  | _ => ()  }  Console.log(r)}
js
Before
After
if (x !== 1) { } else {if (x === 1) { r.contents = 2; 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.