Skip to content

[compiler] avoid outer-scope identifier conflicts in synthesizeName#36194

Open
sleitor wants to merge 1 commit intofacebook:mainfrom
sleitor:fix-36167
Open

[compiler] avoid outer-scope identifier conflicts in synthesizeName#36194
sleitor wants to merge 1 commit intofacebook:mainfrom
sleitor:fix-36167

Conversation

@sleitor
Copy link
Copy Markdown
Contributor

@sleitor sleitor commented Apr 2, 2026

Summary

When a component uses an outer-scope binding named $ (e.g. a utility helper function or an imported component alias), 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 inside Client Components.

Root cause

Context.synthesizeName checks uniqueIdentifiers (local variable names + referenced globals inside the compiled function) but does not check the Babel program scope for outer-scope bindings. A function or import named $ that is used inside a component but not recursively referenced inside the same function body is invisible to the collision check.

Fix

Extend the collision-avoidance loop in Context.synthesizeName to also call env.programContext.hasReference(validated), which queries the Babel scope for existing bindings (imports, outer declarations). If a conflict is found, the synthesized name is incremented ($0, $1, …) until a unique name is found.

Before fix:

// $ is overwritten — broken in Client Components
import { Stringify as $ } from 'shared-runtime';
function Component({x}) {
  const $ = _c(2); // ← collision!
  ...
}

After fix:

import { Stringify as $ } from 'shared-runtime';
function Component({x}) {
  const $0 = _c(2); // ← no conflict
  ...
}

How did you test this change?

Added two regression test fixtures in compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/:

  1. jsx-dollar-sign-component.tsx — outer helper function named $
  2. jsx-dollar-sign-component-imported.tsx$ imported from an external module

All 1721 compiler tests pass (yarn workspace babel-plugin-react-compiler test).

Fixes #36167

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 facebook#36167

Add two regression test fixtures:
- jsx-dollar-sign-component.tsx  — outer function named $
- jsx-dollar-sign-component-imported.tsx — $ imported from a module
@meta-cla meta-cla bot added the CLA Signed label Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Compiler Bug]: can't use $ as component name in Client Components

1 participant