TryStatement: partial lowering
Implements the core lowering logic for try/catch: * Inside of the `try` block, we use the new HIRBuilder mode to wrap every instruction in a separate basic block with a maybe-throw terminal * We emit a 'try' terminal for the try/catch itself For basic examples this already works correctly. But we don't handle catch clause params yet.
Joe Savona committed
Sep 7, 2023 at 16:32 UTC
936b96b24f0225fa4e6359f79a9cf947887386ab
9 files changed
+252
-2
compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts
+62
-1
@@ -913,6 +913,68 @@ function lowerStatement(
913
case "EmptyStatement": {
914
return;
915
}
916
+ case "TryStatement": {
917
+ const stmt = stmtPath as NodePath<t.TryStatement>;
918
+ const continuationBlock = builder.reserve("block");
919
+
920
+ const handlerPath = stmt.get("handler");
921
+ // NOTE: null check is for older babel versions, the hasNode() check is for TS to refine the type
922
+ if (handlerPath.node == null || !handlerPath.hasNode()) {
923
+ builder.errors.push({
924
+ reason: `(BuildHIR::lowerStatement) Handle TryStatement without a catch clause`,
925
+ severity: ErrorSeverity.Todo,
926
+ loc: stmt.node.loc ?? null,
927
+ suggestions: null,
928
+ });
929
+ return;
930
+ }
931
+ const handler = builder.enter("block", (_blockId) => {
932
+ lowerStatement(builder, handlerPath.get("body"));
933
+ return {
934
+ kind: "goto",
935
+ block: continuationBlock.id,
936
+ variant: GotoVariant.Break,
937
+ id: makeInstructionId(0),
938
+ loc: handlerPath.node.loc ?? GeneratedSource,
939
+ };
940
+ });
941
+ if (stmt.get("finalizer").node != null) {
942
+ builder.errors.push({
943
+ reason: `(BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause`,
944
+ severity: ErrorSeverity.Todo,
945
+ loc: stmt.node.loc ?? null,
946
+ suggestions: null,
947
+ });
948
+ }
949
+
950
+ const block = builder.enter("block", (_blockId) => {
951
+ const block = stmt.get("block");
952
+ builder.enterTryCatch(handler, () => {
953
+ lowerStatement(builder, block);
954
+ });
955
+ return {
956
+ kind: "goto",
957
+ block: continuationBlock.id,
958
+ variant: GotoVariant.Break,
959
+ id: makeInstructionId(0),
960
+ loc: block.node.loc ?? GeneratedSource,
961
+ };
962
+ });
963
+
964
+ builder.terminateWithContinuation(
965
+ {
966
+ kind: "try",
967
+ block,
968
+ handler,
969
+ fallthrough: continuationBlock.id,
970
+ id: makeInstructionId(0),
971
+ loc: stmt.node.loc ?? GeneratedSource,
972
+ },
973
+ continuationBlock
974
+ );
975
+
976
+ return;
977
+ }
978
case "ForInStatement":
979
case "ClassDeclaration":
980
case "DeclareClass":
@@ -932,7 +994,6 @@ function lowerStatement(
994
case "ImportDeclaration":
995
case "InterfaceDeclaration":
996
case "OpaqueType":
935
- case "TryStatement":
997
case "TypeAlias":
998
case "TSDeclareFunction":
999
case "TSEnumDeclaration":
compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts
+8
-1
@@ -79,7 +79,7 @@ export type Bindings = Map<
79
80
// Determines how instructions should be constructed in order to preserve
81
// exception semantics
82
-type ExceptionsMode =
82
+export type ExceptionsMode =
83
// Mode used for code not covered by explicit exception handling, any
84
// errors are assumed to be thrown out of the function
85
| { kind: "ThrowExceptions" }
@@ -158,6 +158,13 @@ export default class HIRBuilder {
158
}
159
}
160
161
+ enterTryCatch(handler: BlockId, fn: () => void): void {
162
+ const prevMode = this.#mode;
163
+ this.#mode = { kind: "CatchExceptions", handler };
164
+ fn();
165
+ this.#mode = prevMode;
166
+ }
167
+
168
makeTemporary(): Identifier {
169
const id = this.nextIdentifierId;
170
return {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md
new
+53
@@ -0,0 +1,53 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ const x = [];
7
+ try {
8
+ x.push(foo());
9
+ } catch {
10
+ x.push(bar());
11
+ }
12
+ return x;
13
+}
14
+
15
+```
16
+
17
+## Code
18
+
19
+```javascript
20
+import { unstable_useMemoCache as useMemoCache } from "react";
21
+function Component(props) {
22
+ const $ = useMemoCache(3);
23
+ let x;
24
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
25
+ x = [];
26
+ try {
27
+ let t0;
28
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
29
+ t0 = foo();
30
+ $[1] = t0;
31
+ } else {
32
+ t0 = $[1];
33
+ }
34
+ x.push(t0);
35
+ } catch {
36
+ let t1;
37
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
38
+ t1 = bar();
39
+ $[2] = t1;
40
+ } else {
41
+ t1 = $[2];
42
+ }
43
+ x.push(t1);
44
+ }
45
+ $[0] = x;
46
+ } else {
47
+ x = $[0];
48
+ }
49
+ return x;
50
+}
51
+
52
+```
53
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.js
new
+9
@@ -0,0 +1,9 @@
1
+function Component(props) {
2
+ const x = [];
3
+ try {
4
+ x.push(foo());
5
+ } catch {
6
+ x.push(bar());
7
+ }
8
+ return x;
9
+}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-within-mutable-range.expect.md
new
+58
@@ -0,0 +1,58 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ const x = [];
7
+ try {
8
+ x.push(foo());
9
+ } catch {
10
+ x.push(bar());
11
+ }
12
+ x.push(props.value); // extend the mutable range to include the try/catch
13
+ return x;
14
+}
15
+
16
+```
17
+
18
+## Code
19
+
20
+```javascript
21
+import { unstable_useMemoCache as useMemoCache } from "react";
22
+function Component(props) {
23
+ const $ = useMemoCache(4);
24
+ const c_0 = $[0] !== props.value;
25
+ let x;
26
+ if (c_0) {
27
+ x = [];
28
+ try {
29
+ let t0;
30
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
31
+ t0 = foo();
32
+ $[2] = t0;
33
+ } else {
34
+ t0 = $[2];
35
+ }
36
+ x.push(t0);
37
+ } catch {
38
+ let t1;
39
+ if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
40
+ t1 = bar();
41
+ $[3] = t1;
42
+ } else {
43
+ t1 = $[3];
44
+ }
45
+ x.push(t1);
46
+ }
47
+
48
+ x.push(props.value);
49
+ $[0] = props.value;
50
+ $[1] = x;
51
+ } else {
52
+ x = $[1];
53
+ }
54
+ return x;
55
+}
56
+
57
+```
58
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-within-mutable-range.js
new
+10
@@ -0,0 +1,10 @@
1
+function Component(props) {
2
+ const x = [];
3
+ try {
4
+ x.push(foo());
5
+ } catch {
6
+ x.push(bar());
7
+ }
8
+ x.push(props.value); // extend the mutable range to include the try/catch
9
+ return x;
10
+}
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch.expect.md
new
+40
@@ -0,0 +1,40 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function Component(props) {
6
+ let x;
7
+ try {
8
+ x = foo();
9
+ } catch {
10
+ x = null;
11
+ }
12
+ return x;
13
+}
14
+
15
+```
16
+
17
+## Code
18
+
19
+```javascript
20
+import { unstable_useMemoCache as useMemoCache } from "react";
21
+function Component(props) {
22
+ const $ = useMemoCache(1);
23
+ let x = undefined;
24
+ try {
25
+ let t0;
26
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
27
+ t0 = foo();
28
+ $[0] = t0;
29
+ } else {
30
+ t0 = $[0];
31
+ }
32
+ x = t0;
33
+ } catch {
34
+ x = null;
35
+ }
36
+ return x;
37
+}
38
+
39
+```
40
+
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch.js
new
+9
@@ -0,0 +1,9 @@
1
+function Component(props) {
2
+ let x;
3
+ try {
4
+ x = foo();
5
+ } catch {
6
+ x = null;
7
+ }
8
+ return x;
9
+}
compiler/packages/sprout/src/SproutTodoFilter.ts
+3
@@ -412,6 +412,9 @@ const skipFilter = new Set([
412
"fbt-call-complex-param-value",
413
"fbt-template-string-same-scope",
414
"component-declaration-basic.flow",
415
+ "try-catch-mutate-outer-value",
416
+ "try-catch-within-mutable-range",
417
+ "try-catch",
418
419
// TODO: 🌲
420
"forest-basic",