[compiler] Cleanup: consistent tryRecord() wrapping and error recording (#35880)
--- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35880). * #35888 * #35884 * #35883 * #35882 * #35881 * __->__ #35880
Joseph Savona committed
Feb 23, 2026 at 16:08 UTC
8a33fb3a1cd6a8230cb9c49f4cda71f8d21c8476
6 files changed
+46
-30
compiler/fault-tolerance-overview.md
+2
@@ -328,4 +328,6 @@ Walk through `runWithEnvironment` and wrap each pass call site. This is the inte
328
* **Partial HIR can trigger downstream invariants.** When lowering skips or partially handles constructs (e.g., unreachable hoisted functions, `var` declarations before the fix), downstream passes like `InferMutationAliasingEffects` may encounter uninitialized identifiers and throw invariants. This is acceptable since the function still correctly bails out of compilation, but error messages may be less specific. The fix for `var` (treating as `let`) demonstrates how to avoid this: continue lowering with a best-effort representation rather than skipping entirely.
329
* **Errors accumulated on `env` are lost when an invariant propagates out of the pipeline.** Since invariant CompilerErrors always re-throw through `tryRecord()`, they exit the pipeline as exceptions. The caller only sees the invariant error, not any errors previously recorded on `env`. This is a design limitation that could be addressed by aggregating env errors with caught exceptions in `tryCompileFunction()`.
330
* **Dedicated fault tolerance test fixtures** were added in `__tests__/fixtures/compiler/fault-tolerance/`. Each fixture combines two or more errors from different passes to verify the compiler reports all of them rather than short-circuiting on the first. Coverage includes: `var`+props mutation (BuildHIR→InferMutationAliasingEffects), `var`+ref access (BuildHIR→ValidateNoRefAccessInRender), `try/finally`+props mutation (BuildHIR→InferMutationAliasingEffects), `try/finally`+ref access (BuildHIR→ValidateNoRefAccessInRender), and a 3-error test combining try/finally+ref access+props mutation.
331
+* **Cleanup: consistent `tryRecord()` wrapping in Pipeline.ts.** All validation passes and inference passes are now wrapped in `env.tryRecord()` for defense-in-depth, consistent with the approach used for transform passes. Previously only transform passes were wrapped. Merged duplicate `env.enableValidations` guard blocks. Pattern B lint-only passes (`env.logErrors()`) were intentionally not wrapped since they use a different error recording strategy.
332
+* **Cleanup: normalized validation error recording pattern.** Four validation passes (`ValidateNoDerivedComputationsInEffects`, `ValidateMemoizedEffectDependencies`, `ValidatePreservedManualMemoization`, `ValidateSourceLocations`) were using `for (const detail of errors.details) { env.recordError(detail); }` instead of the simpler `env.recordErrors(errors)`. Normalized to use the batch method.
333
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
+40
-16
@@ -161,8 +161,12 @@ function runWithEnvironment(
161
pruneMaybeThrows(hir);
162
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
163
164
- validateContextVariableLValues(hir);
165
- validateUseMemo(hir);
164
+ env.tryRecord(() => {
165
+ validateContextVariableLValues(hir);
166
+ });
167
+ env.tryRecord(() => {
168
+ validateUseMemo(hir);
169
+ });
170
171
if (env.enableDropManualMemoization) {
172
dropManualMemoization(hir);
@@ -198,10 +202,14 @@ function runWithEnvironment(
202
203
if (env.enableValidations) {
204
if (env.config.validateHooksUsage) {
201
- validateHooksUsage(hir);
205
+ env.tryRecord(() => {
206
+ validateHooksUsage(hir);
207
+ });
208
}
209
if (env.config.validateNoCapitalizedCalls) {
204
- validateNoCapitalizedCalls(hir);
210
+ env.tryRecord(() => {
211
+ validateNoCapitalizedCalls(hir);
212
+ });
213
}
214
}
215
@@ -211,7 +219,9 @@ function runWithEnvironment(
219
analyseFunctions(hir);
220
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
221
214
- inferMutationAliasingEffects(hir);
222
+ env.tryRecord(() => {
223
+ inferMutationAliasingEffects(hir);
224
+ });
225
log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir});
226
227
if (env.outputMode === 'ssr') {
@@ -225,25 +235,31 @@ function runWithEnvironment(
235
pruneMaybeThrows(hir);
236
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
237
228
- inferMutationAliasingRanges(hir, {
229
- isFunctionExpression: false,
238
+ env.tryRecord(() => {
239
+ inferMutationAliasingRanges(hir, {
240
+ isFunctionExpression: false,
241
+ });
242
});
243
log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir});
244
if (env.enableValidations) {
233
- validateLocalsNotReassignedAfterRender(hir);
234
- }
245
+ env.tryRecord(() => {
246
+ validateLocalsNotReassignedAfterRender(hir);
247
+ });
248
236
- if (env.enableValidations) {
249
if (env.config.assertValidMutableRanges) {
250
assertValidMutableRanges(hir);
251
}
252
253
if (env.config.validateRefAccessDuringRender) {
242
- validateNoRefAccessInRender(hir);
254
+ env.tryRecord(() => {
255
+ validateNoRefAccessInRender(hir);
256
+ });
257
}
258
259
if (env.config.validateNoSetStateInRender) {
246
- validateNoSetStateInRender(hir);
260
+ env.tryRecord(() => {
261
+ validateNoSetStateInRender(hir);
262
+ });
263
}
264
265
if (
@@ -252,7 +268,9 @@ function runWithEnvironment(
268
) {
269
env.logErrors(validateNoDerivedComputationsInEffects_exp(hir));
270
} else if (env.config.validateNoDerivedComputationsInEffects) {
255
- validateNoDerivedComputationsInEffects(hir);
271
+ env.tryRecord(() => {
272
+ validateNoDerivedComputationsInEffects(hir);
273
+ });
274
}
275
276
if (env.config.validateNoSetStateInEffects && env.outputMode === 'lint') {
@@ -277,7 +295,9 @@ function runWithEnvironment(
295
env.config.validateExhaustiveEffectDependencies
296
) {
297
// NOTE: this relies on reactivity inference running first
280
- validateExhaustiveDependencies(hir);
298
+ env.tryRecord(() => {
299
+ validateExhaustiveDependencies(hir);
300
+ });
301
}
302
}
303
@@ -506,7 +526,9 @@ function runWithEnvironment(
526
env.config.enablePreserveExistingMemoizationGuarantees ||
527
env.config.validatePreserveExistingMemoizationGuarantees
528
) {
509
- validatePreservedManualMemoization(reactiveFunction);
529
+ env.tryRecord(() => {
530
+ validatePreservedManualMemoization(reactiveFunction);
531
+ });
532
}
533
534
const ast = codegenFunction(reactiveFunction, {
@@ -519,7 +541,9 @@ function runWithEnvironment(
541
}
542
543
if (env.config.validateSourceLocations) {
522
- validateSourceLocations(func, ast, env);
544
+ env.tryRecord(() => {
545
+ validateSourceLocations(func, ast, env);
546
+ });
547
}
548
549
/**
compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
+1
-5
@@ -217,11 +217,7 @@ export function lower(
217
if (err instanceof CompilerError) {
218
// Re-throw invariant errors immediately
219
for (const detail of err.details) {
220
- if (
221
- (detail instanceof CompilerDiagnostic
222
- ? detail.category
223
- : detail.category) === ErrorCategory.Invariant
224
- ) {
220
+ if (detail.category === ErrorCategory.Invariant) {
221
throw err;
222
}
223
}
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects.ts
+1
-3
@@ -97,9 +97,7 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
97
}
98
}
99
}
100
- for (const detail of errors.details) {
101
- fn.env.recordError(detail);
102
- }
100
+ fn.env.recordErrors(errors);
101
}
102
103
function validateEffect(
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts
+1
-3
@@ -52,9 +52,7 @@ export function validatePreservedManualMemoization(fn: ReactiveFunction): void {
52
manualMemoState: null,
53
};
54
visitReactiveFunction(fn, new Visitor(), state);
55
- for (const detail of state.errors.details) {
56
- fn.env.recordError(detail);
57
- }
55
+ fn.env.recordErrors(state.errors);
56
}
57
58
const DEBUG = false;
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateSourceLocations.ts
+1
-3
@@ -310,7 +310,5 @@ export function validateSourceLocations(
310
}
311
}
312
313
- for (const detail of errors.details) {
314
- env.recordError(detail);
315
- }
313
+ env.recordErrors(errors);
314
}