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().
… tolerance Update remaining validation passes to record errors on env: - validateMemoizedEffectDependencies - validatePreservedManualMemoization - validateSourceLocations (added env parameter) - validateContextVariableLValues (changed throwTodo to recordError) - validateLocalsNotReassignedAfterRender (changed throw to recordError) - validateNoDerivedComputationsInEffects (changed throw to recordError) Update inference passes: - inferMutationAliasingEffects: return void, errors on env - inferMutationAliasingRanges: return Array<AliasingEffect> directly, errors on env Update codegen: - codegenFunction: return CodegenFunction directly, errors on env - codegenReactiveFunction: same pattern Update Pipeline.ts to call all passes directly without tryRecord/unwrap. Also update AnalyseFunctions.ts which called inferMutationAliasingRanges.
Add test fixture demonstrating fault tolerance: the compiler now reports both a mutation error and a ref access error in the same function, where previously only one would be reported before bailing out. Update plan doc to mark all phases as complete.
…ing throws Remove `tryRecord()` from the compilation pipeline now that all passes record errors directly via `env.recordError()` / `env.recordErrors()`. A single catch-all try/catch in Program.ts provides the safety net for any pass that incorrectly throws instead of recording. Key changes: - Remove all ~64 `env.tryRecord()` wrappers in Pipeline.ts - Delete `tryRecord()` method from Environment.ts - Add `CompileUnexpectedThrow` logger event so thrown errors are detectable - Log `CompileUnexpectedThrow` in Program.ts catch-all for non-invariant throws - Fail snap tests on `CompileUnexpectedThrow` to surface pass bugs in dev - Convert throwTodo/throwDiagnostic calls in HIRBuilder (fbt, this), CodegenReactiveFunction (for-in/for-of), and BuildReactiveFunction to record errors or use invariants as appropriate - Remove try/catch from BuildHIR's lower() since inner throws are now recorded - CollectOptionalChainDependencies: return null instead of throwing on unsupported optional chain patterns (graceful optimization skip)
Greptile SummaryThis PR completes the fault tolerance refactor for the React Compiler by removing all ~64
Confidence Score: 4/5
Important Files Changed
Last reviewed commit: 6163309 |
| kind: 'err', | ||
| msg: | ||
| `Compiler pass(es) threw instead of recording errors:\n` + | ||
| unexpectedThrows.map(l => (l.event as any).data).join('\n'), |
There was a problem hiding this comment.
Unsafe as any type cast
The (l.event as any).data cast bypasses type safety when the type could be narrowed from the filter. Consider using a type guard or asserting the narrowed event type directly:
| unexpectedThrows.map(l => (l.event as any).data).join('\n'), | |
| unexpectedThrows.map(l => (l.event as CompileUnexpectedThrowEvent).data).join('\n'), |
Alternatively, a type predicate on the filter would avoid the cast entirely.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: compiler/packages/snap/src/compiler.ts
Line: 389
Comment:
**Unsafe `as any` type cast**
The `(l.event as any).data` cast bypasses type safety when the type could be narrowed from the filter. Consider using a type guard or asserting the narrowed event type directly:
```suggestion
unexpectedThrows.map(l => (l.event as CompileUnexpectedThrowEvent).data).join('\n'),
```
Alternatively, a type predicate on the filter would avoid the cast entirely.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.| const errors = new CompilerError(); | ||
| visitReactiveFunction(fn, new Visitor(), errors); | ||
| return errors.asResult(); | ||
| fn.env.recordErrors(errors); |
There was a problem hiding this comment.
Unconditional recordErrors call
This calls fn.env.recordErrors(errors) without checking errors.hasAnyErrors() first. While harmless (empty details array is a no-op), it's inconsistent with the pattern used in most other validators (e.g., ValidateHooksUsage, ValidateUseMemo, ValidateNoCapitalizedCalls). The same inconsistency also appears in ValidatePreservedManualMemoization, ValidateNoDerivedComputationsInEffects, and ValidateSourceLocations.
| fn.env.recordErrors(errors); | |
| if (errors.hasAnyErrors()) { | |
| fn.env.recordErrors(errors); | |
| } |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts
Line: 54
Comment:
**Unconditional `recordErrors` call**
This calls `fn.env.recordErrors(errors)` without checking `errors.hasAnyErrors()` first. While harmless (empty details array is a no-op), it's inconsistent with the pattern used in most other validators (e.g., `ValidateHooksUsage`, `ValidateUseMemo`, `ValidateNoCapitalizedCalls`). The same inconsistency also appears in `ValidatePreservedManualMemoization`, `ValidateNoDerivedComputationsInEffects`, and `ValidateSourceLocations`.
```suggestion
if (errors.hasAnyErrors()) {
fn.env.recordErrors(errors);
}
```
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
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#35837
Original author: josephsavona
Remove
tryRecord()from the compilation pipeline now that all passes recorderrors directly via
env.recordError()/env.recordErrors(). A singlecatch-all try/catch in Program.ts provides the safety net for any pass that
incorrectly throws instead of recording.
Key changes:
env.tryRecord()wrappers in Pipeline.tstryRecord()method from Environment.tsCompileUnexpectedThrowlogger event so thrown errors are detectableCompileUnexpectedThrowin Program.ts catch-all for non-invariant throwsCompileUnexpectedThrowto surface pass bugs in devCodegenReactiveFunction (for-in/for-of), and BuildReactiveFunction to
record errors or use invariants as appropriate
unsupported optional chain patterns (graceful optimization skip)
Stack created with Sapling. Best reviewed with ReviewStack.