[compiler] Fix invariant error for optional chaining in try/catch

Optional chaining and other value blocks within try/catch blocks were throwing an Invariant error ("Unexpected terminal in optional") instead of the expected Todo error. This caused hard failures instead of graceful bailouts.

The issue occurred because DropManualMemoization and ValidateExhaustiveDependencies ran before BuildReactiveFunction, and encountered `maybe-throw` terminals in their switch statements without a handler, falling through to the invariant case.

Fixed by adding explicit `maybe-throw` cases in both files that throw the proper Todo error with the message "Support value blocks (conditional, logical, optional chaining, etc) within a try/catch statement".

Also renamed the existing bug test to reflect it's now correctly handled:
- error.bug-invariant-unexpected-terminal-in-optional → error.todo-optional-chaining-within-try-catch

Closes #35570
This commit is contained in:
Joe Savona
2026-01-23 11:07:42 -08:00
parent ff39247ee0
commit e9cca30b18
6 changed files with 67 additions and 3 deletions

View File

@@ -583,6 +583,14 @@ function findOptionalPlaces(fn: HIRFunction): Set<IdentifierId> {
testBlock = fn.body.blocks.get(terminal.fallthrough)!;
break;
}
case 'maybe-throw': {
CompilerError.throwTodo({
reason: `Support value blocks (conditional, logical, optional chaining, etc) within a try/catch statement`,
description: null,
loc: terminal.loc,
suggestions: null,
});
}
default: {
CompilerError.invariant(false, {
reason: `Unexpected terminal in optional`,

View File

@@ -1016,6 +1016,14 @@ export function findOptionalPlaces(
testBlock = fn.body.blocks.get(terminal.block)!;
break;
}
case 'maybe-throw': {
CompilerError.throwTodo({
reason: `Support value blocks (conditional, logical, optional chaining, etc) within a try/catch statement`,
description: null,
loc: terminal.loc,
suggestions: null,
});
}
default: {
CompilerError.simpleInvariant(false, {
reason: `Unexpected terminal in optional`,

View File

@@ -0,0 +1,37 @@
## Input
```javascript
function Component(props) {
const callback = async () => {
try {
const result = await fetch(props.url);
return result?.error;
} catch (e) {
return null;
}
};
return callback;
}
```
## Error
```
Found 1 error:
Todo: Support value blocks (conditional, logical, optional chaining, etc) within a try/catch statement
error.todo-optional-chaining-within-try-catch-in-callback.ts:5:13
3 | try {
4 | const result = await fetch(props.url);
> 5 | return result?.error;
| ^^^^^^ Support value blocks (conditional, logical, optional chaining, etc) within a try/catch statement
6 | } catch (e) {
7 | return null;
8 | }
```

View File

@@ -0,0 +1,11 @@
function Component(props) {
const callback = async () => {
try {
const result = await fetch(props.url);
return result?.error;
} catch (e) {
return null;
}
};
return callback;
}

View File

@@ -19,13 +19,13 @@ const Foo = ({json}) => {
```
Found 1 error:
Invariant: Unexpected terminal in optional
Todo: Support value blocks (conditional, logical, optional chaining, etc) within a try/catch statement
error.bug-invariant-unexpected-terminal-in-optional.ts:3:16
error.todo-optional-chaining-within-try-catch.ts:3:16
1 | const Foo = ({json}) => {
2 | try {
> 3 | const foo = JSON.parse(json)?.foo;
| ^^^^ Unexpected maybe-throw in optional
| ^^^^ Support value blocks (conditional, logical, optional chaining, etc) within a try/catch statement
4 | return <span>{foo}</span>;
5 | } catch {
6 | return null;