Skip to content

Commit eca778c

Browse files
authored
[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
1 parent 0dbb43b commit eca778c

2 files changed

Lines changed: 83 additions & 3 deletions

File tree

compiler/fault-tolerance-overview.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Note that some errors may continue to cause an eager bailout:
3131

3232
Add error accumulation to the `Environment` class so that any pass can record errors during compilation without halting.
3333

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`)
3535
- Add a `#errors: CompilerError` field, initialized in the constructor
3636
- Add a `recordError(error: CompilerDiagnostic | CompilerErrorDetail)` method that:
3737
- 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
4141
- Add a `aggregateErrors(): CompilerError` method that returns the accumulated error object
4242
- Consider whether `recordError` should accept the same options as `CompilerError.push()` for convenience (reason, description, severity, loc, etc.)
4343

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`)
4545
- Add a `tryRecord(fn: () => void): void` method that wraps a callback in try/catch:
4646
- If `fn` throws a `CompilerError` that is NOT an invariant, record it via `recordError`
4747
- If `fn` throws a non-CompilerError or a CompilerError invariant, re-throw

compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88
import * as t from '@babel/types';
99
import {ZodError, z} from 'zod/v4';
1010
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';
1217
import {CompilerOutputMode, Logger, ProgramContext} from '../Entrypoint';
1318
import {Err, Ok, Result} from '../Utils/Result';
1419
import {
@@ -545,6 +550,12 @@ export class Environment {
545550

546551
#flowTypeEnvironment: FlowTypeEnv | null;
547552

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+
548559
constructor(
549560
scope: BabelScope,
550561
fnType: ReactFunctionType,
@@ -702,6 +713,75 @@ export class Environment {
702713
}
703714
}
704715

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+
705785
isContextIdentifier(node: t.Identifier): boolean {
706786
return this.#contextIdentifiers.has(node);
707787
}

0 commit comments

Comments
 (0)