Skip to content

Commit f5622ab

Browse files
committed
[compiler][wip] Support finally clauses
First pass at adding support for `finally` clauses. I took a simple approach where we reify the JS semantics around return shadowing in the HIR and output. Ie, we create a temporary variable that will be the return value, and change returns in the try/catch to assign to that value and then break to the finalizer. In the finalizer returns work as normal, but we put a final extra "if temporary set, return it" statement. It would be more ideal to fully restore the return shadowing, but given that finally clauses are relatively rare this seems like a reasonable compromise. I need to do more analysis to make sure the approach is correct.
1 parent 3a01c6f commit f5622ab

File tree

52 files changed

+1709
-340
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1709
-340
lines changed

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

Lines changed: 78 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,82 +1329,102 @@ function lowerStatement(
13291329
const continuationBlock = builder.reserve('block');
13301330

13311331
const handlerPath = stmt.get('handler');
1332-
if (!hasNode(handlerPath)) {
1332+
const finalizerPath = stmt.get('finalizer');
1333+
1334+
// Must have at least a catch or finally clause
1335+
if (!hasNode(handlerPath) && !hasNode(finalizerPath)) {
13331336
builder.errors.push({
1334-
reason: `(BuildHIR::lowerStatement) Handle TryStatement without a catch clause`,
1337+
reason: `(BuildHIR::lowerStatement) Handle TryStatement without a catch or finally clause`,
13351338
category: ErrorCategory.Todo,
13361339
loc: stmt.node.loc ?? null,
13371340
suggestions: null,
13381341
});
13391342
return;
13401343
}
1341-
if (hasNode(stmt.get('finalizer'))) {
1342-
builder.errors.push({
1343-
reason: `(BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause`,
1344-
category: ErrorCategory.Todo,
1345-
loc: stmt.node.loc ?? null,
1346-
suggestions: null,
1347-
});
1348-
}
13491344

1350-
const handlerBindingPath = handlerPath.get('param');
1345+
// Build handler (catch) block if present
1346+
let handler: BlockId | null = null;
13511347
let handlerBinding: {
13521348
place: Place;
13531349
path: NodePath<t.Identifier | t.ArrayPattern | t.ObjectPattern>;
13541350
} | null = null;
1355-
if (hasNode(handlerBindingPath)) {
1356-
const place: Place = {
1357-
kind: 'Identifier',
1358-
identifier: builder.makeTemporary(
1359-
handlerBindingPath.node.loc ?? GeneratedSource,
1360-
),
1361-
effect: Effect.Unknown,
1362-
reactive: false,
1363-
loc: handlerBindingPath.node.loc ?? GeneratedSource,
1364-
};
1365-
promoteTemporary(place.identifier);
1366-
lowerValueToTemporary(builder, {
1367-
kind: 'DeclareLocal',
1368-
lvalue: {
1369-
kind: InstructionKind.Catch,
1370-
place: {...place},
1371-
},
1372-
type: null,
1373-
loc: handlerBindingPath.node.loc ?? GeneratedSource,
1374-
});
13751351

1376-
handlerBinding = {
1377-
path: handlerBindingPath,
1378-
place,
1379-
};
1380-
}
1352+
if (hasNode(handlerPath)) {
1353+
const handlerBindingPath = handlerPath.get('param');
1354+
if (hasNode(handlerBindingPath)) {
1355+
const place: Place = {
1356+
kind: 'Identifier',
1357+
identifier: builder.makeTemporary(
1358+
handlerBindingPath.node.loc ?? GeneratedSource,
1359+
),
1360+
effect: Effect.Unknown,
1361+
reactive: false,
1362+
loc: handlerBindingPath.node.loc ?? GeneratedSource,
1363+
};
1364+
promoteTemporary(place.identifier);
1365+
lowerValueToTemporary(builder, {
1366+
kind: 'DeclareLocal',
1367+
lvalue: {
1368+
kind: InstructionKind.Catch,
1369+
place: {...place},
1370+
},
1371+
type: null,
1372+
loc: handlerBindingPath.node.loc ?? GeneratedSource,
1373+
});
13811374

1382-
const handler = builder.enter('catch', _blockId => {
1383-
if (handlerBinding !== null) {
1384-
lowerAssignment(
1385-
builder,
1386-
handlerBinding.path.node.loc ?? GeneratedSource,
1387-
InstructionKind.Catch,
1388-
handlerBinding.path,
1389-
{...handlerBinding.place},
1390-
'Assignment',
1391-
);
1375+
handlerBinding = {
1376+
path: handlerBindingPath,
1377+
place,
1378+
};
13921379
}
1393-
lowerStatement(builder, handlerPath.get('body'));
1394-
return {
1395-
kind: 'goto',
1396-
block: continuationBlock.id,
1397-
variant: GotoVariant.Break,
1398-
id: makeInstructionId(0),
1399-
loc: handlerPath.node.loc ?? GeneratedSource,
1400-
};
1401-
});
1380+
1381+
handler = builder.enter('catch', _blockId => {
1382+
if (handlerBinding !== null) {
1383+
lowerAssignment(
1384+
builder,
1385+
handlerBinding.path.node.loc ?? GeneratedSource,
1386+
InstructionKind.Catch,
1387+
handlerBinding.path,
1388+
{...handlerBinding.place},
1389+
'Assignment',
1390+
);
1391+
}
1392+
lowerStatement(builder, handlerPath.get('body'));
1393+
return {
1394+
kind: 'goto',
1395+
block: continuationBlock.id,
1396+
variant: GotoVariant.Break,
1397+
id: makeInstructionId(0),
1398+
loc: handlerPath.node.loc ?? GeneratedSource,
1399+
};
1400+
});
1401+
}
1402+
1403+
// Build finalizer (finally) block if present
1404+
let finalizer: BlockId | null = null;
1405+
if (hasNode(finalizerPath)) {
1406+
finalizer = builder.enter('block', _blockId => {
1407+
lowerStatement(builder, finalizerPath);
1408+
return {
1409+
kind: 'goto',
1410+
block: continuationBlock.id,
1411+
variant: GotoVariant.Break,
1412+
id: makeInstructionId(0),
1413+
loc: finalizerPath.node.loc ?? GeneratedSource,
1414+
};
1415+
});
1416+
}
14021417

14031418
const block = builder.enter('block', _blockId => {
14041419
const block = stmt.get('block');
1405-
builder.enterTryCatch(handler, () => {
1420+
// If there's a handler, exceptions go there; otherwise they propagate normally
1421+
if (handler !== null) {
1422+
builder.enterTryCatch(handler, () => {
1423+
lowerStatement(builder, block);
1424+
});
1425+
} else {
14061426
lowerStatement(builder, block);
1407-
});
1427+
}
14081428
return {
14091429
kind: 'goto',
14101430
block: continuationBlock.id,
@@ -1421,6 +1441,7 @@ function lowerStatement(
14211441
handlerBinding:
14221442
handlerBinding !== null ? {...handlerBinding.place} : null,
14231443
handler,
1444+
finalizer,
14241445
fallthrough: continuationBlock.id,
14251446
id: makeInstructionId(0),
14261447
loc: stmt.node.loc ?? GeneratedSource,

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,8 @@ export type ReactiveTryTerminal = {
273273
kind: 'try';
274274
block: ReactiveBlock;
275275
handlerBinding: Place | null;
276-
handler: ReactiveBlock;
276+
handler: ReactiveBlock | null;
277+
finalizer: ReactiveBlock | null;
277278
id: InstructionId;
278279
loc: SourceLocation;
279280
};
@@ -602,8 +603,8 @@ export type TryTerminal = {
602603
kind: 'try';
603604
block: BlockId;
604605
handlerBinding: Place | null;
605-
handler: BlockId;
606-
// TODO: support `finally`
606+
handler: BlockId | null;
607+
finalizer: BlockId | null;
607608
fallthrough: BlockId;
608609
id: InstructionId;
609610
loc: SourceLocation;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,7 @@ export function removeUnnecessaryTryCatch(fn: HIR): void {
954954
for (const [, block] of fn.blocks) {
955955
if (
956956
block.terminal.kind === 'try' &&
957+
block.terminal.handler !== null &&
957958
!fn.blocks.has(block.terminal.handler)
958959
) {
959960
const handlerId = block.terminal.handler;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,12 +310,14 @@ export function printTerminal(terminal: Terminal): Array<string> | string {
310310
break;
311311
}
312312
case 'try': {
313-
value = `[${terminal.id}] Try block=bb${terminal.block} handler=bb${
314-
terminal.handler
313+
value = `[${terminal.id}] Try block=bb${terminal.block}${
314+
terminal.handler !== null ? ` handler=bb${terminal.handler}` : ''
315315
}${
316316
terminal.handlerBinding !== null
317317
? ` handlerBinding=(${printPlace(terminal.handlerBinding)})`
318318
: ''
319+
}${
320+
terminal.finalizer !== null ? ` finalizer=bb${terminal.finalizer}` : ''
319321
} fallthrough=${
320322
terminal.fallthrough != null ? `bb${terminal.fallthrough}` : ''
321323
}`;

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -921,13 +921,17 @@ export function mapTerminalSuccessors(
921921
}
922922
case 'try': {
923923
const block = fn(terminal.block);
924-
const handler = fn(terminal.handler);
924+
const handler =
925+
terminal.handler !== null ? fn(terminal.handler) : null;
926+
const finalizer =
927+
terminal.finalizer !== null ? fn(terminal.finalizer) : null;
925928
const fallthrough = fn(terminal.fallthrough);
926929
return {
927930
kind: 'try',
928931
block,
929932
handlerBinding: terminal.handlerBinding,
930933
handler,
934+
finalizer,
931935
fallthrough,
932936
id: makeInstructionId(0),
933937
loc: terminal.loc,
@@ -1088,6 +1092,12 @@ export function* eachTerminalSuccessor(terminal: Terminal): Iterable<BlockId> {
10881092
}
10891093
case 'try': {
10901094
yield terminal.block;
1095+
if (terminal.handler !== null) {
1096+
yield terminal.handler;
1097+
}
1098+
if (terminal.finalizer !== null) {
1099+
yield terminal.finalizer;
1100+
}
10911101
break;
10921102
}
10931103
case 'scope':

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,11 @@ function inferBlock(
522522
instr.effects = effects;
523523
}
524524
const terminal = block.terminal;
525-
if (terminal.kind === 'try' && terminal.handlerBinding != null) {
525+
if (
526+
terminal.kind === 'try' &&
527+
terminal.handler !== null &&
528+
terminal.handlerBinding != null
529+
) {
526530
context.catchHandlers.set(terminal.handler, terminal.handlerBinding);
527531
} else if (terminal.kind === 'maybe-throw') {
528532
const handlerParam = context.catchHandlers.get(terminal.handler);

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -844,14 +844,21 @@ class Driver {
844844
const scheduleId = this.cx.schedule(fallthroughId, 'if');
845845
scheduleIds.push(scheduleId);
846846
}
847-
this.cx.scheduleCatchHandler(terminal.handler);
847+
if (terminal.handler !== null) {
848+
this.cx.scheduleCatchHandler(terminal.handler);
849+
}
848850

849851
const block = this.traverseBlock(
850852
this.cx.ir.blocks.get(terminal.block)!,
851853
);
852-
const handler = this.traverseBlock(
853-
this.cx.ir.blocks.get(terminal.handler)!,
854-
);
854+
const handler =
855+
terminal.handler !== null
856+
? this.traverseBlock(this.cx.ir.blocks.get(terminal.handler)!)
857+
: null;
858+
const finalizer =
859+
terminal.finalizer !== null
860+
? this.traverseBlock(this.cx.ir.blocks.get(terminal.finalizer)!)
861+
: null;
855862

856863
this.cx.unscheduleAll(scheduleIds);
857864
blockValue.push({
@@ -864,6 +871,7 @@ class Driver {
864871
block,
865872
handlerBinding: terminal.handlerBinding,
866873
handler,
874+
finalizer,
867875
id: terminal.id,
868876
},
869877
});

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,15 +1307,26 @@ function codegenTerminal(
13071307
return codegenBlock(cx, terminal.block);
13081308
}
13091309
case 'try': {
1310-
let catchParam = null;
1311-
if (terminal.handlerBinding !== null) {
1312-
catchParam = convertIdentifier(terminal.handlerBinding.identifier);
1313-
cx.temp.set(terminal.handlerBinding.identifier.declarationId, null);
1310+
let catchClause: t.CatchClause | null = null;
1311+
if (terminal.handler !== null) {
1312+
let catchParam = null;
1313+
if (terminal.handlerBinding !== null) {
1314+
catchParam = convertIdentifier(terminal.handlerBinding.identifier);
1315+
cx.temp.set(terminal.handlerBinding.identifier.declarationId, null);
1316+
}
1317+
catchClause = t.catchClause(catchParam, codegenBlock(cx, terminal.handler));
13141318
}
1319+
1320+
const finalizer =
1321+
terminal.finalizer !== null
1322+
? codegenBlock(cx, terminal.finalizer)
1323+
: null;
1324+
13151325
return createTryStatement(
13161326
terminal.loc,
13171327
codegenBlock(cx, terminal.block),
1318-
t.catchClause(catchParam, codegenBlock(cx, terminal.handler)),
1328+
catchClause,
1329+
finalizer,
13191330
);
13201331
}
13211332
default: {

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -395,14 +395,24 @@ function writeTerminal(writer: Writer, terminal: ReactiveTerminal): void {
395395
case 'try': {
396396
writer.writeLine(`[${terminal.id}] try {`);
397397
writeReactiveInstructions(writer, terminal.block);
398-
writer.write(`} catch `);
399-
if (terminal.handlerBinding !== null) {
400-
writer.writeLine(`(${printPlace(terminal.handlerBinding)}) {`);
401-
} else {
402-
writer.writeLine(`{`);
398+
if (terminal.handler !== null) {
399+
writer.write(`} catch `);
400+
if (terminal.handlerBinding !== null) {
401+
writer.writeLine(`(${printPlace(terminal.handlerBinding)}) {`);
402+
} else {
403+
writer.writeLine(`{`);
404+
}
405+
writeReactiveInstructions(writer, terminal.handler);
406+
writer.writeLine('}');
407+
}
408+
if (terminal.finalizer !== null) {
409+
writer.writeLine(`} finally {`);
410+
writeReactiveInstructions(writer, terminal.finalizer);
411+
writer.writeLine('}');
412+
}
413+
if (terminal.handler === null && terminal.finalizer === null) {
414+
writer.writeLine('}');
403415
}
404-
writeReactiveInstructions(writer, terminal.handler);
405-
writer.writeLine('}');
406416
break;
407417
}
408418
default:

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,12 @@ export class ReactiveFunctionVisitor<TState = void> {
169169
}
170170
case 'try': {
171171
this.visitBlock(terminal.block, state);
172-
this.visitBlock(terminal.handler, state);
172+
if (terminal.handler !== null) {
173+
this.visitBlock(terminal.handler, state);
174+
}
175+
if (terminal.finalizer !== null) {
176+
this.visitBlock(terminal.finalizer, state);
177+
}
173178
break;
174179
}
175180
default: {
@@ -559,7 +564,12 @@ export class ReactiveFunctionTransform<
559564
if (terminal.handlerBinding !== null) {
560565
this.visitPlace(terminal.id, terminal.handlerBinding, state);
561566
}
562-
this.visitBlock(terminal.handler, state);
567+
if (terminal.handler !== null) {
568+
this.visitBlock(terminal.handler, state);
569+
}
570+
if (terminal.finalizer !== null) {
571+
this.visitBlock(terminal.finalizer, state);
572+
}
563573
break;
564574
}
565575
default: {
@@ -653,7 +663,12 @@ export function mapTerminalBlocks(
653663
}
654664
case 'try': {
655665
terminal.block = fn(terminal.block);
656-
terminal.handler = fn(terminal.handler);
666+
if (terminal.handler !== null) {
667+
terminal.handler = fn(terminal.handler);
668+
}
669+
if (terminal.finalizer !== null) {
670+
terminal.finalizer = fn(terminal.finalizer);
671+
}
657672
break;
658673
}
659674
default: {

0 commit comments

Comments
 (0)