@samitouri / QOS-React / commits / 936b96b24f

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",