Skip to content

[compiler] Phase 8: Add multi-error test fixture and update plan#565

Closed
everettbu wants to merge 6 commits into
mainfrom
pr35845
Closed

[compiler] Phase 8: Add multi-error test fixture and update plan#565
everettbu wants to merge 6 commits into
mainfrom
pr35845

Conversation

@everettbu

Copy link
Copy Markdown

Mirror of facebook/react#35845
Original author: josephsavona


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.


Stack created with Sapling. Best reviewed with ReviewStack.

  • #35853
  • #35852
  • #35851
  • #35850
  • #35849
  • #35848
  • #35847
  • -> #35845
  • #35844
  • #35843
  • #35842
  • #35841
  • #35840

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.
@everettbu everettbu added CLA Signed React Core Team Opened by a member of the React Core Team labels Feb 21, 2026
@greptile-apps

greptile-apps Bot commented Feb 21, 2026

Copy link
Copy Markdown

Greptile Summary

This PR implements fault tolerance for the React Compiler (Phases 1, 2, 4-8 of the plan), enabling the compiler to continue past errors and report all compilation issues at once rather than bailing out at the first error.

  • Adds error accumulation infrastructure to Environment (recordError, tryRecord, hasErrors, aggregateErrors) that catches non-invariant CompilerErrors and records them for aggregated reporting
  • Changes Pipeline.ts to wrap all pass invocations in env.tryRecord(), converting the pipeline from fail-fast to fault-tolerant; return type changes from CodegenFunction to Result<CodegenFunction, CompilerError>
  • Updates all 12+ validation passes to record errors on env instead of returning Result or throwing, removing .unwrap() calls throughout the pipeline
  • Updates inference passes (inferMutationAliasingEffects, inferMutationAliasingRanges) and codegen to record errors instead of returning Results
  • Adds a new test fixture (error.fault-tolerance-reports-multiple-errors) demonstrating that both a mutation error and a ref-access error are reported from the same function
  • Updates ~26 existing test fixture expectations to reflect additional errors that are now surfaced (previously masked by early bailout)
  • Adds comprehensive plan document (fault-tolerance-overview.md) marking all phases complete with key learnings

Phase 3 (updating BuildHIR/lower to always produce HIR) is not included in this PR and remains for future work.

Confidence Score: 4/5

  • This PR is a well-structured refactor with comprehensive test coverage updates; the main risks are edge cases in fault-tolerant pass ordering.
  • The changes follow a clear, systematic pattern across all passes. The test fixture updates demonstrate the new behavior works as expected. Two minor concerns prevent a 5: (1) the definite assignment assertion on reactiveFunction could cause confusing runtime crashes if buildReactiveFunction fails, and (2) duplicate error logging via recordError + handleError may be unintentional. Both are unlikely to cause production issues given the existing invariant safety nets.
  • Pay close attention to Pipeline.ts (definite assignment assertion pattern for reactiveFunction) and Environment.ts (duplicate logging in recordError)

Important Files Changed

Filename Overview
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts Adds error accumulation infrastructure (#errors field, recordError, recordErrors, hasErrors, aggregateErrors, tryRecord). Core foundation for fault tolerance. The recordError method logs with fnLoc: null which causes duplicate logging when errors are also logged by the caller.
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts Changes return type to Result, wraps all pass calls in env.tryRecord(), removes .unwrap() calls, and adds final error check. Uses definite assignment assertions for value-producing passes wrapped in tryRecord.
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts Updates tryCompileFunction to handle Result from compileFn instead of relying solely on try/catch. Clean adaptation to the new API.
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts Changes codegenFunction and codegenReactiveFunction to return CodegenFunction directly instead of Result, recording errors on env. Records errors from emitInstrumentForget assertion failure.
compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts Changes return type from Result to void — errors are now recorded on env by callers or internally. Simple signature change.
compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts Changes return type from Result to Array, records errors on env when enableValidations is true. Always returns the function effects array.
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateContextVariableLValues.ts Changes from throwing CompilerError to recording errors on env via recordError. Returns early after recording Todo errors to avoid hitting invariant.
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccessInRender.ts Returns void instead of Result. Uses a separate innerErrors object for function expressions to determine ref-access effects without propagating inner errors to the outer scope.
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts Records errors on env instead of constructing and throwing CompilerError. Returns null after recording async reassignment error to avoid propagating a stale reassignment marker.
compiler/fault-tolerance-overview.md New comprehensive plan document describing all 8 phases of fault tolerance implementation. All phases marked complete with key learnings documented.
compiler/packages/babel-plugin-react-compiler/src/tests/fixtures/compiler/error.fault-tolerance-reports-multiple-errors.js New test fixture demonstrating fault tolerance: a component with both ref access during render and mutation of frozen props, verifying both errors are reported.

Last reviewed commit: cd20708

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

50 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +744 to +750
if (this.logger != null) {
this.logger.logEvent(this.filename, {
kind: 'CompileError',
detail: error,
fnLoc: null,
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate error logging with fnLoc: null

recordError logs each error to the logger with fnLoc: null. However, after compilation completes, Program.ts calls handleError/logError on the aggregated CompilerError, which logs each error again with the actual fnLoc. This causes duplicate log entries, as visible in the dynamic-gating-bailout-nopanic test fixture where the same PreserveManualMemo error now appears twice in the Logs section — once with "fnLoc":null and once with the real fnLoc.

This may be intentional (the early log provides immediate feedback during compilation, while the later one has the function location), but it's worth confirming this is the desired behavior, especially since consumers of the logger may not expect duplicates.

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:
**Duplicate error logging with `fnLoc: null`**

`recordError` logs each error to the logger with `fnLoc: null`. However, after compilation completes, `Program.ts` calls `handleError`/`logError` on the aggregated `CompilerError`, which logs each error *again* with the actual `fnLoc`. This causes duplicate log entries, as visible in the `dynamic-gating-bailout-nopanic` test fixture where the same `PreserveManualMemo` error now appears twice in the Logs section — once with `"fnLoc":null` and once with the real `fnLoc`.

This may be intentional (the early log provides immediate feedback during compilation, while the later one has the function location), but it's worth confirming this is the desired behavior, especially since consumers of the logger may not expect duplicates.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 461 to 471
@@ -415,116 +470,144 @@ function runWithEnvironment(

assertWellFormedBreakTargets(reactiveFunction);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definite assignment assertion may mask runtime errors

let reactiveFunction!: ReactiveFunction uses a definite assignment assertion (!), which tells TypeScript to trust that this variable will be assigned before use. If buildReactiveFunction throws a non-invariant CompilerError (caught by tryRecord), reactiveFunction remains undefined at runtime, but all subsequent code — including assertWellFormedBreakTargets(reactiveFunction), pruneUnusedLabels(reactiveFunction), and codegenFunction(reactiveFunction, ...) — will proceed with undefined, likely causing a confusing invariant or runtime crash rather than a clear error message.

The same pattern applies to fbtOperands and uniqueIdentifiers earlier in the function, though those use safe empty defaults (new Set()). Consider using a similar fallback pattern here, or checking if reactiveFunction is defined before proceeding with the reactive-function passes.

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: 461-471

Comment:
**Definite assignment assertion may mask runtime errors**

`let reactiveFunction!: ReactiveFunction` uses a definite assignment assertion (`!`), which tells TypeScript to trust that this variable will be assigned before use. If `buildReactiveFunction` throws a non-invariant `CompilerError` (caught by `tryRecord`), `reactiveFunction` remains `undefined` at runtime, but all subsequent code — including `assertWellFormedBreakTargets(reactiveFunction)`, `pruneUnusedLabels(reactiveFunction)`, and `codegenFunction(reactiveFunction, ...)` — will proceed with `undefined`, likely causing a confusing invariant or runtime crash rather than a clear error message.

The same pattern applies to `fbtOperands` and `uniqueIdentifiers` earlier in the function, though those use safe empty defaults (`new Set()`). Consider using a similar fallback pattern here, or checking if `reactiveFunction` is defined before proceeding with the reactive-function passes.

How can I resolve this? If you propose a fix, please make it concise.

@everettbu

Copy link
Copy Markdown
Author

Upstream PR was closed or merged. Code is synced via branch mirror.

@everettbu everettbu closed this Feb 23, 2026
@everettbu
everettbu deleted the pr35845 branch February 23, 2026 17:34
@everettbu
everettbu restored the pr35845 branch February 23, 2026 18:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed React Core Team Opened by a member of the React Core Team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants