From 1d4cbaffb5f201550a0f98ba85b574ee3d4059e7 Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 2 Jun 2025 11:31:51 -0400 Subject: [PATCH] [compiler][patch] Emit unary expressions instead of negative numbers This is a babel bug + edge case. Babel compact mode produces invalid JavaScript (i.e. parse error) when given a `NumericLiteral` with a negative value. See https://codesandbox.io/p/devbox/5d47fr for repro. As a followup, we could change our test infra parse babel options (e.g. a babel transform options pragma) which could let us track regressions. We may add an "exhaustive" mode to the compiler test runner to test (1) different babel options and (2) commonly used versions. --- .../ReactiveScopes/CodegenReactiveFunction.ts | 13 ++++- ...el-repro-compact-negative-number.expect.md | 56 +++++++++++++++++++ .../babel-repro-compact-negative-number.js | 15 +++++ compiler/packages/snap/src/compiler.ts | 1 + 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index 33a124dcec6e2..17c62c02a6ee8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -1726,7 +1726,7 @@ function codegenInstructionValue( } case 'UnaryExpression': { value = t.unaryExpression( - instrValue.operator as 'throw', // todo + instrValue.operator, codegenPlaceToExpression(cx, instrValue.value), ); break; @@ -2582,7 +2582,16 @@ function codegenValue( value: boolean | number | string | null | undefined, ): t.Expression { if (typeof value === 'number') { - return t.numericLiteral(value); + if (value < 0) { + /** + * Babel's code generator produces invalid JS for negative numbers when + * run with { compact: true }. + * See repro https://codesandbox.io/p/devbox/5d47fr + */ + return t.unaryExpression('-', t.numericLiteral(-value), false); + } else { + return t.numericLiteral(value); + } } else if (typeof value === 'boolean') { return t.booleanLiteral(value); } else if (typeof value === 'string') { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.expect.md new file mode 100644 index 0000000000000..70e19e0744a4a --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.expect.md @@ -0,0 +1,56 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +function Repro(props) { + const MY_CONST = -2; + return {props.arg - MY_CONST}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Repro, + params: [ + { + arg: 3, + }, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +function Repro(props) { + const $ = _c(2); + + const t0 = props.arg - -2; + let t1; + if ($[0] !== t0) { + t1 = {t0}; + $[0] = t0; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Repro, + params: [ + { + arg: 3, + }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"children":5}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.js new file mode 100644 index 0000000000000..891589bc981d4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.js @@ -0,0 +1,15 @@ +import {Stringify} from 'shared-runtime'; + +function Repro(props) { + const MY_CONST = -2; + return {props.arg - MY_CONST}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Repro, + params: [ + { + arg: 3, + }, + ], +}; diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index 0cadf30bf0eb1..7241ed51492bc 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -242,6 +242,7 @@ export async function transformFixtureInput( filename: virtualFilepath, highlightCode: false, retainLines: true, + compact: true, plugins: [ [plugin, options], 'babel-plugin-fbt',