[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',