@samitouri / QOS-React-2 / commits / eca778cf8b

[compiler] Phase 1: Add error accumulation infrastructure to Environment (#35873)

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 --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35873). * #35888 * #35884 * #35883 * #35882 * #35881 * #35880 * #35879 * #35878 * #35877 * #35876 * #35875 * #35874 * __->__ #35873

Joseph Savona committed Feb 23, 2026 at 15:18 UTC eca778cf8bf88b2a62a5244f94a0084593ffbf14
2 files changed +83 -3
compiler/fault-tolerance-overview.md
+2 -2
@@ -31,7 +31,7 @@ Note that some errors may continue to cause an eager bailout:
31
32 Add error accumulation to the `Environment` class so that any pass can record errors during compilation without halting.
33
34 -- [ ] **1.1 Add error accumulator to Environment** (`src/HIR/Environment.ts`)
34 +- [x] **1.1 Add error accumulator to Environment** (`src/HIR/Environment.ts`)
35 - Add a `#errors: CompilerError` field, initialized in the constructor
36 - Add a `recordError(error: CompilerDiagnostic | CompilerErrorDetail)` method that:
37 - If an Invariant-category detail, immediately throw it
@@ -41,7 +41,7 @@ Add error accumulation to the `Environment` class so that any pass can record er
41 - Add a `aggregateErrors(): CompilerError` method that returns the accumulated error object
42 - Consider whether `recordError` should accept the same options as `CompilerError.push()` for convenience (reason, description, severity, loc, etc.)
43
44 -- [ ] **1.2 Add a `tryRecord` helper on Environment** (`src/HIR/Environment.ts`)
44 +- [x] **1.2 Add a `tryRecord` helper on Environment** (`src/HIR/Environment.ts`)
45 - Add a `tryRecord(fn: () => void): void` method that wraps a callback in try/catch:
46 - If `fn` throws a `CompilerError` that is NOT an invariant, record it via `recordError`
47 - If `fn` throws a non-CompilerError or a CompilerError invariant, re-throw
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+81 -1
@@ -8,7 +8,12 @@
8 import * as t from '@babel/types';
9 import {ZodError, z} from 'zod/v4';
10 import {fromZodError} from 'zod-validation-error/v4';
11 -import {CompilerError} from '../CompilerError';
11 +import {
12 + CompilerDiagnostic,
13 + CompilerError,
14 + CompilerErrorDetail,
15 + ErrorCategory,
16 +} from '../CompilerError';
17 import {CompilerOutputMode, Logger, ProgramContext} from '../Entrypoint';
18 import {Err, Ok, Result} from '../Utils/Result';
19 import {
@@ -545,6 +550,12 @@ export class Environment {
550
551 #flowTypeEnvironment: FlowTypeEnv | null;
552
553 + /**
554 + * Accumulated compilation errors. Passes record errors here instead of
555 + * throwing, so the pipeline can continue and report all errors at once.
556 + */
557 + #errors: CompilerError = new CompilerError();
558 +
559 constructor(
560 scope: BabelScope,
561 fnType: ReactFunctionType,
@@ -702,6 +713,75 @@ export class Environment {
713 }
714 }
715
716 + /**
717 + * Record a single diagnostic or error detail on this environment.
718 + * If the error is an Invariant, it is immediately thrown since invariants
719 + * represent internal bugs that cannot be recovered from.
720 + * Otherwise, the error is accumulated and optionally logged.
721 + */
722 + recordError(error: CompilerDiagnostic | CompilerErrorDetail): void {
723 + if (error.category === ErrorCategory.Invariant) {
724 + const compilerError = new CompilerError();
725 + if (error instanceof CompilerDiagnostic) {
726 + compilerError.pushDiagnostic(error);
727 + } else {
728 + compilerError.pushErrorDetail(error);
729 + }
730 + throw compilerError;
731 + }
732 + if (error instanceof CompilerDiagnostic) {
733 + this.#errors.pushDiagnostic(error);
734 + } else {
735 + this.#errors.pushErrorDetail(error);
736 + }
737 + }
738 +
739 + /**
740 + * Record all diagnostics from a CompilerError onto this environment.
741 + */
742 + recordErrors(error: CompilerError): void {
743 + for (const detail of error.details) {
744 + this.recordError(detail);
745 + }
746 + }
747 +
748 + /**
749 + * Returns true if any errors have been recorded during compilation.
750 + */
751 + hasErrors(): boolean {
752 + return this.#errors.hasAnyErrors();
753 + }
754 +
755 + /**
756 + * Returns the accumulated CompilerError containing all recorded diagnostics.
757 + */
758 + aggregateErrors(): CompilerError {
759 + return this.#errors;
760 + }
761 +
762 + /**
763 + * Wraps a callback in try/catch: if the callback throws a CompilerError
764 + * that is NOT an invariant, the error is recorded and execution continues.
765 + * Non-CompilerError exceptions and invariants are re-thrown.
766 + */
767 + tryRecord(fn: () => void): void {
768 + try {
769 + fn();
770 + } catch (err) {
771 + if (err instanceof CompilerError) {
772 + // Check if any detail is an invariant — if so, re-throw
773 + for (const detail of err.details) {
774 + if (detail.category === ErrorCategory.Invariant) {
775 + throw err;
776 + }
777 + }
778 + this.recordErrors(err);
779 + } else {
780 + throw err;
781 + }
782 + }
783 + }
784 +
785 isContextIdentifier(node: t.Identifier): boolean {
786 return this.#contextIdentifiers.has(node);
787 }