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 on
Confidence Score: 3/5
Important Files Changed
Last reviewed commit: b840c08 |
| let reactiveFunction!: ReactiveFunction; | ||
| env.tryRecord(() => { | ||
| reactiveFunction = buildReactiveFunction(hir); | ||
| }); |
There was a problem hiding this comment.
Unsafe definite assignment assertion on reactiveFunction
If buildReactiveFunction(hir) throws a non-invariant CompilerError (e.g. via CompilerError.throwTodo() for unsupported terminal kinds or labeled statements with value blocks), tryRecord will catch it and record the error, but reactiveFunction will never be assigned. The definite assignment assertion (!) tells TypeScript it's assigned, but at runtime it will be undefined.
All subsequent code — log() on line 537, assertWellFormedBreakTargets() on line 540, and every reactive pass after — would then crash with an unclear error about accessing properties of undefined, masking the original meaningful error.
Consider either:
- Adding an early return after this
tryRecordblock ifreactiveFunctionis still unassigned (similar to the codegen early-return pattern on lines 691-694), or - Initializing
reactiveFunctionwith a safe default before thetryRecordblock (as done forfbtOperandsanduniqueIdentifiers).
Prompt To Fix With AI
This is a comment left during a code review.
Path: compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
Line: 530-533
Comment:
**Unsafe definite assignment assertion on `reactiveFunction`**
If `buildReactiveFunction(hir)` throws a non-invariant `CompilerError` (e.g. via `CompilerError.throwTodo()` for unsupported terminal kinds or labeled statements with value blocks), `tryRecord` will catch it and record the error, but `reactiveFunction` will never be assigned. The definite assignment assertion (`!`) tells TypeScript it's assigned, but at runtime it will be `undefined`.
All subsequent code — `log()` on line 537, `assertWellFormedBreakTargets()` on line 540, and every reactive pass after — would then crash with an unclear error about accessing properties of `undefined`, masking the original meaningful error.
Consider either:
1. Adding an early return after this `tryRecord` block if `reactiveFunction` is still unassigned (similar to the codegen early-return pattern on lines 691-694), or
2. Initializing `reactiveFunction` with a safe default before the `tryRecord` block (as done for `fbtOperands` and `uniqueIdentifiers`).
How can I resolve this? If you propose a fix, please make it concise.| validateHooksUsage(hir); | ||
| } | ||
| if (env.config.validateNoCapitalizedCalls) { |
There was a problem hiding this comment.
transformFire errors masked by downstream invariant failures
Wrapping transformFire in tryRecord() records the original descriptive error (e.g. "fire() can only take in a single call expression") but allows execution to continue with a partially-transformed HIR. Later passes like InferMutationAliasingEffects then hit an invariant on the malformed IR, which re-throws and aborts the pipeline.
The result is that the user sees an opaque invariant error ("Expected value kind to be initialized") instead of the original actionable fire error. This is visible in multiple updated test fixtures (error.invalid-mix-fire-and-no-fire, error.invalid-multiple-args, error.invalid-not-call, error.invalid-spread, error.todo-method).
Passes that mutate the HIR and can leave it in an inconsistent state on failure (like transformFire) may need special handling — either an early return when errors are present, or not wrapping them in tryRecord() at all so the original error propagates cleanly.
Prompt To Fix With AI
This is a comment left during a code review.
Path: compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
Line: 230-232
Comment:
**`transformFire` errors masked by downstream invariant failures**
Wrapping `transformFire` in `tryRecord()` records the original descriptive error (e.g. "fire() can only take in a single call expression") but allows execution to continue with a partially-transformed HIR. Later passes like `InferMutationAliasingEffects` then hit an invariant on the malformed IR, which re-throws and aborts the pipeline.
The result is that the user sees an opaque invariant error ("Expected value kind to be initialized") instead of the original actionable fire error. This is visible in multiple updated test fixtures (`error.invalid-mix-fire-and-no-fire`, `error.invalid-multiple-args`, `error.invalid-not-call`, `error.invalid-spread`, `error.todo-method`).
Passes that mutate the HIR and can leave it in an inconsistent state on failure (like `transformFire`) may need special handling — either an early return when errors are present, or not wrapping them in `tryRecord()` at all so the original error propagates cleanly.
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#35831
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.