diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts index 234f97ea4b..d52d6b835a 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts @@ -913,6 +913,68 @@ function lowerStatement( case "EmptyStatement": { return; } + case "TryStatement": { + const stmt = stmtPath as NodePath; + const continuationBlock = builder.reserve("block"); + + const handlerPath = stmt.get("handler"); + // NOTE: null check is for older babel versions, the hasNode() check is for TS to refine the type + if (handlerPath.node == null || !handlerPath.hasNode()) { + builder.errors.push({ + reason: `(BuildHIR::lowerStatement) Handle TryStatement without a catch clause`, + severity: ErrorSeverity.Todo, + loc: stmt.node.loc ?? null, + suggestions: null, + }); + return; + } + const handler = builder.enter("block", (_blockId) => { + lowerStatement(builder, handlerPath.get("body")); + return { + kind: "goto", + block: continuationBlock.id, + variant: GotoVariant.Break, + id: makeInstructionId(0), + loc: handlerPath.node.loc ?? GeneratedSource, + }; + }); + if (stmt.get("finalizer").node != null) { + builder.errors.push({ + reason: `(BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause`, + severity: ErrorSeverity.Todo, + loc: stmt.node.loc ?? null, + suggestions: null, + }); + } + + const block = builder.enter("block", (_blockId) => { + const block = stmt.get("block"); + builder.enterTryCatch(handler, () => { + lowerStatement(builder, block); + }); + return { + kind: "goto", + block: continuationBlock.id, + variant: GotoVariant.Break, + id: makeInstructionId(0), + loc: block.node.loc ?? GeneratedSource, + }; + }); + + builder.terminateWithContinuation( + { + kind: "try", + block, + handler, + fallthrough: continuationBlock.id, + id: makeInstructionId(0), + loc: stmt.node.loc ?? GeneratedSource, + }, + continuationBlock + ); + + return; + } case "ForInStatement": case "ClassDeclaration": case "DeclareClass": @@ -932,7 +994,6 @@ function lowerStatement( case "ImportDeclaration": case "InterfaceDeclaration": case "OpaqueType": - case "TryStatement": case "TypeAlias": case "TSDeclareFunction": case "TSEnumDeclaration": diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts index fbfd9485d4..7399936ce4 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts @@ -79,7 +79,7 @@ export type Bindings = Map< // Determines how instructions should be constructed in order to preserve // exception semantics -type ExceptionsMode = +export type ExceptionsMode = // Mode used for code not covered by explicit exception handling, any // errors are assumed to be thrown out of the function | { kind: "ThrowExceptions" } @@ -158,6 +158,13 @@ export default class HIRBuilder { } } + enterTryCatch(handler: BlockId, fn: () => void): void { + const prevMode = this.#mode; + this.#mode = { kind: "CatchExceptions", handler }; + fn(); + this.#mode = prevMode; + } + makeTemporary(): Identifier { const id = this.nextIdentifierId; return { diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md new file mode 100644 index 0000000000..0feb08976e --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +function Component(props) { + const x = []; + try { + x.push(foo()); + } catch { + x.push(bar()); + } + return x; +} + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(3); + let x; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + x = []; + try { + let t0; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t0 = foo(); + $[1] = t0; + } else { + t0 = $[1]; + } + x.push(t0); + } catch { + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = bar(); + $[2] = t1; + } else { + t1 = $[2]; + } + x.push(t1); + } + $[0] = x; + } else { + x = $[0]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.js new file mode 100644 index 0000000000..813dea5736 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-mutate-outer-value.js @@ -0,0 +1,9 @@ +function Component(props) { + const x = []; + try { + x.push(foo()); + } catch { + x.push(bar()); + } + return x; +} diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-within-mutable-range.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-within-mutable-range.expect.md new file mode 100644 index 0000000000..d6cf282c57 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-within-mutable-range.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +function Component(props) { + const x = []; + try { + x.push(foo()); + } catch { + x.push(bar()); + } + x.push(props.value); // extend the mutable range to include the try/catch + return x; +} + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(4); + const c_0 = $[0] !== props.value; + let x; + if (c_0) { + x = []; + try { + let t0; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t0 = foo(); + $[2] = t0; + } else { + t0 = $[2]; + } + x.push(t0); + } catch { + let t1; + if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + t1 = bar(); + $[3] = t1; + } else { + t1 = $[3]; + } + x.push(t1); + } + + x.push(props.value); + $[0] = props.value; + $[1] = x; + } else { + x = $[1]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-within-mutable-range.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-within-mutable-range.js new file mode 100644 index 0000000000..665d395a94 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-within-mutable-range.js @@ -0,0 +1,10 @@ +function Component(props) { + const x = []; + try { + x.push(foo()); + } catch { + x.push(bar()); + } + x.push(props.value); // extend the mutable range to include the try/catch + return x; +} diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch.expect.md new file mode 100644 index 0000000000..c22421c742 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +function Component(props) { + let x; + try { + x = foo(); + } catch { + x = null; + } + return x; +} + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(1); + let x = undefined; + try { + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = foo(); + $[0] = t0; + } else { + t0 = $[0]; + } + x = t0; + } catch { + x = null; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch.js new file mode 100644 index 0000000000..d729c28bf9 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch.js @@ -0,0 +1,9 @@ +function Component(props) { + let x; + try { + x = foo(); + } catch { + x = null; + } + return x; +} diff --git a/compiler/packages/sprout/src/SproutTodoFilter.ts b/compiler/packages/sprout/src/SproutTodoFilter.ts index 1f4d4fb2d4..75988da6fe 100644 --- a/compiler/packages/sprout/src/SproutTodoFilter.ts +++ b/compiler/packages/sprout/src/SproutTodoFilter.ts @@ -412,6 +412,9 @@ const skipFilter = new Set([ "fbt-call-complex-param-value", "fbt-template-string-same-scope", "component-declaration-basic.flow", + "try-catch-mutate-outer-value", + "try-catch-within-mutable-range", + "try-catch", // TODO: 🌲 "forest-basic",