@samitouri / QOS-React-2 / commits / e3622ee413

TryStatement: disallow throw inside try/catch

Modeling `throw` inside of a try/catch is awkward because it's basically a variable reassignment and a goto together. Thankfully that is an antipattern — using exceptions instead of control-flow — so it seems pretty reasonable to just put a todo here and leave it.

Joe Savona committed Sep 7, 2023 at 16:32 UTC e3622ee41383c84982dd9ffcb79efe24e0a0d166
4 files changed +57 -7
compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts
+13
@@ -199,6 +199,19 @@ function lowerStatement(
199 case "ThrowStatement": {
200 const stmt = stmtPath as NodePath<t.ThrowStatement>;
201 const value = lowerExpressionToTemporary(builder, stmt.get("argument"));
202 + const handler = builder.resolveThrowHandler();
203 + if (handler != null) {
204 + // NOTE: we could support this, but a `throw` inside try/catch is using exceptions
205 + // for control-flow and is generally considered an anti-pattern. we can likely
206 + // just not support this pattern, unless it really becomes necessary for some reason.
207 + builder.errors.push({
208 + reason:
209 + "(BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch",
210 + severity: ErrorSeverity.Todo,
211 + loc: stmt.node.loc ?? null,
212 + suggestions: null,
213 + });
214 + }
215 const terminal: ThrowTerminal = {
216 kind: "throw",
217 value,
compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts
+11 -7
@@ -99,7 +99,7 @@ export default class HIRBuilder {
99 #context: t.Identifier[];
100 #bindings: Bindings;
101 #env: Environment;
102 - #mode: ExceptionsMode = { kind: "ThrowExceptions" };
102 + #exceptionHandlerStack: Array<BlockId> = [];
103 parentFunction: NodePath<t.Function>;
104 errors: CompilerError = new CompilerError();
105
@@ -142,14 +142,14 @@ export default class HIRBuilder {
142 */
143 push(instruction: Instruction): void {
144 this.#current.instructions.push(instruction);
145 - if (this.#mode.kind === "CatchExceptions") {
146 - const handler = this.#mode.handler;
145 + const exceptionHandler = this.#exceptionHandlerStack.at(-1);
146 + if (exceptionHandler !== undefined) {
147 const continuationBlock = this.reserve(this.currentBlockKind());
148 this.terminateWithContinuation(
149 {
150 kind: "maybe-throw",
151 continuation: continuationBlock.id,
152 - handler,
152 + handler: exceptionHandler,
153 id: makeInstructionId(0),
154 loc: instruction.loc,
155 },
@@ -159,10 +159,14 @@ export default class HIRBuilder {
159 }
160
161 enterTryCatch(handler: BlockId, fn: () => void): void {
162 - const prevMode = this.#mode;
163 - this.#mode = { kind: "CatchExceptions", handler };
162 + this.#exceptionHandlerStack.push(handler);
163 fn();
165 - this.#mode = prevMode;
164 + this.#exceptionHandlerStack.pop();
165 + }
166 +
167 + resolveThrowHandler(): BlockId | null {
168 + const handler = this.#exceptionHandlerStack.at(-1);
169 + return handler ?? null;
170 }
171
172 makeTemporary(): Identifier {
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo.try-catch-with-throw.expect.md new
+24
@@ -0,0 +1,24 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + let x;
7 + try {
8 + throw [];
9 + } catch (e) {
10 + x.push(e);
11 + }
12 + return x;
13 +}
14 +
15 +```
16 +
17 +
18 +## Error
19 +
20 +```
21 +[ReactForget] Todo: (BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch (4:4)
22 +```
23 +
24 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo.try-catch-with-throw.js new
+9
@@ -0,0 +1,9 @@
1 +function Component(props) {
2 + let x;
3 + try {
4 + throw [];
5 + } catch (e) {
6 + x.push(e);
7 + }
8 + return x;
9 +}