Skip to content

fix(react_compiler): codegen destructuring reassignment targets#24131

Merged
Boshen merged 1 commit into
mainfrom
fix/rc-scope-deps
Jul 4, 2026
Merged

fix(react_compiler): codegen destructuring reassignment targets#24131
Boshen merged 1 commit into
mainfrom
fix/rc-scope-deps

Conversation

@Boshen

@Boshen Boshen commented Jul 3, 2026

Copy link
Copy Markdown
Member

Root cause

The reactive-function backend (codegen_reactive_function.rs) only knew how to turn a plain BindingIdentifier lvalue into an AssignmentTarget. Any destructuring reassignment to an existing binding — [x] = arr, ({a} = obj), ({a} = {a:{}}), ([x] = [[]]) — hit an unported invariant_err("Destructuring reassignment targets are not yet ported to oxc").

codegen_function deliberately swallows any emission error and falls back to an empty body (a shim from the de-Babel batches). So every function containing such an assignment silently compiled to function f() {} — params and body both dropped — with no diagnostic. This is a hard miscompile, not a whitespace diff.

Fix

Port ox_binding_pattern_to_assignment_target to recursively reshape BindingPatternAssignmentTarget, since oxc (unlike Babel) models declaration patterns and assignment targets as distinct node families. Adds handling for:

  • array patterns → ArrayAssignmentTarget (elements + holes + rest)
  • object patterns → ObjectAssignmentTarget, distinguishing shorthand ({a} / {a = 1}AssignmentTargetPropertyIdentifier) from keyed ({a: b}AssignmentTargetPropertyProperty)
  • rest elements (...x)
  • nested patterns and defaults (AssignmentTargetWithDefault)

Mirrors Babel, which reuses the same pattern nodes as the assignment LHS.

Impact

23 corpus snapshots changed — all move toward the fork golden, none regress; 14 now match the fork exactly. Verified old-vs-new token distance to each fork golden.

Within this batch (reduce-reactive-deps / propagate-scope-deps-hir-fork):

  • ssa-renaming-via-destructuring-with-mutation — was empty function, now matches the fork exactly.
  • ssa-renaming-ternary-destruction / ssa-renaming-ternary-destruction-with-mutation — were empty functions, now compile with matching dependency sets, cache keys, scope boundaries, and dead-code elimination; the only residual is codegen-only (sequence-expression parenthesisation ([x]=[[]], …) vs (([x]=[[]]), …) and object-literal whitespace).

Other improved fixtures across the corpus (also previously empty-bodied): destructuring, destructuring-assignment, destructuring-assignment-array-default, destructure-direct-reassignment, destructure-in-branch-ssa, destructuring-object-pattern-within-rest, escape-analysis-destructured-rest-element, call-args-destructuring-assignment, array-pattern-spread-creates-array, the nonmutated-spread-* set, repro-reassign-props, switch-with-only-default, new-mutability__ssa-renaming-ternary-destruction.

Remaining work (out of scope here)

The rest of the batch's diffs are pure codegen and live in oxc_codegen, not the dependency passes:

  • sequence-expression parenthesisation of assignment operands ((a=[], b) vs ((a=[]), b))
  • JSX multi-line wrapping, TS type printing, object/array-literal whitespace

The two try-catch-* fixtures (stray ;) belong to a sibling fix/rc-try-catch branch and are left untouched.

@codspeed-hq

codspeed-hq Bot commented Jul 3, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 10 untouched benchmarks
⏩ 61 skipped benchmarks1


Comparing fix/rc-scope-deps (bb2a43c) with main (b82c394)

Open in CodSpeed

Footnotes

  1. 61 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.

The reactive-function codegen only knew how to turn a plain
`BindingIdentifier` lvalue into an `AssignmentTarget`; array/object
destructuring reassignments (`[x] = arr`, `({a} = obj)`) hit an
unported `invariant_err`. Because `codegen_function` swallows any
emission error and falls back to an empty body, every function
containing a destructuring assignment to an existing binding compiled
to `function f() {}` (params and body dropped) with no diagnostic.

Port `ox_binding_pattern_to_assignment_target` to recursively reshape
`BindingPattern` -> `AssignmentTarget`, handling array/object patterns,
rest elements, nested patterns, defaults, and shorthand vs keyed object
properties. Mirrors Babel, which reuses the pattern nodes as the
assignment LHS.

Fixes/improves 23 corpus snapshots (all move toward the fork, none
regress); 14 now match the fork golden exactly. The remaining residual
on the ternary-destruction fixtures is codegen-only (sequence-expression
parenthesisation and object-literal whitespace).
@Boshen
Boshen force-pushed the fix/rc-scope-deps branch from 7a84303 to bb2a43c Compare July 4, 2026 02:37
@Boshen
Boshen marked this pull request as ready for review July 4, 2026 02:43
@Boshen
Boshen merged commit cb2a505 into main Jul 4, 2026
29 checks passed
@Boshen
Boshen deleted the fix/rc-scope-deps branch July 4, 2026 02:43
Boshen added a commit that referenced this pull request Jul 6, 2026
### 🚀 Features

- 260425f semantic/examples: Include unresolved references (#24214)
(camc314)
- 2d9b0b3 minifier: Fold boolean-literal ternary branches in value
contexts (#24110) (Dunqing)
- 61fbf10 ast: Implement `ReplaceWith` on all AST types (#24013)
(overlookmotel)
- 7db7a29 allocator: Add `ReplaceWith` trait (#24012) (overlookmotel)
- 4eb074e mangler: Add `reserved` option for names that must not be
mangled (#24041) (Dunqing)
- 2e62012 data_structures: Add `StringExt` trait (#24006)
(overlookmotel)
- 60e7160 minifier: Drop side-effect-free IIFEs whose result is unused
(#23967) (Dunqing)
- 26dd9e2 ast: Add method to widen inherited enum ref to parent ref
(#23961) (overlookmotel)

### 🐛 Bug Fixes

- e8b50ee transformer: Clean up semantics for stripped TypeScript syntax
(#24180) (camc314)
- d966d0b react_compiler: Remove clippy allows (#24168) (Boshen)
- 854ef8d react_compiler: Compile generic functions instead of
over-bailing on type-param hoisting (#24158) (Boshen)
- 093586c react_compiler: Align memoization cache-slot allocation with
Babel (#24157) (Boshen)
- 09c8f59 react_compiler: Normalize snapshot fixture paths (#24142)
(camc314)
- f13df97 react_compiler: Drop stray empty statement from catch bindings
(#24133) (Boshen)
- cb2a505 react_compiler: Codegen destructuring reassignment targets
(#24131) (Boshen)
- b82c394 react_compiler: Propagate codegen invariants instead of
emitting empty bodies (#24128) (Boshen)
- 5771982 react_compiler: Render unchanged programs as source in fixture
snapshots (#24129) (Boshen)
- 4b16e1a transformer/async-to-generator: Preserve direct eval scope
flags (#24136) (camc314)
- 4e9194f react_compiler: Lower `delete obj.prop` to
Property/ComputedDelete (#24123) (Boshen)
- 0b25582 ast: Type binding node `typeAnnotation` as `TSTypeAnnotation |
null` (#23113) (Boshen)
- 018c0e5 transformer: Hoist lowered async declarations (#22770)
(camc314)
- 652fbaf mangler: Keep names of destructured exported bindings (#24036)
(Dunqing)
- e274415 minifier: Don't drop global calls that throw despite pure
arguments (#23917) (Dunqing)
- 59abb30 minifier: Only merge string literals in `try_fold_add` when
the inner operator is `+` (#23622) (Jerry Zhao)

### ⚡ Performance

- c5ca77b transformer: Avoid cloning refresh options (#24191) (camc314)
- bf1a151 react_compiler: Compile out debug printers (#24184) (Boshen)
- abb44a0 transformer: Build fixed object-rest arguments (#24190)
(camc314)
- a4db731 isolated_declarations: Use `ReplaceWith` instead of `TakeIn`
(#24016) (overlookmotel)
- ff10855 transformer: Use `ReplaceWith` instead of `TakeIn` (#24015)
(overlookmotel)
- bd49aff ecmascript: Avoid heap-allocating Math.min/max/imul operands
(#23941) (Lawrence Lin)
- e4b708b react_compiler: Skip compiled files before prefilters (#24171)
(Boshen)
- c59f2fe rust: Return impl ExactSizeIterator from slice-backed
accessors (#24144) (Boshen)
- 5d6d04a codegen: SWAR-skip boring byte runs in sourcemap line/column
scan (#24023) (Boshen)
- a55e0be traverse: Reduce string operations in `get_var_name_from_node`
(#24007) (overlookmotel)
- e6d48e1 transformer/nullish_coalescing: Move cold path into separate
function (#23989) (overlookmotel)
- c4e35b5 transformer/object_rest_spread: Pre-allocate capacity in `Vec`
(#23988) (overlookmotel)
- 527b8e5 transformer/decorators: Narrow type earlier (#23987)
(overlookmotel)

### 📚 Documentation

- 30d17f5 allocator: Clarify docs for `TakeIn::take_in_box` (#24093)
(overlookmotel)
- 675e6a8 ast: Correct doc comment for `PrivateFieldExpression` (#24008)
(overlookmotel)
- e4c30e6 minifier: Explain what `dce` mode means (#23994) (Dunqing)
- 37cbf88 ast_macros: Document fields of `StructDetails` (#23959)
(overlookmotel)
- 4de3e54 ast: Correct doc comment (#23948) (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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant