@samitouri / QOS-React-1 / commits / 8a33fb3a1c

[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 }