Skip to content

[compiler] Preserve JSX case semantics in constant propagation#36295

Open
ousamabenyounes wants to merge 1 commit intofacebook:mainfrom
ousamabenyounes:fix/issue-35268
Open

[compiler] Preserve JSX case semantics in constant propagation#36295
ousamabenyounes wants to merge 1 commit intofacebook:mainfrom
ousamabenyounes:fix/issue-35268

Conversation

@ousamabenyounes
Copy link
Copy Markdown

@ousamabenyounes ousamabenyounes commented Apr 16, 2026

Summary

Fixes #35268.

ConstantPropagation's LoadLocal case rewrote $X = LoadLocal Comp into $X = LoadGlobal base whenever a component-named local aliased a lowercase global. When that lvalue was used as a simple JSX tag, codegen then emitted <base /> (treated as an intrinsic HTML element) instead of <Comp /> (a component reference), silently changing runtime behaviour.

Repro

const base = 'div';

function Component() {
  const Comp = base;
  return <Comp />;
}

Before this PR:

// compiled
t0 = <base />;   // rendered as the HTML `<base>` element

After this PR:

// compiled
t0 = <Comp />;   // rendered as <div /> via the Comp variable reference

Fix

constantPropagation now runs a pre-pass that collects the IdentifierId of every Place used as a simple JSX element tag (i.e. <X />, not <x.Y /> / <x:y />). In the LoadLocal rewrite step, the pass skips the substitution when:

  1. the lvalue is one of those collected JSX-tag places, and
  2. the resolved constant is a LoadGlobal, and
  3. the local binding's name and the global binding's name have different JSX component-vs-intrinsic casing.

Primitive constant folding is unaffected. Member-expression JSX tags (e.g. <localVar.Stringify />) continue to propagate since the JSX-tag Place in that case is the PropertyLoad lvalue, not the LoadLocal lvalue. Same-casing rewrites (e.g. const MyLocal = Stringify; <MyLocal /><Stringify />, as exercised by jsx-local-tag-in-lambda.js) are also unaffected.

How did you test this change?

  • yarn snap1720 passed / 1720 (1719 existing + 1 new regression fixture jsx-component-local-aliasing-lowercase-global).
  • yarn workspace babel-plugin-react-compiler lint — clean.
  • yarn flow dom-node — clean.
  • yarn linc / yarn prettier — clean.
  • Existing jsx-lowercase-localvar-memberexpr*, jsx-local-tag-in-lambda, jsx-tag-evaluation-order, const-propagation-into-function-expression-global fixtures continue to pass unchanged.

🤖 Generated with Claude Code

ConstantPropagation rewrote `$X = LoadLocal Comp` into
`$X = LoadGlobal base` whenever a component-named local aliased a
lowercase global. When the lvalue was used as a simple JSX tag, codegen
emitted `<base />` (an intrinsic HTML tag) instead of `<Comp />` (a
component reference), silently changing runtime behaviour (fixes
facebook#35268).

The fix runs a pre-pass that collects Places used as simple JSX tags,
then skips the LoadLocal → LoadGlobal rewrite when the lvalue is one of
them and the local/global names have different component-vs-intrinsic
casing. Member-expression tags (e.g. `<localVar.Stringify />`) and
same-casing rewrites (e.g. `const MyLocal = Stringify; <MyLocal />` →
`<Stringify />`) are unaffected.

Co-Authored-By: Claude <noreply@anthropic.com>
@meta-cla meta-cla bot added the CLA Signed label Apr 16, 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]: incorrectly uses variable name as JSX tag (<base />) instead of referenced value

2 participants