Skip to content

[compiler] Phase 4 (batch 2), 5, 6: Update remaining passes for fault tolerance#593

Closed
everettbu wants to merge 1 commit into
mainfrom
pr35876
Closed

[compiler] Phase 4 (batch 2), 5, 6: Update remaining passes for fault tolerance#593
everettbu wants to merge 1 commit into
mainfrom
pr35876

Conversation

@everettbu

@everettbu everettbu commented Feb 23, 2026

Copy link
Copy Markdown

Mirror of facebook/react#35876
Original author: josephsavona


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 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.


Stack created with Sapling. Best reviewed with ReviewStack.

  • #35888
  • #35884
  • #35883
  • #35882
  • #35881
  • #35880
  • #35879
  • #35878
  • #35877
  • -> #35876
  • #35875
  • #35874

@everettbu everettbu added CLA Signed React Core Team Opened by a member of the React Core Team labels Feb 23, 2026
@greptile-apps

greptile-apps Bot commented Feb 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR continues the fault tolerance migration (phases 4 batch 2, 5, and 6) by converting remaining validation passes, inference passes, and codegen to record errors on Environment instead of throwing or returning Result types. The pipeline in Pipeline.ts is simplified by removing tryRecord() wrappers and Result unwrapping, relying on the final env.hasErrors() check to aggregate all accumulated errors.

  • Validation passes (validateContextVariableLValues, validateLocalsNotReassignedAfterRender, validateNoDerivedComputationsInEffects, validatePreservedManualMemoization, validateSourceLocations) converted from throwing/returning Result to recording errors directly on env
  • Inference passes (inferMutationAliasingEffects, inferMutationAliasingRanges) now return values directly and record errors on fn.env
  • Codegen (codegenFunction, codegenReactiveFunction) returns CodegenFunction directly instead of Result<CodegenFunction>, recording errors on env
  • Pipeline.ts simplified to call all passes directly; the enableValidations guard for inferMutationAliasingRanges errors moved from the call site into the function itself
  • One test expectation updated to reflect a newly-surfaced duplicate error that was previously masked by early termination via throw

Confidence Score: 4/5

  • This PR is a well-structured continuation of the fault tolerance migration with consistent patterns and low risk of behavioral regression.
  • The changes follow an established pattern from earlier PRs in the stack, converting throw/Result-based error handling to env.recordError() accumulation. The final env.hasErrors() check in Pipeline.ts ensures errors still propagate correctly. The only minor concerns are a style inconsistency (fn.env vs env parameter) and a newly-surfaced duplicate error in test output.
  • ValidateContextVariableLValues.ts has a minor inconsistency using fn.env instead of the passed env parameter. The test expectation change in error.todo-reassign-const.expect.md shows a duplicate error that may warrant deduplication.

Important Files Changed

Filename Overview
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts Removes tryRecord() wrappers and Result unwrapping for validation/inference/codegen passes that now record errors directly on env. Final env.hasErrors() check at end of pipeline ensures errors still propagate.
compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts Changes return type from Result<Array<AliasingEffect>> to Array<AliasingEffect> directly. Errors are now recorded on fn.env with an enableValidations guard moved in from the call site.
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts Converts codegenFunction and codegenReactiveFunction from Result<CodegenFunction> to returning CodegenFunction directly. Errors are recorded on fn.env instead of returned as Err. Recursive/outlined codegen calls also unwrapped.
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateContextVariableLValues.ts Converts throwTodo calls to env.recordError() with CompilerDiagnostic.create(). Adds env parameter threading. Minor inconsistency: default branch uses fn.env instead of the passed env parameter.
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts Replaces throw errors with env.recordError(). Async reassignment case now returns null instead of throwing, preventing further propagation after recording the error.
compiler/packages/babel-plugin-react-compiler/src/tests/fixtures/compiler/error.todo-reassign-const.expect.md Test expectation updated from 2 to 3 errors — a duplicate "Support destructuring of context variables" error now surfaces because recordError continues execution instead of throwing.

Last reviewed commit: 90e0b7b

@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.

48 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +461 to +464
let reactiveFunction!: ReactiveFunction;
env.tryRecord(() => {
reactiveFunction = buildReactiveFunction(hir);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Uninitialized variable on caught error

If buildReactiveFunction(hir) throws a non-invariant CompilerError (e.g. a throwTodo for unsupported terminal kinds), tryRecord catches and records it, but reactiveFunction remains uninitialized (undefined at runtime). The definite assignment assertion (!:) silences TypeScript, but the subsequent code — log(...), assertWellFormedBreakTargets(reactiveFunction), and all reactive passes — will crash accessing properties of undefined.

Consider providing a fallback or early-returning on error, similar to the pattern used for fbtOperands and uniqueIdentifiers (which have safe defaults). For example:

let reactiveFunction: ReactiveFunction | null = null;
env.tryRecord(() => {
  reactiveFunction = buildReactiveFunction(hir);
});
if (reactiveFunction == null) {
  return Err(env.aggregateErrors());
}
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-464

Comment:
**Uninitialized variable on caught error**

If `buildReactiveFunction(hir)` throws a non-invariant `CompilerError` (e.g. a `throwTodo` for unsupported terminal kinds), `tryRecord` catches and records it, but `reactiveFunction` remains uninitialized (`undefined` at runtime). The definite assignment assertion (`!:`) silences TypeScript, but the subsequent code — `log(...)`, `assertWellFormedBreakTargets(reactiveFunction)`, and all reactive passes — will crash accessing properties of `undefined`.

Consider providing a fallback or early-returning on error, similar to the pattern used for `fbtOperands` and `uniqueIdentifiers` (which have safe defaults). For example:
```
let reactiveFunction: ReactiveFunction | null = null;
env.tryRecord(() => {
  reactiveFunction = buildReactiveFunction(hir);
});
if (reactiveFunction == null) {
  return Err(env.aggregateErrors());
}
```

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

@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.

48 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

log({kind: 'hir', name: 'AnalyseFunctions', value: hir});

const mutabilityAliasingErrors = inferMutationAliasingEffects(hir);
inferMutationAliasingEffects(hir);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing tryRecord wrapper for fault tolerance

inferMutationAliasingEffects is called without env.tryRecord() wrapping, unlike nearly every other pass in this pipeline. The function still contains an internal throw errors (at InferMutationAliasingEffects.ts:2369 for knownIncompatible signatures), which means a non-invariant CompilerError can escape uncaught and bypass the new error accumulation mechanism.

Other passes that can throw (e.g., pruneMaybeThrows, enterSSA, constantPropagation, analyseFunctions) are consistently wrapped in env.tryRecord(). For consistency with the fault tolerance pattern established in this PR, this call should also be wrapped:

Suggested change
inferMutationAliasingEffects(hir);
env.tryRecord(() => {
inferMutationAliasingEffects(hir);
});
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: 233

Comment:
**Missing `tryRecord` wrapper for fault tolerance**

`inferMutationAliasingEffects` is called without `env.tryRecord()` wrapping, unlike nearly every other pass in this pipeline. The function still contains an internal `throw errors` (at `InferMutationAliasingEffects.ts:2369` for `knownIncompatible` signatures), which means a non-invariant `CompilerError` can escape uncaught and bypass the new error accumulation mechanism.

Other passes that can throw (e.g., `pruneMaybeThrows`, `enterSSA`, `constantPropagation`, `analyseFunctions`) are consistently wrapped in `env.tryRecord()`. For consistency with the fault tolerance pattern established in this PR, this call should also be wrapped:

```suggestion
  env.tryRecord(() => {
    inferMutationAliasingEffects(hir);
  });
```

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

@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.

47 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@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.

46 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +309 to +310
let fbtOperands: Set<IdentifierId> = new Set();
fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unused safe default values

The fbtOperands variable is initialized with new Set() and then immediately reassigned on the next line without being wrapped in tryRecord(). The same pattern appears for uniqueIdentifiers at line 490. The fault-tolerance-overview.md states these defaults are for "value-producing passes...when wrapped in tryRecord()", but neither call is actually wrapped. This makes the let + default initialization misleading — they behave identically to a simple const assignment. If tryRecord wrapping is intended for a later PR, a brief comment would help clarify the intent.

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/Entrypoint/Pipeline.ts
Line: 309-310

Comment:
**Unused safe default values**

The `fbtOperands` variable is initialized with `new Set()` and then immediately reassigned on the next line without being wrapped in `tryRecord()`. The same pattern appears for `uniqueIdentifiers` at line 490. The fault-tolerance-overview.md states these defaults are for "value-producing passes...when wrapped in `tryRecord()`", but neither call is actually wrapped. This makes the `let` + default initialization misleading — they behave identically to a simple `const` assignment. If `tryRecord` wrapping is intended for a later PR, a brief comment would help clarify the intent.

<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.

@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.

46 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

… 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.
@everettbu

Copy link
Copy Markdown
Author

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

@everettbu everettbu closed this Feb 24, 2026
@everettbu
everettbu deleted the pr35876 branch February 24, 2026 00:36

@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.

12 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

description: `Handle '${value.kind} lvalues`,
suggestions: null,
});
fn.env.recordError(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Inconsistent use of fn.env vs env parameter

The default branch uses fn.env.recordError(...) while all other branches in this function use the passed env parameter (and pass it to visit()). When recursing into nested functions (the FunctionExpression/ObjectMethod case at line 65), fn changes to the nested function but env stays as the top-level environment. In practice this is safe today because all nested HIRFunction objects share the same Environment instance, but for consistency with the rest of this function it should use env:

Suggested change
fn.env.recordError(
env.recordError(

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/ValidateContextVariableLValues.ts
Line: 74

Comment:
**Inconsistent use of `fn.env` vs `env` parameter**

The `default` branch uses `fn.env.recordError(...)` while all other branches in this function use the passed `env` parameter (and pass it to `visit()`). When recursing into nested functions (the `FunctionExpression`/`ObjectMethod` case at line 65), `fn` changes to the nested function but `env` stays as the top-level environment. In practice this is safe today because all nested `HIRFunction` objects share the same `Environment` instance, but for consistency with the rest of this function it should use `env`:

```suggestion
            env.recordError(
```

<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.

Comment on lines +24 to 46
Found 3 errors:

Todo: Support destructuring of context variables

error.todo-reassign-const.ts:3:20
1 | import {Stringify} from 'shared-runtime';
2 |
> 3 | function Component({foo}) {
| ^^^ Support destructuring of context variables
| ^^^
4 | let bar = foo.bar;
5 | return (
6 | <Stringify

Todo: Support destructuring of context variables

error.todo-reassign-const.ts:3:20
1 | import {Stringify} from 'shared-runtime';
2 |
> 3 | function Component({foo}) {
| ^^^
4 | let bar = foo.bar;
5 | return (
6 | <Stringify

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 now surfaced for same location

The test expectation went from 2 to 3 errors, with the "Support destructuring of context variables" error now appearing twice at the exact same location (3:20). Previously, throwTodo halted execution after the first occurrence, masking the duplicate. Now that errors are recorded and execution continues, both occurrences surface. Is the duplicate intentional/acceptable, or should validateContextVariableLValues deduplicate errors for the same identifier+location? Is this duplicate error at the same source location intentional, or should the validation deduplicate errors for the same identifier?

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/error.todo-reassign-const.expect.md
Line: 24-46

Comment:
**Duplicate error now surfaced for same location**

The test expectation went from 2 to 3 errors, with the "Support destructuring of context variables" error now appearing twice at the exact same location (`3:20`). Previously, `throwTodo` halted execution after the first occurrence, masking the duplicate. Now that errors are recorded and execution continues, both occurrences surface. Is the duplicate intentional/acceptable, or should `validateContextVariableLValues` deduplicate errors for the same identifier+location? Is this duplicate error at the same source location intentional, or should the validation deduplicate errors for the same identifier?

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

@everettbu
everettbu restored the pr35876 branch February 24, 2026 01:42
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