Repository: facebook/react
Pull request: [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.


Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts
patch: @@ -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') {

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.expect.md
patch: @@ -0,0 +1,56 @@
+
+## Input
+
+```javascript
+import {Stringify} from 'shared-runtime';
+
+function Repro(props) {
+  const MY_CONST = -2;
+  return <Stringify>{props.arg - MY_CONST}</Stringify>;
+}
+
+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 = <Stringify>{t0}</Stringify>;
+    $[0] = t0;
+    $[1] = t1;
+  } else {
+    t1 = $[1];
+  }
+  return t1;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+  fn: Repro,
+  params: [
+    {
+      arg: 3,
+    },
+  ],
+};
+
+```
+      
+### Eval output
+(kind: ok) <div>{"children":5}</div>
\ No newline at end of file

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/babel-repro-compact-negative-number.js
patch: @@ -0,0 +1,15 @@
+import {Stringify} from 'shared-runtime';
+
+function Repro(props) {
+  const MY_CONST = -2;
+  return <Stringify>{props.arg - MY_CONST}</Stringify>;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+  fn: Repro,
+  params: [
+    {
+      arg: 3,
+    },
+  ],
+};

filename: compiler/packages/snap/src/compiler.ts
patch: @@ -242,6 +242,7 @@ export async function transformFixtureInput(
     filename: virtualFilepath,
     highlightCode: false,
     retainLines: true,
+    compact: true,
     plugins: [
       [plugin, options],
       'babel-plugin-fbt',