You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
constant-folding-plugin emits 0/0 (NaN) for arithmetic on destructured bindings #1927
and Babel returns { confident: true, value: NaN }, because path.evaluate() resolves a destructured binding to its declarator's whole init object. Filed upstream: babel/babel#18238
The plugin then does path.replaceWith(t.valueToNode(result.value)), and valueToNode(NaN) emits 0 / 0.
Why it is easy to miss
Babel only hands back the bad value when each destructured binding is referenced exactly once (a binding.references > 1 guard masks it otherwise), and only when the object is a local literal — destructuring from params, props or imports deopts earlier. So the trigger is narrow, but when it fires the result is a silent wrong value in a shipped bundle. This has caused a rendering failure in a released React Native app: a coordinate became NaN and the renderer drew nothing, with no error. Nothing fails loudly, and dev builds look fine, so it is very hard to trace back to the bundler.
Suggested defensive fix
The plugin already refuses to fold subtrees containing calls or assignments, so it is defensive by design — it just has no protection against the evaluator being confidently wrong. Declining to substitute a non-finite result would have prevented this entirely, and legitimate folds to NaN/Infinity are vanishingly rare and harmless to skip:
Likewise, const e = O.e preserves numeric 81 for the addition case. No dependency patch is needed.
I put the before/after fixture and runnable bundle checks here. Both the reported Metro 0.84.4 + Babel traverse 7.29.7 combination and Metro 0.87.0 + traverse 7.29.8 reproduced the bad values; the adjusted source returned the expected values with dev: false, minify: true for ios and android.
A false flag in a conditional also changed branches in the original fixture, so rejecting non-finite numeric results alone would leave another incorrect case. The property-read version passed that check too.
These are actual Metro bundles executed in Node's VM, not React Native/Expo or device tests. The rewrite is limited to simple bindings; defaults, rest and nested patterns need separate handling.
Activity
tevinch commentedon Sep 13, 2026
For the simple local-object case, replacing the affected destructuring with direct property reads works as a temporary source workaround:
Likewise,
const e = O.epreserves numeric81for the addition case. No dependency patch is needed.I put the before/after fixture and runnable bundle checks here. Both the reported Metro 0.84.4 + Babel traverse 7.29.7 combination and Metro 0.87.0 + traverse 7.29.8 reproduced the bad values; the adjusted source returned the expected values with
dev: false, minify: trueforiosandandroid.A false flag in a conditional also changed branches in the original fixture, so rejecting non-finite numeric results alone would leave another incorrect case. The property-read version passed that check too.
These are actual Metro bundles executed in Node's VM, not React Native/Expo or device tests. The rewrite is limited to simple bindings; defaults, rest and nested patterns need separate handling.