TryStatement: try terminal scaffolding
This PR adds the other piece, a 'try' terminal which represents try/catch and the possibility of fallthrough to the code afterwards. For now `finally` is unsupported. We don't yet produce these terminals, see later in the stack.
Joe Savona committed
Sep 7, 2023 at 14:22 UTC
7f5bac171eaef4a9576d25417916a2b014016625
11 files changed
+131
-2
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+20
-2
@@ -138,7 +138,8 @@ export type ReactiveTerminal =
138
| ReactiveForTerminal
139
| ReactiveForOfTerminal
140
| ReactiveIfTerminal
141
- | ReactiveLabelTerminal;
141
+ | ReactiveLabelTerminal
142
+ | ReactiveTryTerminal;
143
144
export type ReactiveBreakTerminal = {
145
kind: "break";
@@ -209,6 +210,12 @@ export type ReactiveLabelTerminal = {
210
block: ReactiveBlock;
211
id: InstructionId;
212
};
213
+export type ReactiveTryTerminal = {
214
+ kind: "try";
215
+ block: ReactiveBlock;
216
+ handler: ReactiveBlock;
217
+ id: InstructionId;
218
+};
219
220
/**
221
* A function lowered to HIR form, ie where its body is lowered to an HIR control-flow graph
@@ -280,7 +287,8 @@ export type Terminal =
287
| OptionalTerminal
288
| LabelTerminal
289
| SequenceTerminal
283
- | MaybeThrowTerminal;
290
+ | MaybeThrowTerminal
291
+ | TryTerminal;
292
293
function _staticInvariantTerminalHasLocation(
294
terminal: Terminal
@@ -445,6 +453,16 @@ export type SequenceTerminal = {
453
loc: SourceLocation;
454
};
455
456
+export type TryTerminal = {
457
+ kind: "try";
458
+ block: BlockId;
459
+ handler: BlockId;
460
+ // TODO: support `finally`
461
+ fallthrough: BlockId | null;
462
+ id: InstructionId;
463
+ loc: SourceLocation;
464
+};
465
+
466
export type MaybeThrowTerminal = {
467
kind: "maybe-throw";
468
continuation: BlockId;
compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts
+4
@@ -754,6 +754,10 @@ export function reversePostorderBlocks(func: HIR): void {
754
visit(terminal.continuation);
755
break;
756
}
757
+ case "try": {
758
+ visit(terminal.block);
759
+ break;
760
+ }
761
case "unsupported": {
762
break;
763
}
compiler/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts
+9
@@ -80,6 +80,7 @@ export function printMixedHIR(
80
return printInstruction(value);
81
}
82
switch (value.kind) {
83
+ case "try":
84
case "maybe-throw":
85
case "sequence":
86
case "label":
@@ -235,6 +236,14 @@ export function printTerminal(terminal: Terminal): Array<string> | string {
236
value = `MaybeThrow continuation=bb${terminal.continuation} handler=bb${terminal.handler}`;
237
break;
238
}
239
+ case "try": {
240
+ value = `Try block=bb${terminal.block} catch=bb${
241
+ terminal.handler
242
+ } fallthrough=${
243
+ terminal.fallthrough != null ? `bb${terminal.fallthrough}` : ""
244
+ }`;
245
+ break;
246
+ }
247
default: {
248
assertExhaustive(
249
terminal,
compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts
+29
@@ -742,6 +742,20 @@ export function mapTerminalSuccessors(
742
loc: terminal.loc,
743
};
744
}
745
+ case "try": {
746
+ const block = fn(terminal.block);
747
+ const handler = fn(terminal.handler);
748
+ const fallthrough =
749
+ terminal.fallthrough !== null ? fn(terminal.fallthrough) : null;
750
+ return {
751
+ kind: "try",
752
+ block,
753
+ handler,
754
+ fallthrough,
755
+ id: makeInstructionId(0),
756
+ loc: terminal.loc,
757
+ };
758
+ }
759
case "unsupported": {
760
return terminal;
761
}
@@ -769,6 +783,7 @@ export function terminalFallthrough(terminal: Terminal): BlockId | null {
783
case "unsupported": {
784
return null;
785
}
786
+ case "try":
787
case "do-while":
788
case "for-of":
789
case "for":
@@ -858,6 +873,14 @@ export function mapOptionalFallthroughs(
873
const _: BlockId = terminal.fallthrough;
874
break;
875
}
876
+ case "try": {
877
+ if (terminal.fallthrough !== null) {
878
+ terminal.fallthrough = fn(terminal.fallthrough);
879
+ } else {
880
+ terminal.fallthrough = null;
881
+ }
882
+ break;
883
+ }
884
default: {
885
assertExhaustive(
886
terminal,
@@ -935,6 +958,10 @@ export function* eachTerminalSuccessor(terminal: Terminal): Iterable<BlockId> {
958
yield terminal.handler;
959
break;
960
}
961
+ case "try": {
962
+ yield terminal.block;
963
+ break;
964
+ }
965
case "unsupported":
966
break;
967
default: {
@@ -974,6 +1001,7 @@ export function mapTerminalOperands(
1001
terminal.value = fn(terminal.value);
1002
break;
1003
}
1004
+ case "try":
1005
case "maybe-throw":
1006
case "sequence":
1007
case "label":
@@ -1023,6 +1051,7 @@ export function* eachTerminalOperand(terminal: Terminal): Iterable<Place> {
1051
yield terminal.value;
1052
break;
1053
}
1054
+ case "try":
1055
case "maybe-throw":
1056
case "sequence":
1057
case "label":
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts
+37
@@ -637,12 +637,49 @@ class Driver {
637
break;
638
}
639
case "maybe-throw": {
640
+ // ReactiveFunction does not explicit model maybe-throw semantics,
641
+ // so these terminals flatten away
642
this.visitBlock(
643
this.cx.ir.blocks.get(terminal.continuation)!,
644
blockValue
645
);
646
break;
647
}
648
+ case "try": {
649
+ const fallthroughId =
650
+ terminal.fallthrough !== null &&
651
+ !this.cx.isScheduled(terminal.fallthrough)
652
+ ? terminal.fallthrough
653
+ : null;
654
+ if (fallthroughId !== null) {
655
+ const scheduleId = this.cx.schedule(fallthroughId, "if");
656
+ scheduleIds.push(scheduleId);
657
+ }
658
+
659
+ const block = this.traverseBlock(
660
+ this.cx.ir.blocks.get(terminal.block)!
661
+ );
662
+ const handler = this.traverseBlock(
663
+ this.cx.ir.blocks.get(terminal.handler)!
664
+ );
665
+
666
+ this.cx.unscheduleAll(scheduleIds);
667
+ blockValue.push({
668
+ kind: "terminal",
669
+ label: fallthroughId,
670
+ terminal: {
671
+ kind: "try",
672
+ block,
673
+ handler,
674
+ id: terminal.id,
675
+ },
676
+ });
677
+
678
+ if (fallthroughId !== null) {
679
+ this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
680
+ }
681
+ break;
682
+ }
683
case "unsupported": {
684
CompilerError.invariant(false, {
685
reason: "Unexpected unsupported terminal",
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+6
@@ -515,6 +515,12 @@ function codegenTerminal(
515
case "label": {
516
return codegenBlock(cx, terminal.block);
517
}
518
+ case "try": {
519
+ return t.tryStatement(
520
+ codegenBlock(cx, terminal.block),
521
+ t.catchClause(null, codegenBlock(cx, terminal.handler))
522
+ );
523
+ }
524
default: {
525
assertExhaustive(
526
terminal,
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/FlattenReactiveLoops.ts
+1
@@ -54,6 +54,7 @@ class Transform extends ReactiveFunctionTransform<boolean> {
54
break;
55
}
56
// Non-loop terminals passthrough is contextual, inherits the parent isWithinScope
57
+ case "try":
58
case "label":
59
case "break":
60
case "continue":
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PrintReactiveFunction.ts
+8
@@ -276,6 +276,14 @@ function printTerminal(writer: Writer, terminal: ReactiveTerminal): void {
276
writer.writeLine("}");
277
break;
278
}
279
+ case "try": {
280
+ writer.writeLine(`[${terminal.id}] try {`);
281
+ printReactiveInstructions(writer, terminal.block);
282
+ writer.writeLine(`} catch {`);
283
+ printReactiveInstructions(writer, terminal.handler);
284
+ writer.writeLine("}");
285
+ break;
286
+ }
287
default:
288
assertExhaustive(terminal, `Unhandled terminal ${terminal}`);
289
}
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PropagateScopeDependencies.ts
+5
@@ -670,6 +670,11 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
670
this.visitBlock(terminal.block, context);
671
break;
672
}
673
+ case "try": {
674
+ this.visitBlock(terminal.block, context);
675
+ this.visitBlock(terminal.handler, context);
676
+ break;
677
+ }
678
default: {
679
assertExhaustive(
680
terminal,
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/visitors.ts
+10
@@ -151,6 +151,11 @@ export class ReactiveFunctionVisitor<TState = void> {
151
this.visitBlock(terminal.block, state);
152
break;
153
}
154
+ case "try": {
155
+ this.visitBlock(terminal.block, state);
156
+ this.visitBlock(terminal.handler, state);
157
+ break;
158
+ }
159
default: {
160
assertExhaustive(
161
terminal,
@@ -361,6 +366,11 @@ export function mapTerminalBlocks(
366
terminal.block = fn(terminal.block);
367
break;
368
}
369
+ case "try": {
370
+ terminal.block = fn(terminal.block);
371
+ terminal.handler = fn(terminal.handler);
372
+ break;
373
+ }
374
default: {
375
assertExhaustive(
376
terminal,
compiler/packages/babel-plugin-react-forget/src/Utils/VisualizeHIRMermaid.ts
+2
@@ -139,6 +139,7 @@ function printTerminalLabel(terminal: Terminal): string {
139
buffer.push("Sequence");
140
break;
141
}
142
+ case "try":
143
case "maybe-throw":
144
case "unsupported": {
145
break;
@@ -247,6 +248,7 @@ function printTerminalArrows(blockId: BlockId, terminal: Terminal): string {
248
buffer.push(printJumpArrow(blockId, terminal.fallthrough, "fallthrough"));
249
break;
250
}
251
+ case "try":
252
case "maybe-throw":
253
case "throw":
254
case "return":