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.
This commit is contained in:
Joe Savona
2023-09-07 16:32:54 -07:00
parent ff0b05848b
commit e3622ee413
4 changed files with 57 additions and 7 deletions
@@ -199,6 +199,19 @@ function lowerStatement(
case "ThrowStatement": {
const stmt = stmtPath as NodePath<t.ThrowStatement>;
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,
@@ -99,7 +99,7 @@ export default class HIRBuilder {
#context: t.Identifier[];
#bindings: Bindings;
#env: Environment;
#mode: ExceptionsMode = { kind: "ThrowExceptions" };
#exceptionHandlerStack: Array<BlockId> = [];
parentFunction: NodePath<t.Function>;
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 {
@@ -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)
```
@@ -0,0 +1,9 @@
function Component(props) {
let x;
try {
throw [];
} catch (e) {
x.push(e);
}
return x;
}