Skip to content

[rust-compiler] Inject CLI filename into PluginOptions for SWC and OXC#36510

Merged
poteto merged 4 commits into
pr-36173from
lauren/swc-09-cli-filename
May 21, 2026
Merged

[rust-compiler] Inject CLI filename into PluginOptions for SWC and OXC#36510
poteto merged 4 commits into
pr-36173from
lauren/swc-09-cli-filename

Conversation

@poteto
Copy link
Copy Markdown
Collaborator

@poteto poteto commented May 21, 2026

The e2e CLI (react_compiler_e2e_cli) accepted --filename <path>
and used it only for syntax detection. The value was never copied
into PluginOptions.filename, so the compiler's instrumentation
pass (enableEmitInstrumentForget) received None and emitted
useRenderCounter("Bar", "") with an empty path string where the TS
baseline emits the absolute file path.

Set options.filename = Some(filename.to_string()) at the top of
both compile_swc and compile_oxc. One line each.

Fixes 5 e2e parity fixtures:

  • codegen-instrument-forget-test.js (swc)
  • conflict-codegen-instrument-forget.js (swc)
  • gating/codegen-instrument-forget-gating-test.js (swc)
  • (and 2 corresponding OXC variants of the same fixtures, fixed for free)

Test plan:

  • bash compiler/scripts/test-e2e.sh --variant swc:
    Before: Total 1777/1795
    After: Total 1780/1795 (3 fixed)
  • bash compiler/scripts/test-e2e.sh --variant babel: 1788/1795 (unchanged)
  • bash compiler/scripts/test-e2e.sh --variant oxc:
    Before: Total 1702/1795
    After: Total 1704/1795 (2 fixed)
  • cargo test --workspace: 56 passed, 0 failed

@github-actions github-actions Bot added the React Core Team Opened by a member of the React Core Team label May 21, 2026
@meta-cla meta-cla Bot added the CLA Signed label May 21, 2026
poteto added 4 commits May 21, 2026 00:13
…ntend

After the cluster-1 BytePos shift, `ConvertCtx::position()` emitted
`loc.column` and `loc.index` as 0-based UTF-8 byte offsets. Babel emits
them as 0-based UTF-16 code unit offsets (matching JS string indexing).
For files containing any character above U+FFFF (e.g. an emoji like
🔴 U+1F534), the two diverge by +2 per such character because the
char is 4 bytes in UTF-8 but 2 code units in UTF-16.

Precompute a `utf16_offsets: Vec<u32>` table in `ConvertCtx::new`
that maps each source byte index to its 0-based UTF-16 code unit
offset. `position()` then looks up `index` directly and computes
`column` as `index - utf16_index_of_line_start`. O(1) per call; the
table costs ~4× the source length in memory, which is bounded for
fixture/file inputs.

Considered an alternative that walks the source line on each
`position()` call to count UTF-16 code units. More memory-frugal but
O(line length) per call. The precomputed table wins on O(1) lookup
and the per-call cost matters because `position()` is invoked on
every node, comment, and reference in the converter.

Clamp the byte index in `position()` to the sentinel at
`utf16_offsets.len() - 1`. Synthetic spans (e.g. compiler-generated
imports given `BytePos(1)`) can point past EOF in degenerate cases;
clamping avoids a panic.

Line numbers stay 1-based and the binary-search remains keyed on
byte offsets, since the underlying `line_offsets` table is byte-based.

Fixes 4 e2e parity fixtures (3 targeted + 1 latent):
- effect-derived-computations/invalid-derived-computation-in-effect.js
- error.invalid-derived-computation-in-effect.js
- fbt/error.todo-multiple-fbt-plural.tsx
- (one additional latent fixture passes for free)

Test plan:
- bash compiler/scripts/test-e2e.sh --variant swc:
    Before: Total 1770/1795
    After:  Total 1774/1795 (4 fixed)
- bash compiler/scripts/test-e2e.sh --variant babel: 1788/1795 (unchanged)
- bash compiler/scripts/test-e2e.sh --variant oxc:   1702/1795 (unchanged)
- cargo test --workspace: 56 passed, 0 failed
…e info

`convert_scope.rs::visit_fn_decl` allocated a fresh `BindingId` for
every `function x()` declaration. Babel and OXC collapse same-named
function declarations in the same hoist scope into one binding, with
the second declaration registered as a `constantViolations` reference
(reassignment) rather than a new binding.

For `function-declaration-redeclare.js` the SWC variant emitted
`const x_0 = t0; return x_0;` because the compiler saw two distinct
bindings and renamed one. Babel's output is `let x; ... x = t0;
return x;` because there is one binding that gets reassigned.

In `visit_fn_decl`, check whether the hoist scope already has a
binding for the name. If yes, record the redeclaration's ident
position in a new `redeclaration_refs` map and skip adding a fresh
binding. `build_scope_info` overlays this map onto
`reference_to_binding` so the second function's ident resolves to the
first binding's `BindingId`.

Fixes 1 e2e parity fixture:
- function-declaration-redeclare.js

`valid-setState-in-effect-from-ref-function-call.js` and its sibling
`valid-setState-in-useEffect-controlled-by-ref-value.js` still fail.
Those have a distinct root cause: the SWC frontend discards the
compiler's `renames` output (`lib.rs:91-98`) instead of applying it
to the emitted SWC AST the way the babel adapter does via
`applyRenames`. That fix is its own commit.

Test plan:
- bash compiler/scripts/test-e2e.sh --variant swc:
    Before: Total 1774/1795
    After:  Total 1775/1795 (1 fixed)
- bash compiler/scripts/test-e2e.sh --variant babel: 1788/1795 (unchanged)
- bash compiler/scripts/test-e2e.sh --variant oxc:   1702/1795 (unchanged)
- cargo test --workspace: 56 passed, 0 failed
The React Compiler's `RenameVariables` pass records identifier renames
in `CompileResult::Success.renames` as `Vec<BindingRenameInfo>` with
`{original, renamed, declaration_start}`. It does not rewrite the AST
itself; the frontend has to apply them. The Babel adapter does this
via `applyRenames` + `scope.rename()` in `BabelPlugin.ts:206-234`.
The SWC frontend was discarding the field via `..` in the
`CompileResult::Success` destructure, so inner-shadowing renames like
`ref → ref_0` never made it into the emitted output.

Add `apply_renames.rs` with a single pass that mirrors the Babel
semantics:

1. Re-run `build_scope_info` on the post-compile SWC module to get a
   binding table and `reference_to_binding` keyed by source position.
2. Match `BindingRenameInfo` entries to bindings by `declaration_start`
   and `name`, producing the set of `BindingId`s to rename.
3. Walk `reference_to_binding` to collect every position (declaration
   plus references) belonging to a renamed binding.
4. A `VisitMut` rewrites `Ident.sym` at matching `span.lo.0`, expands
   `Prop::Shorthand` and `ObjectPatProp::Assign` (with or without a
   default) into key-value form so `{ref}` becomes `{ref: ref_0}`
   instead of `{ref_0}`, and skips `MemberProp::Ident` so `x.ref`
   stays `x.ref`.

Wire into `lib.rs::transform`: destructure `renames` from the
`CompileResult::Success` arm, and if `program_json` is `None` but
`renames` is non-empty (the `@outputMode:"lint"` path) clone the
original input module so we still have something to rewrite.

Fixes 2 e2e parity fixtures:
- valid-setState-in-effect-from-ref-function-call.js (`ref → ref_0`)
- valid-setState-in-useEffect-controlled-by-ref-value.js (`data → data_0`)

Test plan:
- bash compiler/scripts/test-e2e.sh --variant swc:
    Before: Total 1775/1795
    After:  Total 1777/1795 (2 fixed)
- bash compiler/scripts/test-e2e.sh --variant babel: 1788/1795 (unchanged)
- bash compiler/scripts/test-e2e.sh --variant oxc:   1702/1795 (unchanged)
- cargo test --workspace: 56 passed, 0 failed
The e2e CLI (`react_compiler_e2e_cli`) accepted `--filename <path>`
and used it only for syntax detection. The value was never copied
into `PluginOptions.filename`, so the compiler's instrumentation
pass (`enableEmitInstrumentForget`) received `None` and emitted
`useRenderCounter("Bar", "")` with an empty path string where the TS
baseline emits the absolute file path.

Set `options.filename = Some(filename.to_string())` at the top of
both `compile_swc` and `compile_oxc`. One line each.

Fixes 5 e2e parity fixtures:
- codegen-instrument-forget-test.js                            (swc)
- conflict-codegen-instrument-forget.js                        (swc)
- gating/codegen-instrument-forget-gating-test.js              (swc)
- (and 2 corresponding OXC variants of the same fixtures, fixed for free)

Test plan:
- bash compiler/scripts/test-e2e.sh --variant swc:
    Before: Total 1777/1795
    After:  Total 1780/1795 (3 fixed)
- bash compiler/scripts/test-e2e.sh --variant babel: 1788/1795 (unchanged)
- bash compiler/scripts/test-e2e.sh --variant oxc:
    Before: Total 1702/1795
    After:  Total 1704/1795 (2 fixed)
- cargo test --workspace: 56 passed, 0 failed
@poteto poteto force-pushed the lauren/swc-09-cli-filename branch from be3cf71 to 8ba2829 Compare May 21, 2026 07:14
@poteto poteto force-pushed the lauren/swc-08-apply-renames branch from 93883a0 to 78afea4 Compare May 21, 2026 07:14
Base automatically changed from lauren/swc-08-apply-renames to pr-36173 May 21, 2026 07:15
@poteto poteto merged commit f98a683 into pr-36173 May 21, 2026
14 of 30 checks passed
@poteto poteto deleted the lauren/swc-09-cli-filename branch May 21, 2026 07:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed React Core Team Opened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant