From d8c1280023bf5fa5fa4f2ca70f13ceb8521dc423 Mon Sep 17 00:00:00 2001 From: Dmitrii Troitskii Date: Thu, 2 Apr 2026 10:54:38 +0000 Subject: [PATCH] [compiler] avoid outer-scope identifier conflicts in synthesizeName MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a component uses an outer-scope binding named `$` (e.g. a helper function or imported component), the compiler's synthesizeName('$') would not detect the conflict and would generate a memo cache variable also named `$`, overwriting the outer-scope binding at runtime. The fix extends the collision check in `Context.synthesizeName` to also check `env.programContext.hasReference()`, which queries the Babel scope for existing bindings (including outer-scope declarations and imports). If a conflict is found, the synthesized name is incremented until unique. Fixes https://github.com/facebook/react/issues/36167 Add two regression test fixtures: - jsx-dollar-sign-component.tsx — outer function named $ - jsx-dollar-sign-component-imported.tsx — $ imported from a module --- .../ReactiveScopes/CodegenReactiveFunction.ts | 5 +- ...x-dollar-sign-component-imported.expect.md | 56 ++++++++++++++++ .../jsx-dollar-sign-component-imported.tsx | 15 +++++ .../jsx-dollar-sign-component.expect.md | 67 +++++++++++++++++++ .../compiler/jsx-dollar-sign-component.tsx | 17 +++++ 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component-imported.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component-imported.tsx create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component.tsx 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 486773d5eb91..f0c1a6b6e393 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -462,7 +462,10 @@ class Context { } let validated = makeIdentifierName(name).value; let index = 0; - while (this.uniqueIdentifiers.has(validated)) { + while ( + this.uniqueIdentifiers.has(validated) || + this.env.programContext.hasReference(validated) + ) { validated = makeIdentifierName(`${name}${index++}`).value; } this.uniqueIdentifiers.add(validated); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component-imported.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component-imported.expect.md new file mode 100644 index 000000000000..5927ddb17ca9 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component-imported.expect.md @@ -0,0 +1,56 @@ + +## Input + +```javascript +import {Stringify as $} from 'shared-runtime'; + +// Regression test: when $ is imported as a binding, the compiler should not +// use $ as the name for its synthesized memo cache variable — that would +// shadow the import. The memo cache should be renamed to $0 (or similar). +// See https://github.com/facebook/react/issues/36167 + +function Component({x}: {x: number}) { + return <$ value={x} />; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{x: 1}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify as $ } from "shared-runtime"; + +// Regression test: when $ is imported as a binding, the compiler should not +// use $ as the name for its synthesized memo cache variable — that would +// shadow the import. The memo cache should be renamed to $0 (or similar). +// See https://github.com/facebook/react/issues/36167 + +function Component(t0) { + const $0 = _c(2); + const { x } = t0; + let t1; + if ($0[0] !== x) { + t1 = <$ value={x} />; + $0[0] = x; + $0[1] = t1; + } else { + t1 = $0[1]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ x: 1 }], +}; + +``` + +### Eval output +(kind: ok)
{"value":1}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component-imported.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component-imported.tsx new file mode 100644 index 000000000000..a19d52631efd --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component-imported.tsx @@ -0,0 +1,15 @@ +import {Stringify as $} from 'shared-runtime'; + +// Regression test: when $ is imported as a binding, the compiler should not +// use $ as the name for its synthesized memo cache variable — that would +// shadow the import. The memo cache should be renamed to $0 (or similar). +// See https://github.com/facebook/react/issues/36167 + +function Component({x}: {x: number}) { + return <$ value={x} />; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{x: 1}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component.expect.md new file mode 100644 index 000000000000..1619cc10badf --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component.expect.md @@ -0,0 +1,67 @@ + +## Input + +```javascript +// Regression test: when a function is named `$`, the compiler should not +// use `$` as the name for its synthesized memo cache variable — that would +// shadow the function name. The memo cache should be renamed to $0 (or similar). +// See https://github.com/facebook/react/issues/36167 + +function $(n: number) { + return n * 2; +} + +function Component({x}: {x: number}) { + return
{$(x)}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{x: 5}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // Regression test: when a function is named `$`, the compiler should not +// use `$` as the name for its synthesized memo cache variable — that would +// shadow the function name. The memo cache should be renamed to $0 (or similar). +// See https://github.com/facebook/react/issues/36167 + +function $(n) { + return n * 2; +} + +function Component(t0) { + const $0 = _c(4); + const { x } = t0; + let t1; + if ($0[0] !== x) { + t1 = $(x); + $0[0] = x; + $0[1] = t1; + } else { + t1 = $0[1]; + } + let t2; + if ($0[2] !== t1) { + t2 =
{t1}
; + $0[2] = t1; + $0[3] = t2; + } else { + t2 = $0[3]; + } + return t2; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ x: 5 }], +}; + +``` + +### Eval output +(kind: ok)
10
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component.tsx new file mode 100644 index 000000000000..d2ccbc0288e0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-dollar-sign-component.tsx @@ -0,0 +1,17 @@ +// Regression test: when a function is named `$`, the compiler should not +// use `$` as the name for its synthesized memo cache variable — that would +// shadow the function name. The memo cache should be renamed to $0 (or similar). +// See https://github.com/facebook/react/issues/36167 + +function $(n: number) { + return n * 2; +} + +function Component({x}: {x: number}) { + return
{$(x)}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{x: 5}], +};