fix(compiler): preserve -0/0 distinction through phi constant propagation and codegen #37583

Open
sleitor opened 4:44am on September 11, 2026 wants to merge 4 commits into facebook/react main from
fix-37447

Pull Request Overview

  • Opened on September 11, 2026
  • Status Open
  • Commit count 4 with first commit September 11, 2026

Total Delta

0 Total Diff Delta

Open Days

Open 6 weekdays

Test Delta

0 Diff Delta in Test Files
Breakdown by Phase

How long has this pull request spent in each phase of its lifecycle?

Data pending calculation for pull request

Author avatar

fix(compiler): preserve -0/0 distinction through phi constant propagation and codegen

Summary

Fixes #37447. Two paths in the React Compiler collapsed JavaScript's observable distinction between 0 and -0:



  1. Constant propagation (Optimization/ConstantPropagation.ts): evaluatePhi compared phi operand constant values with !==. Since 0 !== -0 evaluates to false in JS, a phi merging 0 on one branch and -0 on another was incorrectly folded into a single "constant" value, silently discarding whichever branch didn't "win", regardless of which branch actually executed at runtime.


  2. Codegen (ReactiveScopes/CodegenReactiveFunction.ts): codegenValue only emitted a unary negation (-0) when value < 0. Since -0 < 0 is false, a literal -0 was re-emitted as plain 0, losing its sign.

Fix


  • Use Object.is() instead of !== when comparing phi constant candidates in evaluatePhi, so operands with different sign but "equal" primitive values (i.e. 0/-0) are correctly treated as non-constant (and thus preserved as a genuine runtime phi instead of being folded to one arbitrary branch's value).

  • Emit a unary negation in codegen when Object.is(value, -0) is true, in addition to the existing value < 0 check, so -0 literals round-trip correctly.

Tests


  • Added negative-zero-phi-constant-propagation.js/.expect.md: a component whose phi alternates between 0 and -0 across renders. 1 / x is used to observe the sign of zero (Infinity vs -Infinity), and the fixture harness compares compiled vs. uncompiled eval output, verifying Object.is parity is preserved pre/post compile.

  • Updated the pre-existing constant-propagation-unary-number snapshot: it already contained a -0 literal that was silently being emitted as 0 in codegen prior to this fix β€” that regression is now fixed and visible in the updated snapshot (-2, -0, true, ... instead of -2, 0, true, ...).

  • Ran the full babel-plugin-react-compiler snap test suite (1817 fixtures) β€” all pass with only the one expected snapshot update above.


  • yarn workspace babel-plugin-react-compiler lint passes.

Test plan

cd compiler/packages/babel-plugin-react-compiler

yarn build
yarn workspace snap run snap
yarn lint

Description

Two compiler paths collapse JavaScript’s observable distinction between 0 and -0: constant propagation compares phi constants with !==, and reactive-function code generation only emits a unary negative expression when a number is < 0.

Reproduction

Compile a memoized component whose phi alternates between 0 and -0, then observe the value through 1 / value. Current output reports positive infinity for both paths instead of positive then negative infinity.

Expected behavior

The compiler should preserve signed zero through optimization and emitted code.

No comments have been left on this PR.