mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
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:
@@ -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 {
|
||||
|
||||
+24
@@ -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)
|
||||
```
|
||||
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
function Component(props) {
|
||||
let x;
|
||||
try {
|
||||
throw [];
|
||||
} catch (e) {
|
||||
x.push(e);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
Reference in New Issue
Block a user