@samitouri / QOS-React-1 / commits / 526dd340b3

[compiler][patch] Emit unary expressions instead of negative numbers (#33383)

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.

mofeiZ committed Jun 2, 2025 at 11:43 UTC 526dd340b3e77193846fe5eed02b9bb89d7c2d15
4 files changed +83 -2
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts
+11 -2
@@ -1726,7 +1726,7 @@ function codegenInstructionValue(
1726 }
1727 case 'UnaryExpression': {
1728 value = t.unaryExpression(
1729 - instrValue.operator as 'throw', // todo
1729 + instrValue.operator,
1730 codegenPlaceToExpression(cx, instrValue.value),
1731 );
1732 break;
@@ -2582,7 +2582,16 @@ function codegenValue(
2582 value: boolean | number | string | null | undefined,
2583 ): t.Expression {
2584 if (typeof value === 'number') {
2585 - return t.numericLiteral(value);
2585 + if (value < 0) {
2586 + /**
2587 + * Babel's code generator produces invalid JS for negative numbers when
2588 + * run with { compact: true }.
2589 + * See repro https://codesandbox.io/p/devbox/5d47fr
2590 + */
2591 + return t.unaryExpression('-', t.numericLiteral(-value), false);
2592 + } else {
2593 + return t.numericLiteral(value);
2594 + }
2595 } else if (typeof value === 'boolean') {
2596 return t.booleanLiteral(value);
2597 } else if (typeof value === 'string') {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.expect.md new
+56
@@ -0,0 +1,56 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +import {Stringify} from 'shared-runtime';
6 +
7 +function Repro(props) {
8 + const MY_CONST = -2;
9 + return <Stringify>{props.arg - MY_CONST}</Stringify>;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: Repro,
14 + params: [
15 + {
16 + arg: 3,
17 + },
18 + ],
19 +};
20 +
21 +```
22 +
23 +## Code
24 +
25 +```javascript
26 +import { c as _c } from "react/compiler-runtime";
27 +import { Stringify } from "shared-runtime";
28 +
29 +function Repro(props) {
30 + const $ = _c(2);
31 +
32 + const t0 = props.arg - -2;
33 + let t1;
34 + if ($[0] !== t0) {
35 + t1 = <Stringify>{t0}</Stringify>;
36 + $[0] = t0;
37 + $[1] = t1;
38 + } else {
39 + t1 = $[1];
40 + }
41 + return t1;
42 +}
43 +
44 +export const FIXTURE_ENTRYPOINT = {
45 + fn: Repro,
46 + params: [
47 + {
48 + arg: 3,
49 + },
50 + ],
51 +};
52 +
53 +```
54 +
55 +### Eval output
56 +(kind: ok) <div>{"children":5}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.js new
+15
@@ -0,0 +1,15 @@
1 +import {Stringify} from 'shared-runtime';
2 +
3 +function Repro(props) {
4 + const MY_CONST = -2;
5 + return <Stringify>{props.arg - MY_CONST}</Stringify>;
6 +}
7 +
8 +export const FIXTURE_ENTRYPOINT = {
9 + fn: Repro,
10 + params: [
11 + {
12 + arg: 3,
13 + },
14 + ],
15 +};
compiler/packages/snap/src/compiler.ts
+1
@@ -242,6 +242,7 @@ export async function transformFixtureInput(
242 filename: virtualFilepath,
243 highlightCode: false,
244 retainLines: true,
245 + compact: true,
246 plugins: [
247 [plugin, options],
248 'babel-plugin-fbt',