From e9cca30b184f0d1faac8ee70e75c62886a7fc3ad Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Fri, 23 Jan 2026 11:07:42 -0800 Subject: [PATCH] [compiler] Fix invariant error for optional chaining in try/catch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../src/Inference/DropManualMemoization.ts | 8 ++++ .../ValidateExhaustiveDependencies.ts | 8 ++++ ...ing-within-try-catch-in-callback.expect.md | 37 +++++++++++++++++++ ...l-chaining-within-try-catch-in-callback.js | 11 ++++++ ...ional-chaining-within-try-catch.expect.md} | 6 +-- ...odo-optional-chaining-within-try-catch.js} | 0 6 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-chaining-within-try-catch-in-callback.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-chaining-within-try-catch-in-callback.js rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{error.bug-invariant-unexpected-terminal-in-optional.expect.md => error.todo-optional-chaining-within-try-catch.expect.md} (58%) rename compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/{error.bug-invariant-unexpected-terminal-in-optional.js => error.todo-optional-chaining-within-try-catch.js} (100%) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts index 647562aee5..d6232a2fe8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts @@ -583,6 +583,14 @@ function findOptionalPlaces(fn: HIRFunction): Set { 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`, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts index e72de6c3a3..a5d4f75c85 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts @@ -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`, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-chaining-within-try-catch-in-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-chaining-within-try-catch-in-callback.expect.md new file mode 100644 index 0000000000..b450e1bf85 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-chaining-within-try-catch-in-callback.expect.md @@ -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 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-chaining-within-try-catch-in-callback.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-chaining-within-try-catch-in-callback.js new file mode 100644 index 0000000000..659602e8c1 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-chaining-within-try-catch-in-callback.js @@ -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; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-invariant-unexpected-terminal-in-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-chaining-within-try-catch.expect.md similarity index 58% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-invariant-unexpected-terminal-in-optional.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-chaining-within-try-catch.expect.md index 30b9514926..ba59f47826 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-invariant-unexpected-terminal-in-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-chaining-within-try-catch.expect.md @@ -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 {foo}; 5 | } catch { 6 | return null; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-invariant-unexpected-terminal-in-optional.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-chaining-within-try-catch.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-invariant-unexpected-terminal-in-optional.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-optional-chaining-within-try-catch.js