Skip to content

react_compiler: fix panic on an object method shorthand in ssr output mode - #42375

Merged
Jarred-Sumner merged 3 commits into
mainfrom
robobun/eb1698fe/react-compiler-ssr-object-method
Sep 12, 2026
Merged

Jarred-Sumner merged 3 commits into
mainfrom
robobun/eb1698fe/react-compiler-ssr-object-method

Conversation

@robobun

@robobun robobun commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • bun build --react-compiler --target=bun aborts on a component that contains an object literal with a method shorthand, for example <div data-v={{ m() {} }} />: panic: Internal error: Expected all ObjectExpressions and ObjectMethods to have non-null scope. --target=node and reactCompilerOutputMode: "ssr" do the same.
  • pipeline.rs runs infer_reactive_scope_variables only if env.enable_memoization(), which is false in ssr mode. So no identifier gets a reactive scope. align_object_method_scopes.rs:52 runs in every mode and calls .expect() on the scope of each object method and its object literal.

Fix

  • With memoization disabled, find_scopes_to_merge skips a pair with no scope on either side. The sibling pass align_method_call_scopes already has this (None, None) arm.
  • Any other mismatch returns the invariant as an error. program.rs then leaves that one function uncompiled, like upstream's CompilerError.invariant.
  • Correct because without scopes no memo block can separate a method from its object.
  • Verified: test/bundler/transpiler/react-compiler.test.ts (new SsrObjectMethodShorthand, SIGABRT on 1.4.3). Also react-compiler-fixtures.test.ts, and all 1806 upstream fixtures with --target=bun: 5 abort before, 0 after.

Background

  • The React Compiler groups values that change together into reactive scopes and emits one memo block per scope.
  • The build target selects the output mode: browser is client (memoize), bun and node are ssr (no memoization, useState inlined).
  • A method shorthand is a separate instruction from its object literal. Codegen prints it inline, so this pass merges the two scopes.
  • Upstream main has the same invariant in TS and Rust. In TS it is a caught per-function error. Bun builds with panic = "abort".
Notes

Repro (any directory, 1.4.2 and main 4b5862f):

// a.jsx
export function App() {
  return <div data-v={{ m() {} }} />;
}
$ bun build --react-compiler --target=bun a.jsx --external react --external 'react/*'
panic: Internal error: Expected all ObjectExpressions and ObjectMethods to have non-null scope.
Crashed while visiting a.jsx

Frames: find_scopes_to_merge (src/react_compiler/inference/align_object_method_scopes.rs:52), align_object_method_scopes (:99), pipeline::run_hir_passes (pipeline.rs:564), compile_fn (:172), program::maybe_compile_node (program.rs:1363).

Why the guard is on enable_memoization() and the arm is not unconditional: with memoization enabled, infer_reactive_scope_variables gives every ObjectMethod and ObjectExpression a scope (may_allocate is true for both), so a missing scope there is a real invariant violation. That case keeps upstream's behavior, as an error and not as a panic. Inner functions always have scopes, in every mode: analyse_functions runs infer_reactive_scope_variables on them without the gate.

Alternative not taken: gate the two alignment passes in pipeline.rs on enable_memoization(). The header of pipeline.rs says the pass sequence and the gating predicates stay identical with upstream, and the in-pass arm mirrors the sibling pass.

What the test checks: it bundles with --target=bun through the CLI and runs the output. The fake useState never returns its argument. The ssr pass inlines useState to its initial value, so each function prints that value only if the compiler compiled it. Shapes covered: the object literal as a JSX attribute value, const v = { ... } in the body with plain, async, computed-key and nested methods, and a hook that returns an object with a method. A generator method is not in the test: upstream does not lower yield, so that function is skipped in every mode.

Sweep: built each of the 1806 files under test/bundler/transpiler/react-compiler-fixtures/ with bun build --react-compiler --target=bun --external '*'. On 1.4.3 canary five abort with this panic (infer-nested-object-method.jsx, object-method-shorthand-hook-dep.js, object-shorthand-method-nested.js, infer-object-method-uncond-access.tsx, infer-objectmethod-cond-access.js). With this change none abort, and the output of those five keeps each method inline in its object.

Codegen does not depend on the scopes here: it stores each ObjectMethod in a map that lives for the whole function (cx.object_methods, codegen.rs:1418) and reads it back at the ObjectExpression. A missing entry there is already an error return.

Upstream: AlignObjectMethodScopes.ts and react_compiler_inference/src/align_object_method_scopes.rs on facebook/react main both still have the invariant, and Pipeline.ts gates inferReactiveScopeVariables on env.enableMemoization the same way.

Suites run with the debug build: test/bundler/transpiler/react-compiler.test.ts (46 pass), test/bundler/transpiler/react-compiler-fixtures.test.ts (3293 pass, 320 skip). cargo clippy -p bun_react_compiler is clean.


[human-review] gate passed · iteration 1 · 3 files touched

fails on main (without fix)
ASAN without fix: 1 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bundler/transpiler/react-compiler.test.ts
bun test v1.4.3 (6a92015fc)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [689.67ms]
(pass) bundler > react-compiler/ComponentWithHooks [309.24ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [300.71ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [306.43ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [133.03ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [90.31ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [304.08ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [118.98ms]
1031 |       ]);
1032 |       const stdout = Buffer.from(stdoutBytes);
1033 |       const stderr = Buffer.from(stderrBytes);
1034 |       const success = exitCode === 0;
1035 |       if (buildProc.signalCode) {
1036 |         throw new Error(
                         ^
error: [react-compiler/SsrObjectMethodShorthand] 'bun build' subproc
... (truncated)

release without fix: 1 skipped
bun test v1.4.3-canary.1 (7af2e7c7e)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [14.70ms]
(pass) bundler > react-compiler/ComponentWithHooks [5.41ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [5.75ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [5.21ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [4.93ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [3.04ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [7.41ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [3.31ms]
(pass) bundler > react-compiler/SsrObjectMethodShorthand [11.42ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Client [3.83ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Ssr [2.93ms]
(pass) bundler > react-compiler/BundledReactPreservesImportRefs [6.28ms]
(pass) bundler > react-compiler/BundledCjsCompilerRuntimeSurvivesTreeShaking [8.26ms]
(pass) bundler > react-compiler/RequireStringPreservesImportRecord [5.08ms]
(pass) bundler > react-compiler/BranchBooleanFeatur
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bundler/transpiler/react-compiler.test.ts
bun test v1.4.3 (6a92015fc)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [704.04ms]
(pass) bundler > react-compiler/ComponentWithHooks [235.64ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [220.78ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [249.55ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [124.66ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [92.83ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [409.55ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [125.37ms]
(pass) bundler > react-compiler/SsrObjectMethodShorthand [605.33ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Client [104.66ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Ssr [98.85ms]
(pass) bundler > react-compiler/BundledReactPreservesImportRefs [260.57ms]
(pass) bundler > react-
... (truncated)

release with fix: 1 skipped
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 621ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[0/4] cargo bun_runtime → libbun_runtime.a
�[1m�[92m   Compiling�[0m bun_react_compiler v0.0.0 (/workspace/bun/src/react_compiler)
�[1m�[92m   Compiling�[0m bun_js_parser v0.0.0 (/workspace/bun/src/js_parser)
�[1m�[92m   Compiling�[0m bun_resolver v0.0.0 (/workspace/bun/src/resolver)
�[1m�[92m   Compiling�[0m bun_ini v0.0.0 (/workspace/bun/src/ini)
�[1m�[92m   Compiling�[0m bun_bundler v0.0.0 (/workspace/bun/src/bundler)
�[1m�[92m   Compiling�[0m bun_router v0.0.0 (/workspace/bun/src/router)
�[1m�[92m   Compiling�[0m bun_standalone_graph v0.0.0 (/workspace/bun/src/standalone_graph)
�[1m�[92m   Compiling�[0m bun_transpiler v0.0.0 (/workspace/bun/src/transpiler)
�[1m�[92m   Compiling�[0m bun_bunfig v0.0.0 (/workspace/bun/src/bunfig)
�[1m�[92m   Compiling�[0m bun_install v0.0.0 (/workspace/bun/src/install)
�[1m�[92m   Compiling�[0m bun_jsc v0.0.0 (/workspace/bun/src/jsc)
�[1m�[92m   Compiling�[0m bun_js_parser_jsc v0.0.0 (/workspace/bun/src/js_parser_jsc)
�[1m�[92m   Compiling�[0m bun_bundler_jsc v0.0.0 (/w
... (truncated)
diff hotspot
.../inference/align_object_method_scopes.rs        | 43 +++++++++++++------
 src/react_compiler/pipeline.rs                     |  2 +-
 test/bundler/transpiler/react-compiler.test.ts     | 50 ++++++++++++++++++++++
 3 files changed, 81 insertions(+), 14 deletions(-)

gate history · 2 passed · 0 rejected · iteration 1

evidence per changed file
file                                                      reads  edits  tests
…/react_compiler/inference/align_object_method_scopes.rs      4      7     10
src/react_compiler/pipeline.rs                                1      1     10
test/bundler/transpiler/react-compiler.test.ts                4      1     10

… mode

In ssr mode the pipeline skips InferReactiveScopeVariables for the compiled
function, so no identifier has a reactive scope. AlignObjectMethodScopes ran
anyway and called expect() on the scope of every object method and its object
literal, which aborted the process.

Treat a pair with no scope on either side as nothing to align when
memoization is disabled, like AlignMethodCallScopes does. Return any other
mismatch as an invariant error, so the function is left uncompiled.
@robobun

robobun commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: ready for review. CI passes.

Reproduction: on 1.4.3 canary (6a92015) and on main 4b5862f, this aborts with exit 134:

// a.jsx
export function App() {
  return <div data-v={{ m() {} }} />;
}
$ bun build --react-compiler --target=bun a.jsx --external react --external 'react/*'
panic: Internal error: Expected all ObjectExpressions and ObjectMethods to have non-null scope.
Crashed while visiting a.jsx

With this branch the same command exits 0 and prints the component with m() {} inline in the object. USE_SYSTEM_BUN=1 bun test test/bundler/transpiler/react-compiler.test.ts -t SsrObjectMethodShorthand fails with SIGABRT. bun bd test on the same file passes (46 tests).

@coderabbitai

coderabbitai Bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 07b1820c-40ba-4a91-89a5-00d445b30aa4

📥 Commits

Reviewing files that changed from the base of the PR and between 6034585 and c8872c3.

📒 Files selected for processing (1)
  • src/react_compiler/inference/align_object_method_scopes.rs

Included review availability: Your plan provides up to 10 included reviews per hour; 4 remain after this review.


Walkthrough

The compiler now returns diagnostics for incomplete object-method scope pairs instead of panicking. The HIR pipeline propagates alignment errors. A new SSR regression test covers compiled object-method shorthand functions.

Changes

Object-method scope alignment

Layer / File(s) Summary
Fallible scope discovery and recursive alignment
src/react_compiler/inference/align_object_method_scopes.rs
Scope discovery handles missing scopes explicitly. Recursive alignment and the public entry point now return and propagate CompilerDiagnostic values.
Pipeline propagation and SSR regression coverage
src/react_compiler/pipeline.rs, test/bundler/transpiler/react-compiler.test.ts
The HIR pass returns alignment errors to its caller. SSR coverage validates object-method shorthand functions in multiple object and JSX forms.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to c8872

The change addresses the reported SSR compiler panic, with no unresolved merge-blocking risk identified.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely identifies the main change: fixing a React Compiler panic for object method shorthand in SSR output mode.
Description check ✅ Passed The description explains the problem, root cause, fix, scope, reproduction, and verification results. Although it does not use the exact template headings, it provides the required information in equi…

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — focused panic→Result conversion with a narrowly-gated no-op arm, mirrors the sibling pass, well-tested.

What was reviewed:

  • Confirmed the (None, None) arm mirrors align_method_call_scopes.rs:55 and is gated on !enable_memoization() so the invariant still fires when scopes are expected.
  • Checked that ? on the recursive inner-function call leaving the placeholder in env.functions on error matches the existing pattern in analyse_functions.rs:68 and infer_types.rs (env is abandoned on error).
  • Verified run_hir_passes already returns Result so the new ? in pipeline.rs composes; no CODEOWNERS coverage for these paths.
  • Test uses a sentinel useState so it fails both on the old panic and if any shape gets skipped instead of compiled; covers JSX-attr, body, async, computed-key, nested, and hook variants.
Extended reasoning...

Overview

This PR fixes a user-reachable panic in Bun's React Compiler port. When bundling with --react-compiler --target=bun (SSR mode, memoization disabled), a component containing an object-method shorthand ({ m() {} }) hit .expect() on a None reactive scope in align_object_method_scopes.rs and aborted the process. The fix converts find_scopes_to_merge and align_object_method_scopes to return Result<_, CompilerDiagnostic>, adds a (None, None) if !env.enable_memoization() arm that treats the pair as nothing-to-align, replaces the remaining panic with cold_invariant(...), threads ? through the recursive walk and the pipeline.rs call site, and adds an itBundled regression test covering six shapes of object-method shorthand under target: "bun".

Security risks

None. This is an internal compiler pass over already-lowered HIR; the change replaces a process-aborting panic with a per-function recoverable diagnostic. No parsing of untrusted external data, no I/O, no auth/crypto surface.

Level of scrutiny

Low-to-medium. The change is small (~30 Rust lines) and mechanical: a panic-to-Result conversion plus one match arm. The (None, None) no-op arm is a strictly more conservative variant of the unconditional (None, None) arm already present in the sibling pass align_method_call_scopes.rs, and it is gated on the exact predicate (!env.enable_memoization()) that causes InferReactiveScopeVariables to be skipped in pipeline.rs. The deliberate upstream divergence is commented inline as REVIEW.md requires for ported code. I checked whether the new ? on the recursive inner-function call could leave env.functions[func_id] holding a placeholder on the error path — it can, but this matches the established pattern at analyse_functions.rs:68 and infer_types.rs:486/498, where the whole per-function Environment is abandoned once a pass returns Err.

Other factors

The test is added to the existing react-compiler.test.ts (correct location per CLAUDE.md), uses the itBundled harness, and is designed to fail for the right reason: the fake useState returns "not inlined", so the expected stdout is only produced if the SSR pass actually inlined useState in every tested function — catching both the original SIGABRT and any regression where a shape gets silently skipped instead of compiled. The variant matrix (JSX-attr value, body const, async, computed-key, nested, hook) satisfies the "cover the class, not the repro" rule. No CODEOWNERS entry covers src/react_compiler/ or test/bundler/transpiler/. No prior review activity on the PR. Bug-hunt exit reason was dry_streak with zero findings.

@robobun

robobun commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 3:19 PM PT - Sep 11th, 2026

✅ @robobun, your commit c8872c3e387900abd4e44b4db154c86cec7f9ccf passed in Build #114442! 🎉


🧪   To try this PR locally:

bunx bun-pr 42375

That installs a local version of the PR into your bun-42375 executable, so you can run:

bun-42375 --bun

Comment thread src/react_compiler/inference/align_object_method_scopes.rs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the follow-up commit tightening the comment looks good.

What was reviewed:

  • The (None, None) if !env.enable_memoization() arm mirrors the existing unconditional (None, None) arm in align_method_call_scopes.rs, and the fallthrough keeps upstream's invariant as a recoverable cold_invariant diagnostic instead of a process abort.
  • The ? on the recursive inner-function call sits between the mem::replace swap-out and swap-back, but this is the exact pattern used by enter_ssa.rs, analyse_functions.rs, and infer_types.rs; on error the whole env is discarded by program.rs, so the placeholder left in env.functions is never observed.
  • The new itBundled case is hermetic (local stub react package, no network), lands in the existing test file, and its sentinel useState proves each function was actually compiled rather than skipped — it would print "not inlined" on a bail-out.
Extended reasoning...

Overview

This PR converts a user-reachable .expect() panic in the React compiler's AlignObjectMethodScopes HIR pass into a recoverable CompilerDiagnostic. In SSR output mode (--target=bun/--target=node), infer_reactive_scope_variables is gated on enable_memoization() and never runs, so object methods and their enclosing object expressions reach this pass with no reactive scope. The old code called .expect() on both scopes and aborted the whole build. The fix adds a match arm that skips the merge when both scopes are None and memoization is disabled, and returns a cold_invariant error (propagated via ? up through pipeline.rs) for any other mismatch. Three files touched: the pass itself, its one call site in pipeline.rs, and a new itBundled test case.

Security risks

None. This is an internal compiler pass over already-parsed HIR; no new inputs are trusted, no I/O, no allocation or bounds arithmetic changes. The change strictly reduces attack surface by removing a user-triggerable panic = "abort" DoS on valid JSX input.

Level of scrutiny

Low-to-medium. The diff is ~50 lines, mechanical, and follows established in-crate patterns exactly: the sibling pass align_method_call_scopes.rs already has a (None, None) => {} arm, and the mem::replace/?/swap-back sequence for inner-function recursion is identical to enter_ssa.rs:447-449, analyse_functions.rs:63-68, and infer_types.rs:473. REVIEW.md explicitly requires that user-reachable failures be recoverable errors rather than panics, so this is a mandated fix. The PR's gate evidence shows the new test SIGABRTs on the base build and passes with the fix, and the 1806-fixture sweep went from 5 aborts to 0.

Other factors

The test is well-constructed per test/CLAUDE.md: added to the existing react-compiler.test.ts, uses the itBundled harness, vendors a local stub react module (no external network), and asserts exact stdout. The stub useState returns a sentinel that never matches the expected output, so the assertion only passes if the SSR pass actually inlined useState — proving the compiler ran on each of the three shapes (JSX attribute, body with async/computed/nested methods, custom hook) rather than bailing out. Since the prior COMMENTED review, one substantive commit landed (shortening the in-source comment on the no-scope arm); the current comments are terse and load-bearing. No CODEOWNERS entry covers src/react_compiler/, and there are no outstanding CHANGES_REQUESTED reviews from human reviewers.

@Jarred-Sumner
Jarred-Sumner merged commit 7a7cc96 into main Sep 12, 2026
11 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the robobun/eb1698fe/react-compiler-ssr-object-method branch September 12, 2026 04:38
Jarred-Sumner pushed a commit that referenced this pull request Sep 14, 2026
…ight-hand side (#42386)

### Problem

- With `bun build --react-compiler`, `tail.next = tail = node` (list
append) throws `TypeError: undefined is not an object (evaluating
'tail.next = tail = {...}')`: the output declares `let tail;` without `=
head`. `v.z = v = 80` prints as `80 .z = v = 80`. `arr[i] = i++` stores
at the wrong index. Both output modes, 1.4.2 and main.
- `lower_simple_assignment`
(`src/react_compiler/lowering/build_hir/expr.rs:671`) lowers the
right-hand side before the object and key of a member target. JS
evaluates the object and key first. So the target reads the value that
the right-hand side assigned, and the old value has no reader left.
Upstream has the same order.

### Fix

- Lower the object, then a computed key, then the right-hand side, then
the store. `a.b += c` and `a.b++` already lower in this order.
- Correct because codegen prints `object[key] = value`, which JS
evaluates in this order.
- The upstream fixtures (1,587 in client mode, 1,734 in ssr mode) print
the same text before and after.
- Verified: `test/bundler/transpiler/react-compiler.test.ts` (2 new
tests, 9 forms each, 8 fail on 1.4.3-canary). Also
`react-compiler-fixtures.test.ts`, `bundler_jsx.test.ts`.

### Background

- The compiler lowers a function to HIR: instructions in evaluation
order, each result in a temporary. `a.b = c` becomes a load of `a`, the
instructions of `c`, and a `PropertyStore`.
- SSA, constant propagation and dead code elimination decide from that
order which assignment a read sees.
- Codegen puts each temporary back where it is used, so the output has
the shape of the source. A wrong order shows only when a pass acted on
it.

<details><summary>Notes</summary>

**Upstream.** babel-plugin-react-compiler 1.0.0 prints the same `let
tail;` for the first form. The same order is in
`compiler/crates/react_compiler_lowering/src/build_hir.rs` at the ported
commit (560db514, "Member expression assignment" arm) and on
facebook/react main (019019be). It is also in `BuildHIR.ts`: the
`AssignmentExpression` case passes `lowerExpressionToTemporary(builder,
expr.get('right'))` as an argument to `lowerAssignment`, and the
`MemberExpression` case of `lowerAssignment` lowers the object after
that. The new comment in `lower_simple_assignment` records that the port
differs here on purpose.

**Forms.** 44 forms. Each was built without the compiler, in ssr mode
(`--target=bun`) and in client mode (`--target=browser`). Each output
then rendered five times with a memo cache that persists between
renders: the same props twice, two other prop sets, then the first props
again. The compiled outputs must print what the uncompiled output
prints. On 1.4.3-canary (6a92015) 17 forms differ. On this branch none
differ, and client mode memoizes 42 of the 44 (the other two break a
rule of React on purpose and are left uncompiled on both builds). Canary
is two commits behind main and neither commit touches the lowering. A
debug build of main (4b5862f) fails the same way on the 31 forms I
also ran there.

| form | expected | 1.4.3-canary |
| --- | --- | --- |
| `tail.next = tail = {...}` in `for of`, `for`, `while`, and in the
update clause of a `for` | `0,1,2,3` | `TypeError: undefined is not an
object` |
| the same twice with no loop | the list `0,5,1` | the same TypeError |
| `tail[p.key] = tail = {...}` and `tail[0] = tail = {...}` | `0,1,2,3`
| the same TypeError |
| `tail.next = tail = p.make(x)` | `0,1,2,3` | the same TypeError |
| the same inside `try`/`catch` | `0,1,2,3` | the `catch` branch renders
|
| the `for of` form inside an arrow function | `0,1,2,3` | the same
TypeError |
| `box.child = box = { el: <span/> }`, `box.fn = box = { get: () => p.a
}` | the old `box` gets the key | the same TypeError |
| `v.z = v = 80` in an `if`, in a loop, at the top level |
`[{"z":80},80]` | `TypeError: Attempted to assign to readonly property.`
|
| `o[key] = key = "b"` with `let key = "a"` | `[{"a":"b"},"b"]` |
`[{"b":"b"},"b"]` |
| `arr[i] = i++; arr[i] = i++; arr[i++] = i` with `let i = 0` |
`[[0,1,3],3]` | `[[null,0,2],3]` |
| `let a = {name: "a"}; if (p.flag) a.other = a = b;` | with `flag`
false: `{"name":"a"}` | client mode, after a render with `flag` true:
`{"name":"a","other":{"name":"b"}}` |

The last row is the second effect in the report. The compiler saw no
write to the first object, cached it behind a sentinel check, and the
store then changed the cached object for every later render.

27 forms already matched on canary and still match: `v.z = v = p.a` and
`v.z = v = {...}` at the top level, the `arr[i] = i++` forms inside a
loop (no constant to propagate), `arr[0] = arr = [...]`, `a.b.c = a =
{...}`, `o.x = o.y = o = {...}`, a sequence, conditional, logical,
nullish or optional chain on the right, `(a = {...}).x = a.name`, `tail
= tail.next = {...}`, a call as the object and as the key (the calls run
in source order), `useState()` on the right, a local that a closure
captures, `reduce((tail, x) => (tail.next = tail = {...}), head)`, `v.z
+= (v = {...}).z`, `seen.push(v, (v = {...}))`, and destructuring
targets (`[a.x, a] = ...`, `[a, a.y] = ...`).

**Why many forms already worked.** With the instructions in the wrong
order the printed text is still `tail.next = tail = {...}`, in source
order, because codegen puts the temporaries back at their place of use.
The output is wrong only when a pass used the order: dead code
elimination (the removed `= head`), constant propagation (`80 .z`,
`o["b"]`, `arr[1] = i++`), or the mutation analysis (the last row of the
table).

**One form now compiles.** At the top level of a component, `let v = {};
const o = v; v.z = v = p.a;` was left uncompiled in client mode. With
the wrong order the store looked like a write to the prop `p.a`, which
the compiler rejects. Now the store goes to the local object and the
component is memoized.

**Other assignment lowerings.** A member target inside a destructuring
pattern (`[a.b] = c`) or in `for (a.b of c)` goes through
`lower_member_store` with a value that already exists. JS also produces
that value before it evaluates the target, so those stay as they are.
Calls, compound assignment and update expressions lower their operands
in source order.

**Fixture corpus.** I compiled every upstream fixture that builds (1,587
in one `Bun.build` call in client mode, 1,734 with one `bun build` per
file in ssr mode) with a debug build of main and with a debug build of
this branch, and compared the output files. No file differs. So the memo
slot counts and scope boundaries of the corpus are unchanged. Five
fixtures panic in ssr mode on both builds (`Expected all
ObjectExpressions and ObjectMethods to have non-null scope`), which
#42375 fixes.

</details>

<!-- robobun:evidence:begin -->

---

**[human-review]** gate passed · iteration 0 · 2 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: 2 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bundler/transpiler/react-compiler.test.ts
bun test v1.4.3 (6a92015)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [949.44ms]
(pass) bundler > react-compiler/ComponentWithHooks [341.77ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [454.80ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [526.88ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [178.43ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [156.04ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [548.32ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [156.65ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Client [226.14ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Ssr [156.10ms]
(pass) bundler > react-compiler/BundledReactPreservesImportRefs [384.19ms]
(pass) bundler > react-compiler/BundledCjsCompilerRuntimeSurvivesTreeShaking [742.38ms]
(
... (truncated)

release without fix: 11 failed, 1 skipped
bun test v1.4.3-canary.1 (c800ac8)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [37.06ms]
(pass) bundler > react-compiler/ComponentWithHooks [13.21ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [14.65ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [15.81ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [10.81ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [9.10ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [29.97ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [11.46ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Client [26.57ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Ssr [12.37ms]
(pass) bundler > react-compiler/BundledReactPreservesImportRefs [14.27ms]
(pass) bundler > react-compiler/BundledCjsCompilerRuntimeSurvivesTreeShaking [30.20ms]
(pass) bundler > react-compiler/RequireStringPreservesImportRecord [18.43ms]
(pass) bundler > react-compiler/BranchBooleanFeatureFlagPreservesDCE [10.10ms]
(pass) bundler > react-compi
... (truncated)
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bundler/transpiler/react-compiler.test.ts
bun test v1.4.3 (6a92015)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [1041.63ms]
(pass) bundler > react-compiler/ComponentWithHooks [316.85ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [368.95ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [367.44ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [193.38ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [152.07ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [520.79ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [146.70ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Client [148.27ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Ssr [137.77ms]
(pass) bundler > react-compiler/BundledReactPreservesImportRefs [485.46ms]
(pass) bundler > react-compiler/BundledCjsCompilerRuntimeSurvivesTreeShaking [656.34ms]

... (truncated)

release with fix: 1 skipped
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 815ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[0/5] cargo bun_runtime → libbun_runtime.a
�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m   Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1m�[92m   Compiling�[0m bun_base64 v0.0.0 (/workspace/bun/src/base64)
�[1m�[92m   Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys)
�[1m�[92m   Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys)
�[1m�[92m   Compiling�[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd)
�[1m�[92m   Compiling�[0m bun_picohttp v0.0.0 (/workspace/bun/src/picohttp)
�[1m�[92m   Compiling�[0m bun_brotli v0.0.0 (/workspace/bun/src/brotli)
�[1m�[92m   Compiling�[0m bun_output v0.0.0 (/workspace/bun/src/output)
�[1m�[92m   Compiling�[0m bun_clap v0.0.0 (/workspace/bun/src/clap)
�[1m�[92m   Compiling�[0m bu
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
src/react_compiler/lowering/build_hir/expr.rs  |   6 +-
 test/bundler/transpiler/react-compiler.test.ts | 136 +++++++++++++++++++++++++
 2 files changed, 140 insertions(+), 2 deletions(-)
```

</details>

**gate history** · 2 passed · 0 rejected · iteration 0

<details><summary>evidence per changed file</summary>

```
file                                            reads  edits  tests
src/react_compiler/lowering/build_hir/expr.rs       3      2     20
test/bundler/transpiler/react-compiler.test.ts      3      1     20
```

</details>

<!-- robobun:evidence:end -->
Jarred-Sumner pushed a commit that referenced this pull request Sep 15, 2026
)

### Problem
- `bun build --react-compiler --target=browser` aborts on `const r = (m
= p.f()) ? 1 : 0; return <div data-v={[m, r]} />`: `panic: Expected a
node for all scopes`. `Bun.build` aborts the calling process the same
way.
- `CollectDependenciesVisitor` skips the test of a `?:`, so the reactive
scope that reassigns `m` gets no scope node. `visit_scope` still
attaches that scope to `m`. When `m` is force-memoized,
`force_memoize_scope_dependencies`
(`src/react_compiler/reactive_scopes/prune_non_escaping_scopes.rs:1170`)
calls `.expect()` on the missing node.

### Fix
- That invariant is now a `cold_invariant` error. `program.rs` leaves
that one function uncompiled, like upstream's `CompilerError.invariant`.
- The three `Expected identifier to be initialized` invariants in the
same pass get the same treatment. Their visitor callbacks return `()`,
so they record the first error in `CollectState`, as
`assert_scope_instructions_within_scopes` does.
- Correct because babel-plugin-react-compiler 1.0.0 raises the same
invariant on the same inputs and skips the same functions. Compiled
output does not change.
- Verified: `test/bundler/transpiler/react-compiler.test.ts` (new
`AssignmentInConditionalTestSkipsOnlyThatFunction`, SIGABRT on 1.4.3
canary). Also `react-compiler-fixtures.test.ts`.

### Background
- The React Compiler groups values that change together into reactive
scopes and emits one memo block per scope. PruneNonEscapingScopes
removes the scopes whose values never leave the function.
- The pass builds a graph of identifier nodes and scope nodes, then
walks it from the returned values.
- Upstream's `CompilerError.invariant` is a caught per-function error in
TS. Bun builds with `panic = "abort"`, so a ported `.expect()` ends the
process.

<details><summary>Notes</summary>

Repro (any directory, 1.4.2, 1.4.3 canary 6a92015 and main 4b5862f):

```jsx
// a.jsx
export default function App(p) {
  let m;
  const r = (m = p.f()) ? 1 : 0;
  return <div data-v={[m, r]} />;
}
```

```
$ bun build --react-compiler --target=browser --external react a.jsx
panic: Expected a node for all scopes
Crashed while visiting a.jsx
```

Frames: `force_memoize_scope_dependencies`
(`prune_non_escaping_scopes.rs:1170`),
`compute_memoized_identifiers::visit` (`:1156`),
`prune_non_escaping_scopes` (`:70`), `pipeline::run_hir_passes`
(`pipeline.rs:622`), `compile_fn` (`:172`).

The smallest shape: an assignment whose value allocates (a call, an
array or an object literal) in the test of a `?:`, and the assigned
local as an operand of a memoized value (`[m]`, `{ m }`). The result of
the conditional does not need to reach that value. `m = p.a` compiles.
`&&`, `??`, `?.` or an `if` statement in place of `?:` compile. `<div
data-m={m} />` compiles because nothing force-memoizes `m`.
`--target=bun` and `--target=node` (ssr mode) create no reactive scopes
and compile.

A second shape, with no assignment expression (the input of
react/react#37228): `return (() => { try { return check(raw) ? raw :
null; } catch { return null; } })();`. 1.4.2 aborts with the same panic
and this branch skips the function with the same invariant.
babel-plugin-react-compiler 1.0.0 skips it one step earlier, with `Todo:
Support value blocks (conditional, logical, optional chaining, etc)
within a try/catch statement`.

Two loop shapes with no `?:` at all, where `identity` is an imported
function: `let r; do { r = identity(value); } while (cond()); return r;`
and `let r; while (cond()) { r = identity(value); break; } return r;`.
1.4.2 aborts on both with the same panic, this branch skips the
function, and babel-plugin-react-compiler 1.0.0 raises the same
invariant and leaves the function as written. With `String(value)` in
place of `identity(value)` nothing aborts. I ran both by hand on this
branch. They are not in the test.

Why the node is missing. `compute_memoization_inputs` returns the
rvalues of the consequent and the alternate of a `ConditionalExpression`
only (upstream: "Conditionals do not alias their test value"), and
`visit_instruction` does not traverse nested values on its own. So
nothing calls `visit_operand` for `t = p.f()` or `m = t`, and
`visit_operand` is the only place that creates a scope node. The scope
is aligned to the whole `const r = ...` statement and lists `m` in
`reassignments`, so `visit_scope` adds it to the node of `m`. The array
`[m]` is `Memoized`, its scope depends on `m`, `m` is `Unmemoized` (from
`let m;`) and forced, and the walk reaches the scope with no node. `(m =
p.f()) ? p.a() : p.b()` compiles because the calls in the branches are
in the same scope and create the node.

Upstream. `PruneNonEscapingScopes.ts` on facebook/react main still has
all four invariants, and
`react_compiler_reactive_scopes/src/prune_non_escaping_scopes.rs` there
has the same four `.expect()` calls. I ran babel-plugin-react-compiler
1.0.0 on 20 variants of the input. It logs `CompileError: Expected a
node for all scopes` and leaves the function as written for exactly the
10 variants that abort 1.4.3 canary. The two agree on the other 10 as
well: 8 compile with one memo cache, 1 compiles with none, and 1 fails
in both with `Unexpected StoreLocal in codegenInstructionValue`, which
Bun already returns as an error. With this change Bun skips the same 10
functions.

Alternative not taken: create the scope node on demand in
`force_memoize_scope_dependencies` from `env.scopes[id].dependencies`,
which is all `visit_operand` stores. That compiles these functions, but
upstream does not compile them, and nothing in that test value has been
analysed by this pass.

The three `Expected identifier to be initialized` sites. Upstream never
reaches them for the inputs below, but two other bugs of the port do.
`visit_operand` (`:195`): a closure above a `let` that it reads and that
is reassigned later, for example `const row = () => <Row items={items}
/>; let items = p.items; if (p.c) items = [];`. The lowering did not
declare `items` before the closure there. #42390 fixed that (merged),
and upstream compiles and memoizes those functions. `visit_scope`
(`:1060`): dead code elimination dropped `let v` but kept a store to it,
fixed in #42385 (merged). Those inputs were not parity cases, and they
compile on main now. The conversion here is for the next input that
reaches one of these sites. To exercise the three paths on their own I
also forced each lookup to miss in a local build (one site at a time,
selected by an environment variable, not committed). Each time `bun
build` exited 0, the debug log showed `compile_fn err: ... "Expected
identifier to be initialized"` for the affected functions, and the other
functions in the file still compiled.

Not changed here: the other upstream invariants in this crate that are
still `.expect()` or `assert!`
(`build_reactive_scope_terminals_hir.rs:137/145/173`,
`propagate_scope_dependencies_hir.rs:90/1772`,
`align_reactive_scopes_to_block_scopes_hir.rs:262`,
`eliminate_redundant_phi.rs:75`, `build_reactive_function.rs:216/1388`).
No known input reaches them and each needs `Result` plumbing through a
different pass. `align_object_method_scopes.rs` is #42375.

What the test checks. It bundles one file with `--target=browser`
through the CLI and runs the output against a fake
`react/compiler-runtime` whose `c` counts its calls. Four functions have
the shape (call, array and object in the test, a `let m = null`
initializer, a hook that returns the array, the conditional nested in a
binary expression). A fifth is the react/react#37228 input, a `?:` in
a `try` in an IIFE. Each returns the right value and makes 0 calls to
`c`. Two controls (the same `?:` with `m` not memoized, and a plain
component) make 1 call each, so the skip is per function and not per
file. Each of the five aborts 1.4.2 on its own.

Sweep: built all 1807 source files under
`test/bundler/transpiler/react-compiler-fixtures/` with `bun build
--react-compiler --target=browser --external '*'` on 1.4.3 canary. None
aborts, so no upstream fixture has this shape.

Suites run with the debug build, after the rebase on 7a7cc96:
`test/bundler/transpiler/react-compiler.test.ts` (58 pass),
`test/bundler/transpiler/react-compiler-fixtures.test.ts` (3293 pass,
320 skip). `cargo clippy -p bun_react_compiler` and `cargo fmt --check`
are clean.

</details>

<!-- robobun:evidence:begin -->

---

**[human-review]** gate passed · iteration 1 · 2 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: 2 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bundler/transpiler/react-compiler.test.ts
bun test v1.4.3 (6a92015)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [1077.04ms]
(pass) bundler > react-compiler/ComponentWithHooks [1018.66ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [466.17ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [677.15ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [586.00ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [413.21ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [963.12ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [227.16ms]
(pass) bundler > react-compiler/SsrObjectMethodShorthand [1037.88ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Client [325.23ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Ssr [307.01ms]
(pass) bundler > react-compiler/BundledReactPreservesImportRefs [538.68ms]
(pass) bundler > r
... (truncated)

release without fix: 12 failed, 1 skipped
bun test v1.4.3-canary.1 (3c62dfb)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [63.16ms]
(pass) bundler > react-compiler/ComponentWithHooks [20.36ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [11.72ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [14.43ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [30.53ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [7.03ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [24.77ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [11.01ms]
1031 |       ]);
1032 |       const stdout = Buffer.from(stdoutBytes);
1033 |       const stderr = Buffer.from(stderrBytes);
1034 |       const success = exitCode === 0;
1035 |       if (buildProc.signalCode) {
1036 |         throw new Error(
                         ^
error: [react-compiler/SsrObjectMethodShorthand] 'bun build' subprocess killed by SIGABRT
cmd: /workspace/bun/build/release/bun build /tmp/bun-build-tests/bun-eKoWEm/react-compiler/SsrObjectMethodShorthand/entry.jsx --outfile=/tmp/bun-build-tests
... (truncated)
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bundler/transpiler/react-compiler.test.ts
bun test v1.4.3 (6a92015)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [984.04ms]
(pass) bundler > react-compiler/ComponentWithHooks [341.71ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [417.08ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [333.24ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [192.84ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [179.22ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [421.84ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [149.25ms]
(pass) bundler > react-compiler/SsrObjectMethodShorthand [850.95ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Client [149.08ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Ssr [164.78ms]
(pass) bundler > react-compiler/BundledReactPreservesImportRefs [344.39ms]
(pass) bundler > reac
... (truncated)

release with fix: 1 skipped
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 814ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[0/4] cargo bun_runtime → libbun_runtime.a
^[[1m^[[92m   Compiling^[[0m bun_react_compiler v0.0.0 (/workspace/bun/src/react_compiler)
^[[1m^[[92m   Compiling^[[0m bun_js_parser v0.0.0 (/workspace/bun/src/js_parser)
^[[1m^[[92m   Compiling^[[0m bun_resolver v0.0.0 (/workspace/bun/src/resolver)
^[[1m^[[92m   Compiling^[[0m bun_ini v0.0.0 (/workspace/bun/src/ini)
^[[1m^[[92m   Compiling^[[0m bun_bundler v0.0.0 (/workspace/bun/src/bundler)
^[[1m^[[92m   Compiling^[[0m bun_router v0.0.0 (/workspace/bun/src/router)
^[[1m^[[92m   Compiling^[[0m bun_standalone_graph v0.0.0 (/workspace/bun/src/standalone_graph)
^[[1m^[[92m   Compiling^[[0m bun_transpiler v0.0.0 (/workspace/bun/src/transpiler)
^[[1m^[[92m   Compiling^[[0m bun_bunfig v0.0.0 (/workspace/bun/src/bunfig)
^[[1m^[[92m   Compiling^[[0m bun_install v0.0.0 (/workspace/bun/src/install)
^[[1m^[[92m   Compiling^[[0m bun_jsc v0.0.0 (/workspace/bun/src/jsc)
^[[1m^[[92m   Compiling^[[0m bun_js_parser_jsc v0.0.0 (/workspace/bun/src/js_parser_jsc)
^[[1m^[[92m   Compiling^[[0m bun_http_jsc v0.0.0 (/work
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
.../reactive_scopes/prune_non_escaping_scopes.rs   | 103 +++++++++++++--------
 test/bundler/transpiler/react-compiler.test.ts     | 103 +++++++++++++++++++++
 2 files changed, 166 insertions(+), 40 deletions(-)
```

</details>

**gate history** · 2 passed · 0 rejected · iteration 1

<details><summary>evidence per changed file</summary>

```
file                                                      reads  edits  tests
…t_compiler/reactive_scopes/prune_non_escaping_scopes.rs      6     12     29
test/bundler/transpiler/react-compiler.test.ts                2      4     29
```

</details>

<!-- robobun:evidence:end -->
usrbinkat pushed a commit to usrbinkat/bun that referenced this pull request Sep 15, 2026
…ight-hand side (oven-sh#42386)

### Problem

- With `bun build --react-compiler`, `tail.next = tail = node` (list
append) throws `TypeError: undefined is not an object (evaluating
'tail.next = tail = {...}')`: the output declares `let tail;` without `=
head`. `v.z = v = 80` prints as `80 .z = v = 80`. `arr[i] = i++` stores
at the wrong index. Both output modes, 1.4.2 and main.
- `lower_simple_assignment`
(`src/react_compiler/lowering/build_hir/expr.rs:671`) lowers the
right-hand side before the object and key of a member target. JS
evaluates the object and key first. So the target reads the value that
the right-hand side assigned, and the old value has no reader left.
Upstream has the same order.

### Fix

- Lower the object, then a computed key, then the right-hand side, then
the store. `a.b += c` and `a.b++` already lower in this order.
- Correct because codegen prints `object[key] = value`, which JS
evaluates in this order.
- The upstream fixtures (1,587 in client mode, 1,734 in ssr mode) print
the same text before and after.
- Verified: `test/bundler/transpiler/react-compiler.test.ts` (2 new
tests, 9 forms each, 8 fail on 1.4.3-canary). Also
`react-compiler-fixtures.test.ts`, `bundler_jsx.test.ts`.

### Background

- The compiler lowers a function to HIR: instructions in evaluation
order, each result in a temporary. `a.b = c` becomes a load of `a`, the
instructions of `c`, and a `PropertyStore`.
- SSA, constant propagation and dead code elimination decide from that
order which assignment a read sees.
- Codegen puts each temporary back where it is used, so the output has
the shape of the source. A wrong order shows only when a pass acted on
it.

<details><summary>Notes</summary>

**Upstream.** babel-plugin-react-compiler 1.0.0 prints the same `let
tail;` for the first form. The same order is in
`compiler/crates/react_compiler_lowering/src/build_hir.rs` at the ported
commit (560db514, "Member expression assignment" arm) and on
facebook/react main (019019be). It is also in `BuildHIR.ts`: the
`AssignmentExpression` case passes `lowerExpressionToTemporary(builder,
expr.get('right'))` as an argument to `lowerAssignment`, and the
`MemberExpression` case of `lowerAssignment` lowers the object after
that. The new comment in `lower_simple_assignment` records that the port
differs here on purpose.

**Forms.** 44 forms. Each was built without the compiler, in ssr mode
(`--target=bun`) and in client mode (`--target=browser`). Each output
then rendered five times with a memo cache that persists between
renders: the same props twice, two other prop sets, then the first props
again. The compiled outputs must print what the uncompiled output
prints. On 1.4.3-canary (6a92015) 17 forms differ. On this branch none
differ, and client mode memoizes 42 of the 44 (the other two break a
rule of React on purpose and are left uncompiled on both builds). Canary
is two commits behind main and neither commit touches the lowering. A
debug build of main (4b5862f) fails the same way on the 31 forms I
also ran there.

| form | expected | 1.4.3-canary |
| --- | --- | --- |
| `tail.next = tail = {...}` in `for of`, `for`, `while`, and in the
update clause of a `for` | `0,1,2,3` | `TypeError: undefined is not an
object` |
| the same twice with no loop | the list `0,5,1` | the same TypeError |
| `tail[p.key] = tail = {...}` and `tail[0] = tail = {...}` | `0,1,2,3`
| the same TypeError |
| `tail.next = tail = p.make(x)` | `0,1,2,3` | the same TypeError |
| the same inside `try`/`catch` | `0,1,2,3` | the `catch` branch renders
|
| the `for of` form inside an arrow function | `0,1,2,3` | the same
TypeError |
| `box.child = box = { el: <span/> }`, `box.fn = box = { get: () => p.a
}` | the old `box` gets the key | the same TypeError |
| `v.z = v = 80` in an `if`, in a loop, at the top level |
`[{"z":80},80]` | `TypeError: Attempted to assign to readonly property.`
|
| `o[key] = key = "b"` with `let key = "a"` | `[{"a":"b"},"b"]` |
`[{"b":"b"},"b"]` |
| `arr[i] = i++; arr[i] = i++; arr[i++] = i` with `let i = 0` |
`[[0,1,3],3]` | `[[null,0,2],3]` |
| `let a = {name: "a"}; if (p.flag) a.other = a = b;` | with `flag`
false: `{"name":"a"}` | client mode, after a render with `flag` true:
`{"name":"a","other":{"name":"b"}}` |

The last row is the second effect in the report. The compiler saw no
write to the first object, cached it behind a sentinel check, and the
store then changed the cached object for every later render.

27 forms already matched on canary and still match: `v.z = v = p.a` and
`v.z = v = {...}` at the top level, the `arr[i] = i++` forms inside a
loop (no constant to propagate), `arr[0] = arr = [...]`, `a.b.c = a =
{...}`, `o.x = o.y = o = {...}`, a sequence, conditional, logical,
nullish or optional chain on the right, `(a = {...}).x = a.name`, `tail
= tail.next = {...}`, a call as the object and as the key (the calls run
in source order), `useState()` on the right, a local that a closure
captures, `reduce((tail, x) => (tail.next = tail = {...}), head)`, `v.z
+= (v = {...}).z`, `seen.push(v, (v = {...}))`, and destructuring
targets (`[a.x, a] = ...`, `[a, a.y] = ...`).

**Why many forms already worked.** With the instructions in the wrong
order the printed text is still `tail.next = tail = {...}`, in source
order, because codegen puts the temporaries back at their place of use.
The output is wrong only when a pass used the order: dead code
elimination (the removed `= head`), constant propagation (`80 .z`,
`o["b"]`, `arr[1] = i++`), or the mutation analysis (the last row of the
table).

**One form now compiles.** At the top level of a component, `let v = {};
const o = v; v.z = v = p.a;` was left uncompiled in client mode. With
the wrong order the store looked like a write to the prop `p.a`, which
the compiler rejects. Now the store goes to the local object and the
component is memoized.

**Other assignment lowerings.** A member target inside a destructuring
pattern (`[a.b] = c`) or in `for (a.b of c)` goes through
`lower_member_store` with a value that already exists. JS also produces
that value before it evaluates the target, so those stay as they are.
Calls, compound assignment and update expressions lower their operands
in source order.

**Fixture corpus.** I compiled every upstream fixture that builds (1,587
in one `Bun.build` call in client mode, 1,734 with one `bun build` per
file in ssr mode) with a debug build of main and with a debug build of
this branch, and compared the output files. No file differs. So the memo
slot counts and scope boundaries of the corpus are unchanged. Five
fixtures panic in ssr mode on both builds (`Expected all
ObjectExpressions and ObjectMethods to have non-null scope`), which
oven-sh#42375 fixes.

</details>

<!-- robobun:evidence:begin -->

---

**[human-review]** gate passed · iteration 0 · 2 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: 2 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bundler/transpiler/react-compiler.test.ts
bun test v1.4.3 (6a92015)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [949.44ms]
(pass) bundler > react-compiler/ComponentWithHooks [341.77ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [454.80ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [526.88ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [178.43ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [156.04ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [548.32ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [156.65ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Client [226.14ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Ssr [156.10ms]
(pass) bundler > react-compiler/BundledReactPreservesImportRefs [384.19ms]
(pass) bundler > react-compiler/BundledCjsCompilerRuntimeSurvivesTreeShaking [742.38ms]
(
... (truncated)

release without fix: 11 failed, 1 skipped
bun test v1.4.3-canary.1 (c800ac8)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [37.06ms]
(pass) bundler > react-compiler/ComponentWithHooks [13.21ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [14.65ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [15.81ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [10.81ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [9.10ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [29.97ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [11.46ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Client [26.57ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Ssr [12.37ms]
(pass) bundler > react-compiler/BundledReactPreservesImportRefs [14.27ms]
(pass) bundler > react-compiler/BundledCjsCompilerRuntimeSurvivesTreeShaking [30.20ms]
(pass) bundler > react-compiler/RequireStringPreservesImportRecord [18.43ms]
(pass) bundler > react-compiler/BranchBooleanFeatureFlagPreservesDCE [10.10ms]
(pass) bundler > react-compi
... (truncated)
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bundler/transpiler/react-compiler.test.ts
bun test v1.4.3 (6a92015)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [1041.63ms]
(pass) bundler > react-compiler/ComponentWithHooks [316.85ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [368.95ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [367.44ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [193.38ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [152.07ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [520.79ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [146.70ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Client [148.27ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Ssr [137.77ms]
(pass) bundler > react-compiler/BundledReactPreservesImportRefs [485.46ms]
(pass) bundler > react-compiler/BundledCjsCompilerRuntimeSurvivesTreeShaking [656.34ms]

... (truncated)

release with fix: 1 skipped
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 815ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[0/5] cargo bun_runtime → libbun_runtime.a
�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl_sys)
�[1m�[92m   Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety)
�[1m�[92m   Compiling�[0m bun_base64 v0.0.0 (/workspace/bun/src/base64)
�[1m�[92m   Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys)
�[1m�[92m   Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys)
�[1m�[92m   Compiling�[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd)
�[1m�[92m   Compiling�[0m bun_picohttp v0.0.0 (/workspace/bun/src/picohttp)
�[1m�[92m   Compiling�[0m bun_brotli v0.0.0 (/workspace/bun/src/brotli)
�[1m�[92m   Compiling�[0m bun_output v0.0.0 (/workspace/bun/src/output)
�[1m�[92m   Compiling�[0m bun_clap v0.0.0 (/workspace/bun/src/clap)
�[1m�[92m   Compiling�[0m bu
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
src/react_compiler/lowering/build_hir/expr.rs  |   6 +-
 test/bundler/transpiler/react-compiler.test.ts | 136 +++++++++++++++++++++++++
 2 files changed, 140 insertions(+), 2 deletions(-)
```

</details>

**gate history** · 2 passed · 0 rejected · iteration 0

<details><summary>evidence per changed file</summary>

```
file                                            reads  edits  tests
src/react_compiler/lowering/build_hir/expr.rs       3      2     20
test/bundler/transpiler/react-compiler.test.ts      3      1     20
```

</details>

<!-- robobun:evidence:end -->
usrbinkat pushed a commit to usrbinkat/bun that referenced this pull request Sep 15, 2026
…n-sh#42378)

### Problem
- `bun build --react-compiler --target=browser` aborts on `const r = (m
= p.f()) ? 1 : 0; return <div data-v={[m, r]} />`: `panic: Expected a
node for all scopes`. `Bun.build` aborts the calling process the same
way.
- `CollectDependenciesVisitor` skips the test of a `?:`, so the reactive
scope that reassigns `m` gets no scope node. `visit_scope` still
attaches that scope to `m`. When `m` is force-memoized,
`force_memoize_scope_dependencies`
(`src/react_compiler/reactive_scopes/prune_non_escaping_scopes.rs:1170`)
calls `.expect()` on the missing node.

### Fix
- That invariant is now a `cold_invariant` error. `program.rs` leaves
that one function uncompiled, like upstream's `CompilerError.invariant`.
- The three `Expected identifier to be initialized` invariants in the
same pass get the same treatment. Their visitor callbacks return `()`,
so they record the first error in `CollectState`, as
`assert_scope_instructions_within_scopes` does.
- Correct because babel-plugin-react-compiler 1.0.0 raises the same
invariant on the same inputs and skips the same functions. Compiled
output does not change.
- Verified: `test/bundler/transpiler/react-compiler.test.ts` (new
`AssignmentInConditionalTestSkipsOnlyThatFunction`, SIGABRT on 1.4.3
canary). Also `react-compiler-fixtures.test.ts`.

### Background
- The React Compiler groups values that change together into reactive
scopes and emits one memo block per scope. PruneNonEscapingScopes
removes the scopes whose values never leave the function.
- The pass builds a graph of identifier nodes and scope nodes, then
walks it from the returned values.
- Upstream's `CompilerError.invariant` is a caught per-function error in
TS. Bun builds with `panic = "abort"`, so a ported `.expect()` ends the
process.

<details><summary>Notes</summary>

Repro (any directory, 1.4.2, 1.4.3 canary 6a92015 and main 4b5862f):

```jsx
// a.jsx
export default function App(p) {
  let m;
  const r = (m = p.f()) ? 1 : 0;
  return <div data-v={[m, r]} />;
}
```

```
$ bun build --react-compiler --target=browser --external react a.jsx
panic: Expected a node for all scopes
Crashed while visiting a.jsx
```

Frames: `force_memoize_scope_dependencies`
(`prune_non_escaping_scopes.rs:1170`),
`compute_memoized_identifiers::visit` (`:1156`),
`prune_non_escaping_scopes` (`:70`), `pipeline::run_hir_passes`
(`pipeline.rs:622`), `compile_fn` (`:172`).

The smallest shape: an assignment whose value allocates (a call, an
array or an object literal) in the test of a `?:`, and the assigned
local as an operand of a memoized value (`[m]`, `{ m }`). The result of
the conditional does not need to reach that value. `m = p.a` compiles.
`&&`, `??`, `?.` or an `if` statement in place of `?:` compile. `<div
data-m={m} />` compiles because nothing force-memoizes `m`.
`--target=bun` and `--target=node` (ssr mode) create no reactive scopes
and compile.

A second shape, with no assignment expression (the input of
react/react#37228): `return (() => { try { return check(raw) ? raw :
null; } catch { return null; } })();`. 1.4.2 aborts with the same panic
and this branch skips the function with the same invariant.
babel-plugin-react-compiler 1.0.0 skips it one step earlier, with `Todo:
Support value blocks (conditional, logical, optional chaining, etc)
within a try/catch statement`.

Two loop shapes with no `?:` at all, where `identity` is an imported
function: `let r; do { r = identity(value); } while (cond()); return r;`
and `let r; while (cond()) { r = identity(value); break; } return r;`.
1.4.2 aborts on both with the same panic, this branch skips the
function, and babel-plugin-react-compiler 1.0.0 raises the same
invariant and leaves the function as written. With `String(value)` in
place of `identity(value)` nothing aborts. I ran both by hand on this
branch. They are not in the test.

Why the node is missing. `compute_memoization_inputs` returns the
rvalues of the consequent and the alternate of a `ConditionalExpression`
only (upstream: "Conditionals do not alias their test value"), and
`visit_instruction` does not traverse nested values on its own. So
nothing calls `visit_operand` for `t = p.f()` or `m = t`, and
`visit_operand` is the only place that creates a scope node. The scope
is aligned to the whole `const r = ...` statement and lists `m` in
`reassignments`, so `visit_scope` adds it to the node of `m`. The array
`[m]` is `Memoized`, its scope depends on `m`, `m` is `Unmemoized` (from
`let m;`) and forced, and the walk reaches the scope with no node. `(m =
p.f()) ? p.a() : p.b()` compiles because the calls in the branches are
in the same scope and create the node.

Upstream. `PruneNonEscapingScopes.ts` on facebook/react main still has
all four invariants, and
`react_compiler_reactive_scopes/src/prune_non_escaping_scopes.rs` there
has the same four `.expect()` calls. I ran babel-plugin-react-compiler
1.0.0 on 20 variants of the input. It logs `CompileError: Expected a
node for all scopes` and leaves the function as written for exactly the
10 variants that abort 1.4.3 canary. The two agree on the other 10 as
well: 8 compile with one memo cache, 1 compiles with none, and 1 fails
in both with `Unexpected StoreLocal in codegenInstructionValue`, which
Bun already returns as an error. With this change Bun skips the same 10
functions.

Alternative not taken: create the scope node on demand in
`force_memoize_scope_dependencies` from `env.scopes[id].dependencies`,
which is all `visit_operand` stores. That compiles these functions, but
upstream does not compile them, and nothing in that test value has been
analysed by this pass.

The three `Expected identifier to be initialized` sites. Upstream never
reaches them for the inputs below, but two other bugs of the port do.
`visit_operand` (`:195`): a closure above a `let` that it reads and that
is reassigned later, for example `const row = () => <Row items={items}
/>; let items = p.items; if (p.c) items = [];`. The lowering did not
declare `items` before the closure there. oven-sh#42390 fixed that (merged),
and upstream compiles and memoizes those functions. `visit_scope`
(`:1060`): dead code elimination dropped `let v` but kept a store to it,
fixed in oven-sh#42385 (merged). Those inputs were not parity cases, and they
compile on main now. The conversion here is for the next input that
reaches one of these sites. To exercise the three paths on their own I
also forced each lookup to miss in a local build (one site at a time,
selected by an environment variable, not committed). Each time `bun
build` exited 0, the debug log showed `compile_fn err: ... "Expected
identifier to be initialized"` for the affected functions, and the other
functions in the file still compiled.

Not changed here: the other upstream invariants in this crate that are
still `.expect()` or `assert!`
(`build_reactive_scope_terminals_hir.rs:137/145/173`,
`propagate_scope_dependencies_hir.rs:90/1772`,
`align_reactive_scopes_to_block_scopes_hir.rs:262`,
`eliminate_redundant_phi.rs:75`, `build_reactive_function.rs:216/1388`).
No known input reaches them and each needs `Result` plumbing through a
different pass. `align_object_method_scopes.rs` is oven-sh#42375.

What the test checks. It bundles one file with `--target=browser`
through the CLI and runs the output against a fake
`react/compiler-runtime` whose `c` counts its calls. Four functions have
the shape (call, array and object in the test, a `let m = null`
initializer, a hook that returns the array, the conditional nested in a
binary expression). A fifth is the react/react#37228 input, a `?:` in
a `try` in an IIFE. Each returns the right value and makes 0 calls to
`c`. Two controls (the same `?:` with `m` not memoized, and a plain
component) make 1 call each, so the skip is per function and not per
file. Each of the five aborts 1.4.2 on its own.

Sweep: built all 1807 source files under
`test/bundler/transpiler/react-compiler-fixtures/` with `bun build
--react-compiler --target=browser --external '*'` on 1.4.3 canary. None
aborts, so no upstream fixture has this shape.

Suites run with the debug build, after the rebase on 7a7cc96:
`test/bundler/transpiler/react-compiler.test.ts` (58 pass),
`test/bundler/transpiler/react-compiler-fixtures.test.ts` (3293 pass,
320 skip). `cargo clippy -p bun_react_compiler` and `cargo fmt --check`
are clean.

</details>

<!-- robobun:evidence:begin -->

---

**[human-review]** gate passed · iteration 1 · 2 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: 2 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bundler/transpiler/react-compiler.test.ts
bun test v1.4.3 (6a92015)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [1077.04ms]
(pass) bundler > react-compiler/ComponentWithHooks [1018.66ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [466.17ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [677.15ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [586.00ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [413.21ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [963.12ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [227.16ms]
(pass) bundler > react-compiler/SsrObjectMethodShorthand [1037.88ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Client [325.23ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Ssr [307.01ms]
(pass) bundler > react-compiler/BundledReactPreservesImportRefs [538.68ms]
(pass) bundler > r
... (truncated)

release without fix: 12 failed, 1 skipped
bun test v1.4.3-canary.1 (3c62dfb)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [63.16ms]
(pass) bundler > react-compiler/ComponentWithHooks [20.36ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [11.72ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [14.43ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [30.53ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [7.03ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [24.77ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [11.01ms]
1031 |       ]);
1032 |       const stdout = Buffer.from(stdoutBytes);
1033 |       const stderr = Buffer.from(stderrBytes);
1034 |       const success = exitCode === 0;
1035 |       if (buildProc.signalCode) {
1036 |         throw new Error(
                         ^
error: [react-compiler/SsrObjectMethodShorthand] 'bun build' subprocess killed by SIGABRT
cmd: /workspace/bun/build/release/bun build /tmp/bun-build-tests/bun-eKoWEm/react-compiler/SsrObjectMethodShorthand/entry.jsx --outfile=/tmp/bun-build-tests
... (truncated)
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bundler/transpiler/react-compiler.test.ts
bun test v1.4.3 (6a92015)

test/bundler/transpiler/react-compiler.test.ts:
(pass) bundler > react-compiler/SimpleComponent [984.04ms]
(pass) bundler > react-compiler/ComponentWithHooks [341.71ms]
(pass) bundler > react-compiler/ObjectPatternRestInProps [417.08ms]
(pass) bundler > react-compiler/UnderscoreAndDollarComponentTags [333.24ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Browser [192.84ms]
(pass) bundler > react-compiler/OutputModeDefaultsByTarget-Bun [179.22ms]
(pass) bundler > react-compiler/FullstackHtmlImportCompilesClientGraphInClientMode [421.84ms]
(pass) bundler > react-compiler/OutputModeExplicitSsrOverridesTarget [149.25ms]
(pass) bundler > react-compiler/SsrObjectMethodShorthand [850.95ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Client [149.08ms]
(pass) bundler > react-compiler/OutputModeIgnoredWhenCompilerDisabled-Ssr [164.78ms]
(pass) bundler > react-compiler/BundledReactPreservesImportRefs [344.39ms]
(pass) bundler > reac
... (truncated)

release with fix: 1 skipped
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 814ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[0/4] cargo bun_runtime → libbun_runtime.a
^[[1m^[[92m   Compiling^[[0m bun_react_compiler v0.0.0 (/workspace/bun/src/react_compiler)
^[[1m^[[92m   Compiling^[[0m bun_js_parser v0.0.0 (/workspace/bun/src/js_parser)
^[[1m^[[92m   Compiling^[[0m bun_resolver v0.0.0 (/workspace/bun/src/resolver)
^[[1m^[[92m   Compiling^[[0m bun_ini v0.0.0 (/workspace/bun/src/ini)
^[[1m^[[92m   Compiling^[[0m bun_bundler v0.0.0 (/workspace/bun/src/bundler)
^[[1m^[[92m   Compiling^[[0m bun_router v0.0.0 (/workspace/bun/src/router)
^[[1m^[[92m   Compiling^[[0m bun_standalone_graph v0.0.0 (/workspace/bun/src/standalone_graph)
^[[1m^[[92m   Compiling^[[0m bun_transpiler v0.0.0 (/workspace/bun/src/transpiler)
^[[1m^[[92m   Compiling^[[0m bun_bunfig v0.0.0 (/workspace/bun/src/bunfig)
^[[1m^[[92m   Compiling^[[0m bun_install v0.0.0 (/workspace/bun/src/install)
^[[1m^[[92m   Compiling^[[0m bun_jsc v0.0.0 (/workspace/bun/src/jsc)
^[[1m^[[92m   Compiling^[[0m bun_js_parser_jsc v0.0.0 (/workspace/bun/src/js_parser_jsc)
^[[1m^[[92m   Compiling^[[0m bun_http_jsc v0.0.0 (/work
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
.../reactive_scopes/prune_non_escaping_scopes.rs   | 103 +++++++++++++--------
 test/bundler/transpiler/react-compiler.test.ts     | 103 +++++++++++++++++++++
 2 files changed, 166 insertions(+), 40 deletions(-)
```

</details>

**gate history** · 2 passed · 0 rejected · iteration 1

<details><summary>evidence per changed file</summary>

```
file                                                      reads  edits  tests
…t_compiler/reactive_scopes/prune_non_escaping_scopes.rs      6     12     29
test/bundler/transpiler/react-compiler.test.ts                2      4     29
```

</details>

<!-- robobun:evidence:end -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants