mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
fix(minifier): do not fold a -0 ternary branch to +a (loses the sign) (#23637)
### Summary The minifier folded `a ? 1 : -0` to `+a` (and `a ? -0 : 1` to `+!a`), turning a `-0` branch into `+0` and changing the produced value. ### Problem The fold matched the `-0` operand against the `0.0` literal pattern (`-0.0 == 0.0` in Rust), so `a ? 1 : -0` became `+a`. When the test selects the `-0` branch, `+a` / `+!a` yields `+0`. `-0` and `+0` are equal under `===`, but they are distinct values: `Object.is(-0, +0) === false`, `1 / -0 === -Infinity` vs `1 / +0 === +Infinity`, `Math.atan2(0, -0) === Math.PI` vs `0`. Code relying on SameValue (e.g. React's `Object.is`-based `useState`/`useMemo` bailout) or on the sign of zero behaves differently. ### Fix Bind the zero operand with `@` and guard it with `!is_sign_negative()`, so only a `+0` branch is folded. Pure `+0` cases (`a ? 1 : 0` → `+!!a`, `a ? 0 : 1` → `+!a`) are unchanged. ### Testing - Added cases to `test_minimize_conditional_numeric`: `a ? 1 : -0` and `a ? -0 : 1` are left unfolded; `!y ? 1 : -0` may still be negated and branch-swapped to `y ? -0 : 1`, but `-0` is preserved. - `cargo test -p oxc_minifier` (536 passed) and `cargo lint -- -D warnings` pass.
This commit is contained in:
1 parent
133bb35dee
commit
4b1284071a
2 files changed
+13
-4
No files matched your search
@@ -516,7 +516,9 @@ impl<'a> PeepholeOptimizations {
|
||||
.and_then(ConstantValue::into_number)
|
||||
.filter(|_| !expr.alternate.may_have_side_effects(ctx)),
|
||||
) {
|
||||
(Some(1.0), Some(0.0)) => {
|
||||
// The `0` must be `+0`: `a ? 1 : -0` would become `+a`, which yields `+0` (not `-0`)
|
||||
// when the test is falsy. `-0.0` matches the `0.0` pattern (`-0.0 == 0.0`), so guard it.
|
||||
(Some(1.0), Some(alternate @ 0.0)) if !alternate.is_sign_negative() => {
|
||||
// "a ? 1 : 0"
|
||||
let is_boolean = expr.test.value_type(ctx).is_boolean();
|
||||
let needs_parens = Self::test_needs_parens(&expr.test);
|
||||
@@ -538,10 +540,11 @@ impl<'a> PeepholeOptimizations {
|
||||
test, ctx));
|
||||
}
|
||||
}
|
||||
(Some(0.0), Some(1.0))
|
||||
(Some(consequent @ 0.0), Some(1.0))
|
||||
// "a ? 0 : 1" => "+!a"
|
||||
// Skip if parens would be needed (e.g., "a+b?0:1" => "+!(a+b)" is same length)
|
||||
if !Self::test_needs_parens(&expr.test) => {
|
||||
// Skip if parens would be needed (e.g., "a+b?0:1" => "+!(a+b)" is same length).
|
||||
// The `0` must be `+0`: `a ? -0 : 1` would become `+!a`, yielding `+0`, not `-0`.
|
||||
if !consequent.is_sign_negative() && !Self::test_needs_parens(&expr.test) => {
|
||||
let test = expr.test.take_in(ctx);
|
||||
let test = Self::minimize_not(expr.span, test, ctx);
|
||||
return Some(Expression::new_unary_expression(expr.span,
|
||||
|
||||
@@ -104,6 +104,12 @@ fn test_minimize_conditional_numeric() {
|
||||
|
||||
// "a ? 0 : 1" stays when parens would make it same or longer
|
||||
test("let x = a + b ? 0 : 1", "let x = a + b ? 0 : 1");
|
||||
|
||||
// `-0` must not be folded to `+a`/`+!a`: that would turn the `-0` branch into `+0`.
|
||||
test_same("let x = a ? 1 : -0");
|
||||
test_same("let x = a ? -0 : 1");
|
||||
// The test may still be negated + branches swapped, but `-0` is preserved (not `+0`).
|
||||
test("let x = !y ? 1 : -0", "let x = y ? -0 : 1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user