Conversation
Add detailed plan for making the React Compiler fault-tolerant by accumulating errors across all passes instead of stopping at the first error. This enables reporting multiple compilation errors at once.
Add error accumulation methods to the Environment class: - #errors field to accumulate CompilerErrors across passes - recordError() to record a single diagnostic (throws if Invariant) - recordErrors() to record all diagnostics from a CompilerError - hasErrors() to check if any errors have been recorded - aggregateErrors() to retrieve the accumulated CompilerError - tryRecord() to wrap callbacks and catch CompilerErrors
…erance - Change runWithEnvironment/run/compileFn to return Result<CodegenFunction, CompilerError> - Wrap all pipeline passes in env.tryRecord() to catch and record CompilerErrors - Record inference pass errors via env.recordErrors() instead of throwing - Handle codegen Result explicitly, returning Err on failure - Add final error check: return Err(env.aggregateErrors()) if any errors accumulated - Update tryCompileFunction and retryCompileFunction in Program.ts to handle Result - Keep lint-only passes using env.logErrors() (non-blocking) - Update 52 test fixture expectations that now report additional errors This is the core integration that enables fault tolerance: errors are caught, recorded, and the pipeline continues to discover more errors.
…rs on env Update 9 validation passes to record errors directly on fn.env instead of returning Result<void, CompilerError>: - validateHooksUsage - validateNoCapitalizedCalls (also changed throwInvalidReact to recordError) - validateUseMemo - dropManualMemoization - validateNoRefAccessInRender - validateNoSetStateInRender - validateNoImpureFunctionsInRender - validateNoFreezingKnownMutableFunctions - validateExhaustiveDependencies Each pass now calls fn.env.recordErrors() instead of returning errors.asResult(). Pipeline.ts call sites updated to remove tryRecord() wrappers and .unwrap().
Greptile SummaryThis PR implements Phase 4 (batch 1) of the React Compiler fault tolerance plan, updating 9 validation passes to record errors directly on
Confidence Score: 4/5
Important Files Changed
Last reviewed commit: 821d6a8 |
| if (this.logger != null) { | ||
| this.logger.logEvent(this.filename, { | ||
| kind: 'CompileError', | ||
| detail: error, | ||
| fnLoc: null, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Double-logging of compilation errors
recordError logs each error with fnLoc: null here, and then when the pipeline returns Err(aggregateErrors()), Program.ts's logError/handleError iterates through the same error details and logs them again (with the proper fnLoc). This is visible in the dynamic-gating-bailout-nopanic test fixture, which now has a duplicate log entry — the first with "fnLoc":null and the second with the correct fnLoc.
This may be intentional during the transition period, but it could confuse consumers of the logger output. Consider either:
- Deferring the logging here and letting
Program.tshandle all logging at the end, or - Skipping the re-logging in
Program.tsfor errors that were already recorded on env
Prompt To Fix With AI
This is a comment left during a code review.
Path: compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
Line: 744-750
Comment:
**Double-logging of compilation errors**
`recordError` logs each error with `fnLoc: null` here, and then when the pipeline returns `Err(aggregateErrors())`, `Program.ts`'s `logError`/`handleError` iterates through the same error details and logs them again (with the proper `fnLoc`). This is visible in the `dynamic-gating-bailout-nopanic` test fixture, which now has a duplicate log entry — the first with `"fnLoc":null` and the second with the correct `fnLoc`.
This may be intentional during the transition period, but it could confuse consumers of the logger output. Consider either:
- Deferring the logging here and letting `Program.ts` handle all logging at the end, or
- Skipping the re-logging in `Program.ts` for errors that were already recorded on env
How can I resolve this? If you propose a fix, please make it concise.| Found 1 error: | ||
|
|
||
| Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) | ||
| Invariant: Unexpected empty block with `goto` terminal | ||
|
|
||
| error.invalid-hook-for.ts:4:9 | ||
| 2 | let i = 0; | ||
| 3 | for (let x = 0; useHook(x) < 10; useHook(i), x++) { | ||
| > 4 | i += useHook(x); | ||
| | ^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) | ||
| 5 | } | ||
| 6 | return i; | ||
| 7 | } | ||
| Block bb5 is empty. | ||
|
|
||
| Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) | ||
|
|
||
| error.invalid-hook-for.ts:3:35 | ||
| error.invalid-hook-for.ts:3:2 | ||
| 1 | function Component(props) { | ||
| 2 | let i = 0; | ||
| > 3 | for (let x = 0; useHook(x) < 10; useHook(i), x++) { | ||
| | ^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) | ||
| 4 | i += useHook(x); | ||
| 5 | } | ||
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| > 4 | i += useHook(x); | ||
| | ^^^^^^^^^^^^^^^^^^^^ | ||
| > 5 | } | ||
| | ^^^^ Unexpected empty block with `goto` terminal | ||
| 6 | return i; | ||
| 7 | } | ||
| 8 | | ||
| ``` |
There was a problem hiding this comment.
User-facing hook errors replaced by internal invariant
This test previously reported 2 clear "Hooks must always be called in a consistent order" errors but now reports an internal invariant ("Unexpected empty block with goto terminal") from BuildReactiveFunction. This happens because validateHooksUsage now records its errors on env instead of throwing, allowing the pipeline to continue with malformed HIR (hooks in a for-loop), which triggers the invariant in a later pass.
While this is a known trade-off of the phased fault tolerance approach, this particular case is a regression in error quality for end users — they lose the actionable hook validation message and instead see an opaque internal error. Is this expected to be resolved in a later phase (e.g., when BuildReactiveFunction is made more resilient)?
Prompt To Fix With AI
This is a comment left during a code review.
Path: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-for.expect.md
Line: 19-37
Comment:
**User-facing hook errors replaced by internal invariant**
This test previously reported 2 clear "Hooks must always be called in a consistent order" errors but now reports an internal invariant ("Unexpected empty block with `goto` terminal") from `BuildReactiveFunction`. This happens because `validateHooksUsage` now records its errors on env instead of throwing, allowing the pipeline to continue with malformed HIR (hooks in a for-loop), which triggers the invariant in a later pass.
While this is a known trade-off of the phased fault tolerance approach, this particular case is a regression in error quality for end users — they lose the actionable hook validation message and instead see an opaque internal error. Is this expected to be resolved in a later phase (e.g., when `BuildReactiveFunction` is made more resilient)?
How can I resolve this? If you propose a fix, please make it concise.
Additional Comments (1)
Prompt To Fix With AIThis is a comment left during a code review.
Path: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.dont-hoist-inline-reference.expect.md
Line: 22-36
Comment:
**User-facing Todo error replaced by internal invariant**
Similar to the `error.invalid-hook-for` case: the original Todo error from `enterSSA` ("[hoisting] EnterSSA: Expected identifier to be defined before being used") is now recorded via `tryRecord`, but the pipeline continues with corrupted state, causing an invariant in `InferMutationAliasingEffects`. The user now sees "Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized" instead of the more descriptive original error. Is this expected to be addressed in a later phase?
How can I resolve this? If you propose a fix, please make it concise. |
|
Upstream PR was closed or merged. Code is synced via branch mirror. |
Mirror of facebook/react#35843
Original author: josephsavona
Update 9 validation passes to record errors directly on fn.env instead of
returning Result<void, CompilerError>:
Each pass now calls fn.env.recordErrors() instead of returning errors.asResult().
Pipeline.ts call sites updated to remove tryRecord() wrappers and .unwrap().
Stack created with Sapling. Best reviewed with ReviewStack.