Skip to content

Commit ec8e4c8

Browse files
committed
[compiler] Handle more cases with for in try/catch
1 parent 61702ba commit ec8e4c8

23 files changed

+364
-166
lines changed

compiler/.claude/agents/compiler-bug-investigator.md renamed to compiler/.claude/agents/investigate-error.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
2-
name: compiler-bug-investigator
3-
description: "Use this agent when the developer asks to 'investigate a bug', 'debug why this fixture errors', 'understand why the compiler is failing', 'find the root cause of a compiler issue', or when they provide a code snippet that the React Compiler handles incorrectly and want to understand why. This agent is specifically for React Compiler bugs, not general React bugs.\\n\\n<example>\\nContext: User provides a code snippet that causes the compiler to error unexpectedly.\\nuser: \"Can you investigate why this code errors? function Component() { const x = a?.b; return <div>{x}</div> }\"\\nassistant: \"I'll use the compiler-bug-investigator agent to investigate this bug and find the root cause.\"\\n<Task tool call to launch compiler-bug-investigator>\\n</example>\\n\\n<example>\\nContext: User asks to debug a fixture that's producing incorrect output.\\nuser: \"Debug why the fixture error.invalid-optional-chain.js is failing\"\\nassistant: \"Let me launch the compiler-bug-investigator agent to analyze this fixture and identify the problematic compiler pass.\"\\n<Task tool call to launch compiler-bug-investigator>\\n</example>\\n\\n<example>\\nContext: User wants to understand unexpected compiler behavior.\\nuser: \"Investigate a bug - the compiler is generating wrong code for ternary expressions with side effects\"\\nassistant: \"I'll use the compiler-bug-investigator agent to systematically investigate this issue and identify the faulty compiler logic.\"\\n<Task tool call to launch compiler-bug-investigator>\\n</example>"
2+
name: investigate-error
3+
description: Investigates React compiler errors to determine the root cause and identify potential mitigation(s). Use this agent when the user asks to 'investigate a bug', 'debug why this fixture errors', 'understand why the compiler is failing', 'find the root cause of a compiler issue', or when they provide a snippet of code and ask to debug. Use automatically when encountering a failing test case, in order to understand the root cause.
44
model: opus
55
color: pink
66
---

compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ export type TryTerminal = {
612612
export type MaybeThrowTerminal = {
613613
kind: 'maybe-throw';
614614
continuation: BlockId;
615-
handler: BlockId;
615+
handler: BlockId | null;
616616
id: InstructionId;
617617
loc: SourceLocation;
618618
fallthrough?: never;

compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,9 @@ export function printTerminal(terminal: Terminal): Array<string> | string {
291291
break;
292292
}
293293
case 'maybe-throw': {
294-
value = `[${terminal.id}] MaybeThrow continuation=bb${terminal.continuation} handler=bb${terminal.handler}`;
294+
const handlerStr =
295+
terminal.handler !== null ? `bb${terminal.handler}` : '(none)';
296+
value = `[${terminal.id}] MaybeThrow continuation=bb${terminal.continuation} handler=${handlerStr}`;
295297
if (terminal.effects != null) {
296298
value += `\n ${terminal.effects.map(printAliasingEffect).join('\n ')}`;
297299
}

compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -909,7 +909,8 @@ export function mapTerminalSuccessors(
909909
}
910910
case 'maybe-throw': {
911911
const continuation = fn(terminal.continuation);
912-
const handler = fn(terminal.handler);
912+
const handler =
913+
terminal.handler !== null ? fn(terminal.handler) : null;
913914
return {
914915
kind: 'maybe-throw',
915916
continuation,
@@ -1083,7 +1084,9 @@ export function* eachTerminalSuccessor(terminal: Terminal): Iterable<BlockId> {
10831084
}
10841085
case 'maybe-throw': {
10851086
yield terminal.continuation;
1086-
yield terminal.handler;
1087+
if (terminal.handler !== null) {
1088+
yield terminal.handler;
1089+
}
10871090
break;
10881091
}
10891092
case 'try': {

compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ function inferBlock(
508508
const terminal = block.terminal;
509509
if (terminal.kind === 'try' && terminal.handlerBinding != null) {
510510
context.catchHandlers.set(terminal.handler, terminal.handlerBinding);
511-
} else if (terminal.kind === 'maybe-throw') {
511+
} else if (terminal.kind === 'maybe-throw' && terminal.handler !== null) {
512512
const handlerParam = context.catchHandlers.get(terminal.handler);
513513
if (handlerParam != null) {
514514
CompilerError.invariant(state.kind(handlerParam) != null, {

compiler/packages/babel-plugin-react-compiler/src/Optimization/PruneMaybeThrows.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {CompilerError} from '..';
99
import {
1010
BlockId,
1111
GeneratedSource,
12-
GotoVariant,
1312
HIRFunction,
1413
Instruction,
1514
assertConsistentIdentifiers,
@@ -25,9 +24,15 @@ import {
2524
} from '../HIR/HIRBuilder';
2625
import {printPlace} from '../HIR/PrintHIR';
2726

28-
/*
29-
* This pass prunes `maybe-throw` terminals for blocks that can provably *never* throw.
30-
* For now this is very conservative, and only affects blocks with primitives or
27+
/**
28+
* This pass updates `maybe-throw` terminals for blocks that can provably *never* throw,
29+
* nulling out the handler to indicate that control will always continue. Note that
30+
* rewriting to a `goto` disrupts the structure of the HIR, making it more difficult to
31+
* reconstruct an ast during BuildReactiveFunction. Preserving the maybe-throw makes the
32+
* continuations clear, while nulling out the handler tells us that control cannot flow
33+
* to the handler.
34+
*
35+
* For now the analysis is very conservative, and only affects blocks with primitives or
3136
* array/object literals. Even a variable reference could throw bc of the TDZ.
3237
*/
3338
export function pruneMaybeThrows(fn: HIRFunction): void {
@@ -82,13 +87,7 @@ function pruneMaybeThrowsImpl(fn: HIRFunction): Map<BlockId, BlockId> | null {
8287
if (!canThrow) {
8388
const source = terminalMapping.get(block.id) ?? block.id;
8489
terminalMapping.set(terminal.continuation, source);
85-
block.terminal = {
86-
kind: 'goto',
87-
block: terminal.continuation,
88-
variant: GotoVariant.Break,
89-
id: terminal.id,
90-
loc: terminal.loc,
91-
};
90+
terminal.handler = null;
9291
}
9392
}
9493
return terminalMapping.size > 0 ? terminalMapping : null;

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/BuildReactiveFunction.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,12 @@ class Driver {
168168
innerValue = innerValue.value;
169169
}
170170

171-
// Only add the final instruction if the innermost value is not just a LoadLocal
172-
// of the same place we're storing to (which would be a no-op).
173-
// This happens when MaybeThrow blocks cause the sequence to already contain
174-
// all the necessary instructions.
171+
/*
172+
* Only add the final instruction if the innermost value is not just a LoadLocal
173+
* of the same place we're storing to (which would be a no-op).
174+
* This happens when MaybeThrow blocks cause the sequence to already contain
175+
* all the necessary instructions.
176+
*/
175177
const isLoadOfSamePlace =
176178
innerValue.kind === 'LoadLocal' &&
177179
innerValue.place.identifier.id === result.place.identifier.id;
@@ -496,7 +498,10 @@ class Driver {
496498
const init = this.visitValueBlock(terminal.init, terminal.loc);
497499
const initValue = this.valueBlockResultToSequence(init, terminal.loc);
498500

499-
const testValue = this.visitValueBlock(terminal.test, terminal.loc).value;
501+
const testValue = this.visitValueBlock(
502+
terminal.test,
503+
terminal.loc,
504+
).value;
500505

501506
const updateValue =
502507
terminal.update !== null

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-for-loop-in-try.expect.md

Lines changed: 0 additions & 43 deletions
This file was deleted.

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-for-loop-in-try.js

Lines changed: 0 additions & 11 deletions
This file was deleted.

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-invariant-expected-break-target.expect.md

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)