Skip to content

Commit 0c7b7ca

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

18 files changed

+369
-164
lines changed

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
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
2+
## Input
3+
4+
```javascript
5+
function Foo({items}) {
6+
const results = [];
7+
try {
8+
for (let i = 0; i < items.length; i++) {
9+
results.push(items[i]);
10+
}
11+
} catch (e) {
12+
return <span>Error</span>;
13+
}
14+
return <span>{results.join(', ')}</span>;
15+
}
16+
17+
export const FIXTURE_ENTRYPOINT = {
18+
fn: Foo,
19+
params: [{items: ['a', 'b', 'c']}],
20+
sequentialRenders: [
21+
{items: ['a', 'b', 'c']},
22+
{items: ['a', 'b', 'c']},
23+
{items: ['x', 'y']},
24+
{items: []},
25+
{items: ['single']},
26+
],
27+
};
28+
29+
```
30+
31+
## Code
32+
33+
```javascript
34+
import { c as _c } from "react/compiler-runtime";
35+
function Foo(t0) {
36+
const $ = _c(5);
37+
const { items } = t0;
38+
let results;
39+
let t1;
40+
if ($[0] !== items) {
41+
t1 = Symbol.for("react.early_return_sentinel");
42+
bb0: {
43+
results = [];
44+
try {
45+
for (let i = 0; i < items.length; i++) {
46+
results.push(items[i]);
47+
}
48+
} catch (t2) {
49+
t1 = <span>Error</span>;
50+
break bb0;
51+
}
52+
}
53+
$[0] = items;
54+
$[1] = results;
55+
$[2] = t1;
56+
} else {
57+
results = $[1];
58+
t1 = $[2];
59+
}
60+
if (t1 !== Symbol.for("react.early_return_sentinel")) {
61+
return t1;
62+
}
63+
64+
const t2 = results.join(", ");
65+
let t3;
66+
if ($[3] !== t2) {
67+
t3 = <span>{t2}</span>;
68+
$[3] = t2;
69+
$[4] = t3;
70+
} else {
71+
t3 = $[4];
72+
}
73+
return t3;
74+
}
75+
76+
export const FIXTURE_ENTRYPOINT = {
77+
fn: Foo,
78+
params: [{ items: ["a", "b", "c"] }],
79+
sequentialRenders: [
80+
{ items: ["a", "b", "c"] },
81+
{ items: ["a", "b", "c"] },
82+
{ items: ["x", "y"] },
83+
{ items: [] },
84+
{ items: ["single"] },
85+
],
86+
};
87+
88+
```
89+
90+
### Eval output
91+
(kind: ok) <span>a, b, c</span>
92+
<span>a, b, c</span>
93+
<span>x, y</span>
94+
<span></span>
95+
<span>single</span>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
function Foo({items}) {
2+
const results = [];
3+
try {
4+
for (let i = 0; i < items.length; i++) {
5+
results.push(items[i]);
6+
}
7+
} catch (e) {
8+
return <span>Error</span>;
9+
}
10+
return <span>{results.join(', ')}</span>;
11+
}
12+
13+
export const FIXTURE_ENTRYPOINT = {
14+
fn: Foo,
15+
params: [{items: ['a', 'b', 'c']}],
16+
sequentialRenders: [
17+
{items: ['a', 'b', 'c']},
18+
{items: ['a', 'b', 'c']},
19+
{items: ['x', 'y']},
20+
{items: []},
21+
{items: ['single']},
22+
],
23+
};
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
2+
## Input
3+
4+
```javascript
5+
import {useMemo} from 'react';
6+
7+
function useFoo(text) {
8+
return useMemo(() => {
9+
try {
10+
let formattedText = '';
11+
try {
12+
formattedText = format(text);
13+
} catch {
14+
formattedText = text;
15+
}
16+
return formattedText || '';
17+
} catch (e) {
18+
return '';
19+
}
20+
}, [text]);
21+
}
22+
23+
function format(text) {
24+
return text.toUpperCase();
25+
}
26+
27+
function Foo({text}) {
28+
const result = useFoo(text);
29+
return <span>{result}</span>;
30+
}
31+
32+
export const FIXTURE_ENTRYPOINT = {
33+
fn: Foo,
34+
params: [{text: 'hello'}],
35+
sequentialRenders: [
36+
{text: 'hello'},
37+
{text: 'hello'},
38+
{text: 'world'},
39+
{text: ''},
40+
],
41+
};
42+
43+
```
44+
45+
## Code
46+
47+
```javascript
48+
import { c as _c } from "react/compiler-runtime";
49+
import { useMemo } from "react";
50+
51+
function useFoo(text) {
52+
const $ = _c(2);
53+
let t0;
54+
try {
55+
let formattedText;
56+
try {
57+
let t2;
58+
if ($[0] !== text) {
59+
t2 = format(text);
60+
$[0] = text;
61+
$[1] = t2;
62+
} else {
63+
t2 = $[1];
64+
}
65+
formattedText = t2;
66+
} catch {
67+
formattedText = text;
68+
}
69+
70+
t0 = formattedText || "";
71+
} catch (t1) {
72+
t0 = "";
73+
}
74+
return t0;
75+
}
76+
77+
function format(text) {
78+
const $ = _c(2);
79+
let t0;
80+
if ($[0] !== text) {
81+
t0 = text.toUpperCase();
82+
$[0] = text;
83+
$[1] = t0;
84+
} else {
85+
t0 = $[1];
86+
}
87+
return t0;
88+
}
89+
90+
function Foo(t0) {
91+
const $ = _c(2);
92+
const { text } = t0;
93+
const result = useFoo(text);
94+
let t1;
95+
if ($[0] !== result) {
96+
t1 = <span>{result}</span>;
97+
$[0] = result;
98+
$[1] = t1;
99+
} else {
100+
t1 = $[1];
101+
}
102+
return t1;
103+
}
104+
105+
export const FIXTURE_ENTRYPOINT = {
106+
fn: Foo,
107+
params: [{ text: "hello" }],
108+
sequentialRenders: [
109+
{ text: "hello" },
110+
{ text: "hello" },
111+
{ text: "world" },
112+
{ text: "" },
113+
],
114+
};
115+
116+
```
117+
118+
### Eval output
119+
(kind: ok) <span>HELLO</span>
120+
<span>HELLO</span>
121+
<span>WORLD</span>
122+
<span></span>

0 commit comments

Comments
 (0)