Skip to content

fix(react_compiler): keep imports referenced only by a computed key#23586

Merged
graphite-app[bot] merged 1 commit into
mainfrom
fix/react-compiler-computed-key-imports
Jun 18, 2026
Merged

fix(react_compiler): keep imports referenced only by a computed key#23586
graphite-app[bot] merged 1 commit into
mainfrom
fix/react-compiler-computed-key-imports

Conversation

@Boshen

@Boshen Boshen commented Jun 18, 2026

Copy link
Copy Markdown
Member

Problem

When the React Compiler hoists an object into a memo cache slot, a computed key like { [CSS_VAR]: x } keeps referencing the imported CSS_VAR. But convert_expression_to_property_key built every identifier key as a static property name, ignoring the computed flag. Semantic analysis then never linked the key to the import, so the TypeScript transform's import elision dropped the import — while codegen still emitted [CSS_VAR]. The result is output with a dangling reference (a runtime ReferenceError).

// input
import { iconSizeVar } from "./styles.css";
export function Box({ size }) {
  const style = { [iconSizeVar]: size + "px" };
  return <div style={style} />;
}
// before: the compiled body still reads `[iconSizeVar]`, but the import is gone ❌

Fix

Thread computed through convert_expression_to_property_key and build computed keys from the expression, so the identifier stays an IdentifierReference, semantic analysis links it to the binding, and import elision keeps the import.

How it was found / impact

By diffing oxc's React Compiler output against babel-plugin-react-compiler across the oxc-ecosystem-ci repos. 56 real-world files (in kibana, posthog, affine, …) emitted code referencing a dropped import — all UPPER_SNAKE config constants or vanilla-extract CSS vars used as dynamic keys. This fixes every one of them, with 0 regressions in the ~42k-file corpus.


🤖 Developed with Claude Code (AI-assisted).

Boshen commented Jun 18, 2026

Copy link
Copy Markdown
Member Author

How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • 0-merge - adds this PR to the back of the merge queue
  • hotfix - for urgent changes, fast-track this PR to the front of the merge queue

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions github-actions Bot added the A-transformer Area - Transformer / Transpiler label Jun 18, 2026
@Boshen Boshen added the 0-merge Merge with Graphite Merge Queue label Jun 18, 2026

Boshen commented Jun 18, 2026

Copy link
Copy Markdown
Member Author

Merge activity

…23586)

## Problem

When the React Compiler hoists an object into a memo cache slot, a computed key like `{ [CSS_VAR]: x }` keeps referencing the imported `CSS_VAR`. But `convert_expression_to_property_key` built **every** identifier key as a static property name, ignoring the `computed` flag. Semantic analysis then never linked the key to the import, so the TypeScript transform's import elision dropped the import — while codegen still emitted `[CSS_VAR]`. The result is output with a dangling reference (a runtime `ReferenceError`).

```tsx
// input
import { iconSizeVar } from "./styles.css";
export function Box({ size }) {
  const style = { [iconSizeVar]: size + "px" };
  return <div style={style} />;
}
// before: the compiled body still reads `[iconSizeVar]`, but the import is gone ❌
```

## Fix

Thread `computed` through `convert_expression_to_property_key` and build computed keys from the expression, so the identifier stays an `IdentifierReference`, semantic analysis links it to the binding, and import elision keeps the import.

## How it was found / impact

By diffing oxc's React Compiler output against `babel-plugin-react-compiler` across the `oxc-ecosystem-ci` repos. **56 real-world files** (in `kibana`, `posthog`, `affine`, …) emitted code referencing a dropped import — all UPPER_SNAKE config constants or vanilla-extract CSS vars used as dynamic keys. This fixes every one of them, with **0 regressions** in the ~42k-file corpus.

---
🤖 Developed with [Claude Code](https://claude.com/claude-code) (AI-assisted).
@graphite-app graphite-app Bot force-pushed the fix/react-compiler-computed-key-imports branch from 68a6d8b to 20375f9 Compare June 18, 2026 07:33
@codspeed-hq

codspeed-hq Bot commented Jun 18, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 57 untouched benchmarks
⏩ 14 skipped benchmarks1


Comparing fix/react-compiler-computed-key-imports (68a6d8b) with main (2bac45e)2

Open in CodSpeed

Footnotes

  1. 14 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (836d5b9) during the generation of this report, so 2bac45e was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@graphite-app graphite-app Bot merged commit 20375f9 into main Jun 18, 2026
29 checks passed
@graphite-app graphite-app Bot deleted the fix/react-compiler-computed-key-imports branch June 18, 2026 07:38
@graphite-app graphite-app Bot removed the 0-merge Merge with Graphite Merge Queue label Jun 18, 2026
Boshen added a commit that referenced this pull request Jun 18, 2026
### 💥 BREAKING CHANGES

- 7a76cd3 estree: [**BREAKING**] Make whether to include TS fields a
runtime option (#23574) (overlookmotel)
- e7b6b68 estree: [**BREAKING**] `ESTree` config use methods not consts
(#23573) (overlookmotel)

### 🚀 Features

- 556cc6d data_structures: Add `CodeBuffer::as_str` method (#23571)
(overlookmotel)
- 38c4b06 parser: Add friendly error for adjacent JSX elements (#23378)
(sapphi-red)
- 53509a8 minifier: Treeshake pure typed arrays and Set/Map array
literals (#23469) (Dunqing)
- 09762d9 minifier: Inline const value for read-only vars (#22593)
(Dunqing)

### 🐛 Bug Fixes

- 20375f9 react_compiler: Keep imports referenced only by a computed key
(#23586) (Boshen)
- 31bfd9b minifier: Keep Object introspection calls on a possible Proxy
(#23483) (Dunqing)
- 837a395 parser: Treat a line comment after ':' as leading, not
trailing (#23515) (Dunqing)
- e409fe0 minifier: Keep `new Map`/`WeakSet`/`WeakMap` with a string
argument (#23470) (Dunqing)
- ae02b4e ci/parser: Use `minimal` for vitest reporter (#23457)
(camc314)

### ⚡ Performance

- cf24329 mangler: Compile slot sort once instead of per CAPACITY
(#23577) (Boshen)
- 4058a6a parser: Reduce code bloat from verify_modifiers
monomorphization (#23576) (Boshen)
- 053b0c1 estree: Remove pointless `mem::take` (#23572) (overlookmotel)
- dfb52b6 transformer: Pre-size statement vecs in TS enum & namespace
lowering (#23516) (Yunfei He)
- 970e09a minifier: Compute template-literal inline checks in a single
pass (#23467) (Yunfei He)
- 3170c0e semantic,mangler,minifier: Fix `Semantic::stats` node count
and reuse stats in mangler builds (#23352) (Boshen)
- d1fa6e0 minifier: Evaluate ternary branches once in
minimize_conditional_expression (#23479) (Yunfei He)
- 3fa8051 transformer: Pre-size JSX props vec to attribute count
(#23466) (Yunfei He)
- 488b382 react_compiler: Borrow binding names in prefilter instead of
allocating (#23471) (Yunfei He)
- bcb3894 minifier: Incremental scoping refresh, delete
LiveUsageCollector (#23197) (Dunqing)

### 📚 Documentation

- f68641e data_structures: Improve docs on safety contract (#23575)
(overlookmotel)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
camc314 pushed a commit that referenced this pull request Jul 3, 2026
…23586)

## Problem

When the React Compiler hoists an object into a memo cache slot, a computed key like `{ [CSS_VAR]: x }` keeps referencing the imported `CSS_VAR`. But `convert_expression_to_property_key` built **every** identifier key as a static property name, ignoring the `computed` flag. Semantic analysis then never linked the key to the import, so the TypeScript transform's import elision dropped the import — while codegen still emitted `[CSS_VAR]`. The result is output with a dangling reference (a runtime `ReferenceError`).

```tsx
// input
import { iconSizeVar } from "./styles.css";
export function Box({ size }) {
  const style = { [iconSizeVar]: size + "px" };
  return <div style={style} />;
}
// before: the compiled body still reads `[iconSizeVar]`, but the import is gone ❌
```

## Fix

Thread `computed` through `convert_expression_to_property_key` and build computed keys from the expression, so the identifier stays an `IdentifierReference`, semantic analysis links it to the binding, and import elision keeps the import.

## How it was found / impact

By diffing oxc's React Compiler output against `babel-plugin-react-compiler` across the `oxc-ecosystem-ci` repos. **56 real-world files** (in `kibana`, `posthog`, `affine`, …) emitted code referencing a dropped import — all UPPER_SNAKE config constants or vanilla-extract CSS vars used as dynamic keys. This fixes every one of them, with **0 regressions** in the ~42k-file corpus.

---
🤖 Developed with [Claude Code](https://claude.com/claude-code) (AI-assisted).
camc314 pushed a commit that referenced this pull request Jul 3, 2026
### 💥 BREAKING CHANGES

- 7a76cd3 estree: [**BREAKING**] Make whether to include TS fields a
runtime option (#23574) (overlookmotel)
- e7b6b68 estree: [**BREAKING**] `ESTree` config use methods not consts
(#23573) (overlookmotel)

### 🚀 Features

- 556cc6d data_structures: Add `CodeBuffer::as_str` method (#23571)
(overlookmotel)
- 38c4b06 parser: Add friendly error for adjacent JSX elements (#23378)
(sapphi-red)
- 53509a8 minifier: Treeshake pure typed arrays and Set/Map array
literals (#23469) (Dunqing)
- 09762d9 minifier: Inline const value for read-only vars (#22593)
(Dunqing)

### 🐛 Bug Fixes

- 20375f9 react_compiler: Keep imports referenced only by a computed key
(#23586) (Boshen)
- 31bfd9b minifier: Keep Object introspection calls on a possible Proxy
(#23483) (Dunqing)
- 837a395 parser: Treat a line comment after ':' as leading, not
trailing (#23515) (Dunqing)
- e409fe0 minifier: Keep `new Map`/`WeakSet`/`WeakMap` with a string
argument (#23470) (Dunqing)
- ae02b4e ci/parser: Use `minimal` for vitest reporter (#23457)
(camc314)

### ⚡ Performance

- cf24329 mangler: Compile slot sort once instead of per CAPACITY
(#23577) (Boshen)
- 4058a6a parser: Reduce code bloat from verify_modifiers
monomorphization (#23576) (Boshen)
- 053b0c1 estree: Remove pointless `mem::take` (#23572) (overlookmotel)
- dfb52b6 transformer: Pre-size statement vecs in TS enum & namespace
lowering (#23516) (Yunfei He)
- 970e09a minifier: Compute template-literal inline checks in a single
pass (#23467) (Yunfei He)
- 3170c0e semantic,mangler,minifier: Fix `Semantic::stats` node count
and reuse stats in mangler builds (#23352) (Boshen)
- d1fa6e0 minifier: Evaluate ternary branches once in
minimize_conditional_expression (#23479) (Yunfei He)
- 3fa8051 transformer: Pre-size JSX props vec to attribute count
(#23466) (Yunfei He)
- 488b382 react_compiler: Borrow binding names in prefilter instead of
allocating (#23471) (Yunfei He)
- bcb3894 minifier: Incremental scoping refresh, delete
LiveUsageCollector (#23197) (Dunqing)

### 📚 Documentation

- f68641e data_structures: Improve docs on safety contract (#23575)
(overlookmotel)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-transformer Area - Transformer / Transpiler

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant