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 2de4e3f054..6d15722873 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts @@ -199,6 +199,19 @@ function lowerStatement( case "ThrowStatement": { const stmt = stmtPath as NodePath; const value = lowerExpressionToTemporary(builder, stmt.get("argument")); + const handler = builder.resolveThrowHandler(); + if (handler != null) { + // NOTE: we could support this, but a `throw` inside try/catch is using exceptions + // for control-flow and is generally considered an anti-pattern. we can likely + // just not support this pattern, unless it really becomes necessary for some reason. + builder.errors.push({ + reason: + "(BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch", + severity: ErrorSeverity.Todo, + loc: stmt.node.loc ?? null, + suggestions: null, + }); + } const terminal: ThrowTerminal = { kind: "throw", value, 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 7399936ce4..ff91ae19ff 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts @@ -99,7 +99,7 @@ export default class HIRBuilder { #context: t.Identifier[]; #bindings: Bindings; #env: Environment; - #mode: ExceptionsMode = { kind: "ThrowExceptions" }; + #exceptionHandlerStack: Array = []; parentFunction: NodePath; errors: CompilerError = new CompilerError(); @@ -142,14 +142,14 @@ export default class HIRBuilder { */ push(instruction: Instruction): void { this.#current.instructions.push(instruction); - if (this.#mode.kind === "CatchExceptions") { - const handler = this.#mode.handler; + const exceptionHandler = this.#exceptionHandlerStack.at(-1); + if (exceptionHandler !== undefined) { const continuationBlock = this.reserve(this.currentBlockKind()); this.terminateWithContinuation( { kind: "maybe-throw", continuation: continuationBlock.id, - handler, + handler: exceptionHandler, id: makeInstructionId(0), loc: instruction.loc, }, @@ -159,10 +159,14 @@ export default class HIRBuilder { } enterTryCatch(handler: BlockId, fn: () => void): void { - const prevMode = this.#mode; - this.#mode = { kind: "CatchExceptions", handler }; + this.#exceptionHandlerStack.push(handler); fn(); - this.#mode = prevMode; + this.#exceptionHandlerStack.pop(); + } + + resolveThrowHandler(): BlockId | null { + const handler = this.#exceptionHandlerStack.at(-1); + return handler ?? null; } makeTemporary(): Identifier { diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo.try-catch-with-throw.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo.try-catch-with-throw.expect.md new file mode 100644 index 0000000000..685ba7d8db --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo.try-catch-with-throw.expect.md @@ -0,0 +1,24 @@ + +## Input + +```javascript +function Component(props) { + let x; + try { + throw []; + } catch (e) { + x.push(e); + } + return x; +} + +``` + + +## Error + +``` +[ReactForget] Todo: (BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch (4:4) +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo.try-catch-with-throw.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo.try-catch-with-throw.js new file mode 100644 index 0000000000..dc51780e8f --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo.try-catch-with-throw.js @@ -0,0 +1,9 @@ +function Component(props) { + let x; + try { + throw []; + } catch (e) { + x.push(e); + } + return x; +}