Skip to content

Commit af88b0b

Browse files
committed
[compiler] Phase 1: Add error accumulation infrastructure to Environment
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
1 parent d30d189 commit af88b0b

2 files changed

Lines changed: 90 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: 88 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 {
@@ -777,6 +782,12 @@ export class Environment {
777782

778783
#flowTypeEnvironment: FlowTypeEnv | null;
779784

785+
/**
786+
* Accumulated compilation errors. Passes record errors here instead of
787+
* throwing, so the pipeline can continue and report all errors at once.
788+
*/
789+
#errors: CompilerError = new CompilerError();
790+
780791
constructor(
781792
scope: BabelScope,
782793
fnType: ReactFunctionType,
@@ -955,6 +966,82 @@ export class Environment {
955966
}
956967
}
957968

969+
/**
970+
* Record a single diagnostic or error detail on this environment.
971+
* If the error is an Invariant, it is immediately thrown since invariants
972+
* represent internal bugs that cannot be recovered from.
973+
* Otherwise, the error is accumulated and optionally logged.
974+
*/
975+
recordError(error: CompilerDiagnostic | CompilerErrorDetail): void {
976+
if (error.category === ErrorCategory.Invariant) {
977+
const compilerError = new CompilerError();
978+
if (error instanceof CompilerDiagnostic) {
979+
compilerError.pushDiagnostic(error);
980+
} else {
981+
compilerError.pushErrorDetail(error);
982+
}
983+
throw compilerError;
984+
}
985+
if (error instanceof CompilerDiagnostic) {
986+
this.#errors.pushDiagnostic(error);
987+
} else {
988+
this.#errors.pushErrorDetail(error);
989+
}
990+
if (this.logger != null) {
991+
this.logger.logEvent(this.filename, {
992+
kind: 'CompileError',
993+
detail: error,
994+
fnLoc: null,
995+
});
996+
}
997+
}
998+
999+
/**
1000+
* Record all diagnostics from a CompilerError onto this environment.
1001+
*/
1002+
recordErrors(error: CompilerError): void {
1003+
for (const detail of error.details) {
1004+
this.recordError(detail);
1005+
}
1006+
}
1007+
1008+
/**
1009+
* Returns true if any errors have been recorded during compilation.
1010+
*/
1011+
hasErrors(): boolean {
1012+
return this.#errors.hasAnyErrors();
1013+
}
1014+
1015+
/**
1016+
* Returns the accumulated CompilerError containing all recorded diagnostics.
1017+
*/
1018+
aggregateErrors(): CompilerError {
1019+
return this.#errors;
1020+
}
1021+
1022+
/**
1023+
* Wraps a callback in try/catch: if the callback throws a CompilerError
1024+
* that is NOT an invariant, the error is recorded and execution continues.
1025+
* Non-CompilerError exceptions and invariants are re-thrown.
1026+
*/
1027+
tryRecord(fn: () => void): void {
1028+
try {
1029+
fn();
1030+
} catch (err) {
1031+
if (err instanceof CompilerError) {
1032+
// Check if any detail is an invariant — if so, re-throw
1033+
for (const detail of err.details) {
1034+
if (detail.category === ErrorCategory.Invariant) {
1035+
throw err;
1036+
}
1037+
}
1038+
this.recordErrors(err);
1039+
} else {
1040+
throw err;
1041+
}
1042+
}
1043+
}
1044+
9581045
isContextIdentifier(node: t.Identifier): boolean {
9591046
return this.#contextIdentifiers.has(node);
9601047
}

0 commit comments

Comments
 (0)