[compiler] Remove local CompilerError accumulators, emit directly to env.recordError()

Removes unnecessary indirection in 17 compiler passes that previously
accumulated errors in a local `CompilerError` instance before flushing
them to `env.recordErrors()` at the end of each pass. Errors are now
emitted directly via `env.recordError()` as they're discovered.

For passes with recursive error-detection patterns (ValidateNoRefAccessInRender,
ValidateNoSetStateInRender), the internal accumulator is kept but flushed
via individual `recordError()` calls. For InferMutationAliasingRanges,
a `shouldRecordErrors` flag preserves the conditional suppression logic.
For TransformFire, the throw-based error propagation is replaced with
direct recording plus an early-exit check in Pipeline.ts.
This commit is contained in:
Joe Savona
2026-02-20 17:24:38 -08:00
parent 61633098fa
commit f283c6860c
23 changed files with 817 additions and 684 deletions

View File

@@ -220,6 +220,9 @@ function runWithEnvironment(
if (env.config.enableFire) { if (env.config.enableFire) {
transformFire(hir); transformFire(hir);
log({kind: 'hir', name: 'TransformFire', value: hir}); log({kind: 'hir', name: 'TransformFire', value: hir});
if (env.hasErrors()) {
return Err(env.aggregateErrors());
}
} }
if (env.config.lowerContextAccess) { if (env.config.lowerContextAccess) {

View File

@@ -7,7 +7,12 @@
import {Binding, NodePath} from '@babel/traverse'; import {Binding, NodePath} from '@babel/traverse';
import * as t from '@babel/types'; import * as t from '@babel/types';
import {CompilerError, ErrorCategory} from '../CompilerError'; import {
CompilerError,
CompilerDiagnostic,
CompilerErrorDetail,
ErrorCategory,
} from '../CompilerError';
import {Environment} from './Environment'; import {Environment} from './Environment';
import { import {
BasicBlock, BasicBlock,
@@ -110,7 +115,6 @@ export default class HIRBuilder {
#bindings: Bindings; #bindings: Bindings;
#env: Environment; #env: Environment;
#exceptionHandlerStack: Array<BlockId> = []; #exceptionHandlerStack: Array<BlockId> = [];
errors: CompilerError = new CompilerError();
/** /**
* Traversal context: counts the number of `fbt` tag parents * Traversal context: counts the number of `fbt` tag parents
* of the current babel node. * of the current babel node.
@@ -148,6 +152,10 @@ export default class HIRBuilder {
this.#current = newBlock(this.#entry, options?.entryBlockKind ?? 'block'); this.#current = newBlock(this.#entry, options?.entryBlockKind ?? 'block');
} }
recordError(error: CompilerDiagnostic | CompilerErrorDetail): void {
this.#env.recordError(error);
}
currentBlockKind(): BlockKind { currentBlockKind(): BlockKind {
return this.#current.kind; return this.#current.kind;
} }
@@ -308,24 +316,28 @@ export default class HIRBuilder {
resolveBinding(node: t.Identifier): Identifier { resolveBinding(node: t.Identifier): Identifier {
if (node.name === 'fbt') { if (node.name === 'fbt') {
this.errors.push({ this.recordError(
category: ErrorCategory.Todo, new CompilerErrorDetail({
reason: 'Support local variables named `fbt`', category: ErrorCategory.Todo,
description: reason: 'Support local variables named `fbt`',
'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', description:
loc: node.loc ?? GeneratedSource, 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported',
suggestions: null, loc: node.loc ?? GeneratedSource,
}); suggestions: null,
}),
);
} }
if (node.name === 'this') { if (node.name === 'this') {
this.errors.push({ this.recordError(
category: ErrorCategory.UnsupportedSyntax, new CompilerErrorDetail({
reason: '`this` is not supported syntax', category: ErrorCategory.UnsupportedSyntax,
description: reason: '`this` is not supported syntax',
'React Compiler does not support compiling functions that use `this`', description:
loc: node.loc ?? GeneratedSource, 'React Compiler does not support compiling functions that use `this`',
suggestions: null, loc: node.loc ?? GeneratedSource,
}); suggestions: null,
}),
);
} }
const originalName = node.name; const originalName = node.name;
let name = originalName; let name = originalName;
@@ -371,13 +383,15 @@ export default class HIRBuilder {
instr => instr.value.kind === 'FunctionExpression', instr => instr.value.kind === 'FunctionExpression',
) )
) { ) {
this.errors.push({ this.recordError(
reason: `Support functions with unreachable code that may contain hoisted declarations`, new CompilerErrorDetail({
loc: block.instructions[0]?.loc ?? block.terminal.loc, reason: `Support functions with unreachable code that may contain hoisted declarations`,
description: null, loc: block.instructions[0]?.loc ?? block.terminal.loc,
suggestions: null, description: null,
category: ErrorCategory.Todo, suggestions: null,
}); category: ErrorCategory.Todo,
}),
);
} }
} }
ir.blocks = rpoBlocks; ir.blocks = rpoBlocks;

View File

@@ -293,7 +293,7 @@ function extractManualMemoizationArgs(
instr: TInstruction<CallExpression> | TInstruction<MethodCall>, instr: TInstruction<CallExpression> | TInstruction<MethodCall>,
kind: 'useCallback' | 'useMemo', kind: 'useCallback' | 'useMemo',
sidemap: IdentifierSidemap, sidemap: IdentifierSidemap,
errors: CompilerError, env: Environment,
): { ): {
fnPlace: Place; fnPlace: Place;
depsList: Array<ManualMemoDependency> | null; depsList: Array<ManualMemoDependency> | null;
@@ -303,7 +303,7 @@ function extractManualMemoizationArgs(
Place | SpreadPattern | undefined Place | SpreadPattern | undefined
>; >;
if (fnPlace == null || fnPlace.kind !== 'Identifier') { if (fnPlace == null || fnPlace.kind !== 'Identifier') {
errors.pushDiagnostic( env.recordError(
CompilerDiagnostic.create({ CompilerDiagnostic.create({
category: ErrorCategory.UseMemo, category: ErrorCategory.UseMemo,
reason: `Expected a callback function to be passed to ${kind}`, reason: `Expected a callback function to be passed to ${kind}`,
@@ -335,7 +335,7 @@ function extractManualMemoizationArgs(
? sidemap.maybeDepsLists.get(depsListPlace.identifier.id) ? sidemap.maybeDepsLists.get(depsListPlace.identifier.id)
: null; : null;
if (maybeDepsList == null) { if (maybeDepsList == null) {
errors.pushDiagnostic( env.recordError(
CompilerDiagnostic.create({ CompilerDiagnostic.create({
category: ErrorCategory.UseMemo, category: ErrorCategory.UseMemo,
reason: `Expected the dependency list for ${kind} to be an array literal`, reason: `Expected the dependency list for ${kind} to be an array literal`,
@@ -354,7 +354,7 @@ function extractManualMemoizationArgs(
for (const dep of maybeDepsList.deps) { for (const dep of maybeDepsList.deps) {
const maybeDep = sidemap.maybeDeps.get(dep.identifier.id); const maybeDep = sidemap.maybeDeps.get(dep.identifier.id);
if (maybeDep == null) { if (maybeDep == null) {
errors.pushDiagnostic( env.recordError(
CompilerDiagnostic.create({ CompilerDiagnostic.create({
category: ErrorCategory.UseMemo, category: ErrorCategory.UseMemo,
reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`, reason: `Expected the dependency list to be an array of simple expressions (e.g. \`x\`, \`x.y.z\`, \`x?.y?.z\`)`,
@@ -389,7 +389,6 @@ function extractManualMemoizationArgs(
* is only used for memoizing values and not for running arbitrary side effects. * is only used for memoizing values and not for running arbitrary side effects.
*/ */
export function dropManualMemoization(func: HIRFunction): void { export function dropManualMemoization(func: HIRFunction): void {
const errors = new CompilerError();
const isValidationEnabled = const isValidationEnabled =
func.env.config.validatePreserveExistingMemoizationGuarantees || func.env.config.validatePreserveExistingMemoizationGuarantees ||
func.env.config.validateNoSetStateInRender || func.env.config.validateNoSetStateInRender ||
@@ -436,7 +435,7 @@ export function dropManualMemoization(func: HIRFunction): void {
instr as TInstruction<CallExpression> | TInstruction<MethodCall>, instr as TInstruction<CallExpression> | TInstruction<MethodCall>,
manualMemo.kind, manualMemo.kind,
sidemap, sidemap,
errors, func.env,
); );
if (memoDetails == null) { if (memoDetails == null) {
@@ -464,7 +463,7 @@ export function dropManualMemoization(func: HIRFunction): void {
* is rare and likely sketchy. * is rare and likely sketchy.
*/ */
if (!sidemap.functions.has(fnPlace.identifier.id)) { if (!sidemap.functions.has(fnPlace.identifier.id)) {
errors.pushDiagnostic( func.env.recordError(
CompilerDiagnostic.create({ CompilerDiagnostic.create({
category: ErrorCategory.UseMemo, category: ErrorCategory.UseMemo,
reason: `Expected the first argument to be an inline function expression`, reason: `Expected the first argument to be an inline function expression`,
@@ -549,10 +548,6 @@ export function dropManualMemoization(func: HIRFunction): void {
markInstructionIds(func.body); markInstructionIds(func.body);
} }
} }
if (errors.hasAnyErrors()) {
func.env.recordErrors(errors);
}
} }
function findOptionalPlaces(fn: HIRFunction): Set<IdentifierId> { function findOptionalPlaces(fn: HIRFunction): Set<IdentifierId> {

View File

@@ -20,6 +20,7 @@ import {
Place, Place,
isPrimitiveType, isPrimitiveType,
} from '../HIR/HIR'; } from '../HIR/HIR';
import {Environment} from '../HIR/Environment';
import { import {
eachInstructionLValue, eachInstructionLValue,
eachInstructionValueOperand, eachInstructionValueOperand,
@@ -107,7 +108,7 @@ export function inferMutationAliasingRanges(
let index = 0; let index = 0;
const errors = new CompilerError(); const shouldRecordErrors = !isFunctionExpression && fn.env.enableValidations;
for (const param of [...fn.params, ...fn.context, fn.returns]) { for (const param of [...fn.params, ...fn.context, fn.returns]) {
const place = param.kind === 'Identifier' ? param : param.place; const place = param.kind === 'Identifier' ? param : param.place;
@@ -200,7 +201,9 @@ export function inferMutationAliasingRanges(
effect.kind === 'MutateGlobal' || effect.kind === 'MutateGlobal' ||
effect.kind === 'Impure' effect.kind === 'Impure'
) { ) {
errors.pushDiagnostic(effect.error); if (shouldRecordErrors) {
fn.env.recordError(effect.error);
}
functionEffects.push(effect); functionEffects.push(effect);
} else if (effect.kind === 'Render') { } else if (effect.kind === 'Render') {
renders.push({index: index++, place: effect.place}); renders.push({index: index++, place: effect.place});
@@ -245,11 +248,15 @@ export function inferMutationAliasingRanges(
mutation.kind, mutation.kind,
mutation.place.loc, mutation.place.loc,
mutation.reason, mutation.reason,
errors, shouldRecordErrors ? fn.env : null,
); );
} }
for (const render of renders) { for (const render of renders) {
state.render(render.index, render.place.identifier, errors); state.render(
render.index,
render.place.identifier,
shouldRecordErrors ? fn.env : null,
);
} }
for (const param of [...fn.context, ...fn.params]) { for (const param of [...fn.context, ...fn.params]) {
const place = param.kind === 'Identifier' ? param : param.place; const place = param.kind === 'Identifier' ? param : param.place;
@@ -498,7 +505,6 @@ export function inferMutationAliasingRanges(
* would be transitively mutated needs a capture relationship. * would be transitively mutated needs a capture relationship.
*/ */
const tracked: Array<Place> = []; const tracked: Array<Place> = [];
const ignoredErrors = new CompilerError();
for (const param of [...fn.params, ...fn.context, fn.returns]) { for (const param of [...fn.params, ...fn.context, fn.returns]) {
const place = param.kind === 'Identifier' ? param : param.place; const place = param.kind === 'Identifier' ? param : param.place;
tracked.push(place); tracked.push(place);
@@ -513,7 +519,7 @@ export function inferMutationAliasingRanges(
MutationKind.Conditional, MutationKind.Conditional,
into.loc, into.loc,
null, null,
ignoredErrors, null,
); );
for (const from of tracked) { for (const from of tracked) {
if ( if (
@@ -547,23 +553,17 @@ export function inferMutationAliasingRanges(
} }
} }
if (
errors.hasAnyErrors() &&
!isFunctionExpression &&
fn.env.enableValidations
) {
fn.env.recordErrors(errors);
}
return functionEffects; return functionEffects;
} }
function appendFunctionErrors(errors: CompilerError, fn: HIRFunction): void { function appendFunctionErrors(env: Environment | null, fn: HIRFunction): void {
if (env == null) return;
for (const effect of fn.aliasingEffects ?? []) { for (const effect of fn.aliasingEffects ?? []) {
switch (effect.kind) { switch (effect.kind) {
case 'Impure': case 'Impure':
case 'MutateFrozen': case 'MutateFrozen':
case 'MutateGlobal': { case 'MutateGlobal': {
errors.pushDiagnostic(effect.error); env.recordError(effect.error);
break; break;
} }
} }
@@ -664,7 +664,7 @@ class AliasingState {
} }
} }
render(index: number, start: Identifier, errors: CompilerError): void { render(index: number, start: Identifier, env: Environment | null): void {
const seen = new Set<Identifier>(); const seen = new Set<Identifier>();
const queue: Array<Identifier> = [start]; const queue: Array<Identifier> = [start];
while (queue.length !== 0) { while (queue.length !== 0) {
@@ -678,7 +678,7 @@ class AliasingState {
continue; continue;
} }
if (node.value.kind === 'Function') { if (node.value.kind === 'Function') {
appendFunctionErrors(errors, node.value.function); appendFunctionErrors(env, node.value.function);
} }
for (const [alias, when] of node.createdFrom) { for (const [alias, when] of node.createdFrom) {
if (when >= index) { if (when >= index) {
@@ -710,7 +710,7 @@ class AliasingState {
startKind: MutationKind, startKind: MutationKind,
loc: SourceLocation, loc: SourceLocation,
reason: MutationReason | null, reason: MutationReason | null,
errors: CompilerError, env: Environment | null,
): void { ): void {
const seen = new Map<Identifier, MutationKind>(); const seen = new Map<Identifier, MutationKind>();
const queue: Array<{ const queue: Array<{
@@ -742,7 +742,7 @@ class AliasingState {
node.transitive == null && node.transitive == null &&
node.local == null node.local == null
) { ) {
appendFunctionErrors(errors, node.value.function); appendFunctionErrors(env, node.value.function);
} }
if (transitive) { if (transitive) {
if (node.transitive == null || node.transitive.kind < kind) { if (node.transitive == null || node.transitive.kind < kind) {

View File

@@ -13,7 +13,11 @@ import {
pruneUnusedLabels, pruneUnusedLabels,
renameVariables, renameVariables,
} from '.'; } from '.';
import {CompilerError, ErrorCategory} from '../CompilerError'; import {
CompilerError,
CompilerErrorDetail,
ErrorCategory,
} from '../CompilerError';
import {Environment, ExternalFunction} from '../HIR'; import {Environment, ExternalFunction} from '../HIR';
import { import {
ArrayPattern, ArrayPattern,
@@ -358,10 +362,6 @@ function codegenReactiveFunction(
} }
} }
if (cx.errors.hasAnyErrors()) {
fn.env.recordErrors(cx.errors);
}
const countMemoBlockVisitor = new CountMemoBlockVisitor(fn.env); const countMemoBlockVisitor = new CountMemoBlockVisitor(fn.env);
visitReactiveFunction(fn, countMemoBlockVisitor, undefined); visitReactiveFunction(fn, countMemoBlockVisitor, undefined);
@@ -434,7 +434,6 @@ class Context {
*/ */
#declarations: Set<DeclarationId> = new Set(); #declarations: Set<DeclarationId> = new Set();
temp: Temporaries; temp: Temporaries;
errors: CompilerError = new CompilerError();
objectMethods: Map<IdentifierId, ObjectMethod> = new Map(); objectMethods: Map<IdentifierId, ObjectMethod> = new Map();
uniqueIdentifiers: Set<string>; uniqueIdentifiers: Set<string>;
fbtOperands: Set<IdentifierId>; fbtOperands: Set<IdentifierId>;
@@ -453,6 +452,10 @@ class Context {
this.fbtOperands = fbtOperands; this.fbtOperands = fbtOperands;
this.temp = temporaries !== null ? new Map(temporaries) : new Map(); this.temp = temporaries !== null ? new Map(temporaries) : new Map();
} }
recordError(error: CompilerErrorDetail): void {
this.env.recordError(error);
}
get nextCacheIndex(): number { get nextCacheIndex(): number {
return this.#nextCacheIndex++; return this.#nextCacheIndex++;
} }
@@ -972,12 +975,14 @@ function codegenTerminal(
loc: terminal.init.loc, loc: terminal.init.loc,
}); });
if (terminal.init.instructions.length !== 2) { if (terminal.init.instructions.length !== 2) {
cx.errors.push({ cx.recordError(
reason: 'Support non-trivial for..in inits', new CompilerErrorDetail({
category: ErrorCategory.Todo, reason: 'Support non-trivial for..in inits',
loc: terminal.init.loc, category: ErrorCategory.Todo,
suggestions: null, loc: terminal.init.loc,
}); suggestions: null,
}),
);
return t.emptyStatement(); return t.emptyStatement();
} }
const iterableCollection = terminal.init.instructions[0]; const iterableCollection = terminal.init.instructions[0];
@@ -993,12 +998,14 @@ function codegenTerminal(
break; break;
} }
case 'StoreContext': { case 'StoreContext': {
cx.errors.push({ cx.recordError(
reason: 'Support non-trivial for..in inits', new CompilerErrorDetail({
category: ErrorCategory.Todo, reason: 'Support non-trivial for..in inits',
loc: terminal.init.loc, category: ErrorCategory.Todo,
suggestions: null, loc: terminal.init.loc,
}); suggestions: null,
}),
);
return t.emptyStatement(); return t.emptyStatement();
} }
default: default:
@@ -1069,12 +1076,14 @@ function codegenTerminal(
loc: terminal.test.loc, loc: terminal.test.loc,
}); });
if (terminal.test.instructions.length !== 2) { if (terminal.test.instructions.length !== 2) {
cx.errors.push({ cx.recordError(
reason: 'Support non-trivial for..of inits', new CompilerErrorDetail({
category: ErrorCategory.Todo, reason: 'Support non-trivial for..of inits',
loc: terminal.init.loc, category: ErrorCategory.Todo,
suggestions: null, loc: terminal.init.loc,
}); suggestions: null,
}),
);
return t.emptyStatement(); return t.emptyStatement();
} }
const iterableItem = terminal.test.instructions[1]; const iterableItem = terminal.test.instructions[1];
@@ -1089,12 +1098,14 @@ function codegenTerminal(
break; break;
} }
case 'StoreContext': { case 'StoreContext': {
cx.errors.push({ cx.recordError(
reason: 'Support non-trivial for..of inits', new CompilerErrorDetail({
category: ErrorCategory.Todo, reason: 'Support non-trivial for..of inits',
loc: terminal.init.loc, category: ErrorCategory.Todo,
suggestions: null, loc: terminal.init.loc,
}); suggestions: null,
}),
);
return t.emptyStatement(); return t.emptyStatement();
} }
default: default:
@@ -2189,22 +2200,26 @@ function codegenInstructionValue(
} else { } else {
if (t.isVariableDeclaration(stmt)) { if (t.isVariableDeclaration(stmt)) {
const declarator = stmt.declarations[0]; const declarator = stmt.declarations[0];
cx.errors.push({ cx.recordError(
reason: `(CodegenReactiveFunction::codegenInstructionValue) Cannot declare variables in a value block, tried to declare '${ new CompilerErrorDetail({
(declarator.id as t.Identifier).name reason: `(CodegenReactiveFunction::codegenInstructionValue) Cannot declare variables in a value block, tried to declare '${
}'`, (declarator.id as t.Identifier).name
category: ErrorCategory.Todo, }'`,
loc: declarator.loc ?? null, category: ErrorCategory.Todo,
suggestions: null, loc: declarator.loc ?? null,
}); suggestions: null,
}),
);
return t.stringLiteral(`TODO handle ${declarator.id}`); return t.stringLiteral(`TODO handle ${declarator.id}`);
} else { } else {
cx.errors.push({ cx.recordError(
reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`, new CompilerErrorDetail({
category: ErrorCategory.Todo, reason: `(CodegenReactiveFunction::codegenInstructionValue) Handle conversion of ${stmt.type} to expression`,
loc: stmt.loc ?? null, category: ErrorCategory.Todo,
suggestions: null, loc: stmt.loc ?? null,
}); suggestions: null,
}),
);
return t.stringLiteral(`TODO handle ${stmt.type}`); return t.stringLiteral(`TODO handle ${stmt.type}`);
} }
} }

View File

@@ -5,7 +5,11 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {CompilerError, CompilerErrorDetailOptions, SourceLocation} from '..'; import {
CompilerErrorDetail,
CompilerErrorDetailOptions,
SourceLocation,
} from '..';
import { import {
ArrayExpression, ArrayExpression,
CallExpression, CallExpression,
@@ -54,7 +58,6 @@ export function transformFire(fn: HIRFunction): void {
if (!context.hasErrors()) { if (!context.hasErrors()) {
ensureNoMoreFireUses(fn, context); ensureNoMoreFireUses(fn, context);
} }
context.throwIfErrorsFound();
} }
function replaceFireFunctions(fn: HIRFunction, context: Context): void { function replaceFireFunctions(fn: HIRFunction, context: Context): void {
@@ -526,7 +529,7 @@ type FireCalleesToFireFunctionBinding = Map<
class Context { class Context {
#env: Environment; #env: Environment;
#errors: CompilerError = new CompilerError(); #hasErrors: boolean = false;
/* /*
* Used to look up the call expression passed to a `fire(callExpr())`. Gives back * Used to look up the call expression passed to a `fire(callExpr())`. Gives back
@@ -589,7 +592,8 @@ class Context {
#arrayExpressions = new Map<IdentifierId, ArrayExpression>(); #arrayExpressions = new Map<IdentifierId, ArrayExpression>();
pushError(error: CompilerErrorDetailOptions): void { pushError(error: CompilerErrorDetailOptions): void {
this.#errors.push(error); this.#hasErrors = true;
this.#env.recordError(new CompilerErrorDetail(error));
} }
withFunctionScope(fn: () => void): FireCalleesToFireFunctionBinding { withFunctionScope(fn: () => void): FireCalleesToFireFunctionBinding {
@@ -698,11 +702,7 @@ class Context {
} }
hasErrors(): boolean { hasErrors(): boolean {
return this.#errors.hasAnyErrors(); return this.#hasErrors;
}
throwIfErrorsFound(): void {
if (this.hasErrors()) throw this.#errors;
} }
} }

View File

@@ -100,7 +100,6 @@ export function validateExhaustiveDependencies(fn: HIRFunction): void {
loc: place.loc, loc: place.loc,
}); });
} }
const error = new CompilerError();
let startMemo: StartMemoize | null = null; let startMemo: StartMemoize | null = null;
function onStartMemoize( function onStartMemoize(
@@ -141,7 +140,7 @@ export function validateExhaustiveDependencies(fn: HIRFunction): void {
'all', 'all',
); );
if (diagnostic != null) { if (diagnostic != null) {
error.pushDiagnostic(diagnostic); fn.env.recordError(diagnostic);
} }
} }
@@ -206,15 +205,12 @@ export function validateExhaustiveDependencies(fn: HIRFunction): void {
effectReportMode, effectReportMode,
); );
if (diagnostic != null) { if (diagnostic != null) {
error.pushDiagnostic(diagnostic); fn.env.recordError(diagnostic);
} }
}, },
}, },
false, // isFunctionExpression false, // isFunctionExpression
); );
if (error.hasAnyErrors()) {
fn.env.recordErrors(error);
}
} }
function validateDependencies( function validateDependencies(

View File

@@ -6,13 +6,9 @@
*/ */
import * as t from '@babel/types'; import * as t from '@babel/types';
import { import {CompilerErrorDetail, ErrorCategory} from '../CompilerError';
CompilerError,
CompilerErrorDetail,
ErrorCategory,
} from '../CompilerError';
import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks'; import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks';
import {isHookName} from '../HIR/Environment'; import {Environment, isHookName} from '../HIR/Environment';
import { import {
HIRFunction, HIRFunction,
IdentifierId, IdentifierId,
@@ -90,15 +86,14 @@ function joinKinds(a: Kind, b: Kind): Kind {
export function validateHooksUsage(fn: HIRFunction): void { export function validateHooksUsage(fn: HIRFunction): void {
const unconditionalBlocks = computeUnconditionalBlocks(fn); const unconditionalBlocks = computeUnconditionalBlocks(fn);
const errors = new CompilerError();
const errorsByPlace = new Map<t.SourceLocation, CompilerErrorDetail>(); const errorsByPlace = new Map<t.SourceLocation, CompilerErrorDetail>();
function recordError( function trackError(
loc: SourceLocation, loc: SourceLocation,
errorDetail: CompilerErrorDetail, errorDetail: CompilerErrorDetail,
): void { ): void {
if (typeof loc === 'symbol') { if (typeof loc === 'symbol') {
errors.pushErrorDetail(errorDetail); fn.env.recordError(errorDetail);
} else { } else {
errorsByPlace.set(loc, errorDetail); errorsByPlace.set(loc, errorDetail);
} }
@@ -118,7 +113,7 @@ export function validateHooksUsage(fn: HIRFunction): void {
* If that same place is also used as a conditional call, upgrade the error to a conditonal hook error * If that same place is also used as a conditional call, upgrade the error to a conditonal hook error
*/ */
if (previousError === undefined || previousError.reason !== reason) { if (previousError === undefined || previousError.reason !== reason) {
recordError( trackError(
place.loc, place.loc,
new CompilerErrorDetail({ new CompilerErrorDetail({
category: ErrorCategory.Hooks, category: ErrorCategory.Hooks,
@@ -134,7 +129,7 @@ export function validateHooksUsage(fn: HIRFunction): void {
const previousError = const previousError =
typeof place.loc !== 'symbol' ? errorsByPlace.get(place.loc) : undefined; typeof place.loc !== 'symbol' ? errorsByPlace.get(place.loc) : undefined;
if (previousError === undefined) { if (previousError === undefined) {
recordError( trackError(
place.loc, place.loc,
new CompilerErrorDetail({ new CompilerErrorDetail({
category: ErrorCategory.Hooks, category: ErrorCategory.Hooks,
@@ -151,7 +146,7 @@ export function validateHooksUsage(fn: HIRFunction): void {
const previousError = const previousError =
typeof place.loc !== 'symbol' ? errorsByPlace.get(place.loc) : undefined; typeof place.loc !== 'symbol' ? errorsByPlace.get(place.loc) : undefined;
if (previousError === undefined) { if (previousError === undefined) {
recordError( trackError(
place.loc, place.loc,
new CompilerErrorDetail({ new CompilerErrorDetail({
category: ErrorCategory.Hooks, category: ErrorCategory.Hooks,
@@ -396,7 +391,7 @@ export function validateHooksUsage(fn: HIRFunction): void {
} }
case 'ObjectMethod': case 'ObjectMethod':
case 'FunctionExpression': { case 'FunctionExpression': {
visitFunctionExpression(errors, instr.value.loweredFunc.func); visitFunctionExpression(fn.env, instr.value.loweredFunc.func);
break; break;
} }
default: { default: {
@@ -421,20 +416,17 @@ export function validateHooksUsage(fn: HIRFunction): void {
} }
for (const [, error] of errorsByPlace) { for (const [, error] of errorsByPlace) {
errors.pushErrorDetail(error); fn.env.recordError(error);
}
if (errors.hasAnyErrors()) {
fn.env.recordErrors(errors);
} }
} }
function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void { function visitFunctionExpression(env: Environment, fn: HIRFunction): void {
for (const [, block] of fn.body.blocks) { for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) { for (const instr of block.instructions) {
switch (instr.value.kind) { switch (instr.value.kind) {
case 'ObjectMethod': case 'ObjectMethod':
case 'FunctionExpression': { case 'FunctionExpression': {
visitFunctionExpression(errors, instr.value.loweredFunc.func); visitFunctionExpression(env, instr.value.loweredFunc.func);
break; break;
} }
case 'MethodCall': case 'MethodCall':
@@ -445,7 +437,7 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void {
: instr.value.property; : instr.value.property;
const hookKind = getHookKind(fn.env, callee.identifier); const hookKind = getHookKind(fn.env, callee.identifier);
if (hookKind != null) { if (hookKind != null) {
errors.pushErrorDetail( env.recordError(
new CompilerErrorDetail({ new CompilerErrorDetail({
category: ErrorCategory.Hooks, category: ErrorCategory.Hooks,
reason: reason:

View File

@@ -5,8 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {CompilerError} from '..'; import {CompilerErrorDetail, ErrorCategory} from '../CompilerError';
import {ErrorCategory} from '../CompilerError';
import { import {
Identifier, Identifier,
Instruction, Instruction,
@@ -18,6 +17,7 @@ import {
isUseInsertionEffectHookType, isUseInsertionEffectHookType,
isUseLayoutEffectHookType, isUseLayoutEffectHookType,
} from '../HIR'; } from '../HIR';
import {Environment} from '../HIR/Environment';
import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables'; import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables';
import { import {
ReactiveFunctionVisitor, ReactiveFunctionVisitor,
@@ -49,17 +49,15 @@ import {
* ``` * ```
*/ */
export function validateMemoizedEffectDependencies(fn: ReactiveFunction): void { export function validateMemoizedEffectDependencies(fn: ReactiveFunction): void {
const errors = new CompilerError(); visitReactiveFunction(fn, new Visitor(), fn.env);
visitReactiveFunction(fn, new Visitor(), errors);
fn.env.recordErrors(errors);
} }
class Visitor extends ReactiveFunctionVisitor<CompilerError> { class Visitor extends ReactiveFunctionVisitor<Environment> {
scopes: Set<ScopeId> = new Set(); scopes: Set<ScopeId> = new Set();
override visitScope( override visitScope(
scopeBlock: ReactiveScopeBlock, scopeBlock: ReactiveScopeBlock,
state: CompilerError, state: Environment,
): void { ): void {
this.traverseScope(scopeBlock, state); this.traverseScope(scopeBlock, state);
@@ -87,7 +85,7 @@ class Visitor extends ReactiveFunctionVisitor<CompilerError> {
override visitInstruction( override visitInstruction(
instruction: ReactiveInstruction, instruction: ReactiveInstruction,
state: CompilerError, state: Environment,
): void { ): void {
this.traverseInstruction(instruction, state); this.traverseInstruction(instruction, state);
if ( if (
@@ -105,14 +103,16 @@ class Visitor extends ReactiveFunctionVisitor<CompilerError> {
(isMutable(instruction as Instruction, deps) || (isMutable(instruction as Instruction, deps) ||
isUnmemoized(deps.identifier, this.scopes)) isUnmemoized(deps.identifier, this.scopes))
) { ) {
state.push({ state.recordError(
category: ErrorCategory.EffectDependencies, new CompilerErrorDetail({
reason: category: ErrorCategory.EffectDependencies,
'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior', reason:
description: null, 'React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior',
loc: typeof instruction.loc !== 'symbol' ? instruction.loc : null, description: null,
suggestions: null, loc: typeof instruction.loc !== 'symbol' ? instruction.loc : null,
}); suggestions: null,
}),
);
} }
} }
} }

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {CompilerError, CompilerErrorDetail, EnvironmentConfig} from '..'; import {CompilerErrorDetail, EnvironmentConfig} from '..';
import {ErrorCategory} from '../CompilerError'; import {ErrorCategory} from '../CompilerError';
import {HIRFunction, IdentifierId} from '../HIR'; import {HIRFunction, IdentifierId} from '../HIR';
import {DEFAULT_GLOBALS} from '../HIR/Globals'; import {DEFAULT_GLOBALS} from '../HIR/Globals';
@@ -28,7 +28,6 @@ export function validateNoCapitalizedCalls(fn: HIRFunction): void {
); );
}; };
const errors = new CompilerError();
const capitalLoadGlobals = new Map<IdentifierId, string>(); const capitalLoadGlobals = new Map<IdentifierId, string>();
const capitalizedProperties = new Map<IdentifierId, string>(); const capitalizedProperties = new Map<IdentifierId, string>();
const reason = const reason =
@@ -80,20 +79,19 @@ export function validateNoCapitalizedCalls(fn: HIRFunction): void {
const propertyIdentifier = value.property.identifier.id; const propertyIdentifier = value.property.identifier.id;
const propertyName = capitalizedProperties.get(propertyIdentifier); const propertyName = capitalizedProperties.get(propertyIdentifier);
if (propertyName != null) { if (propertyName != null) {
errors.push({ fn.env.recordError(
category: ErrorCategory.CapitalizedCalls, new CompilerErrorDetail({
reason, category: ErrorCategory.CapitalizedCalls,
description: `${propertyName} may be a component`, reason,
loc: value.loc, description: `${propertyName} may be a component`,
suggestions: null, loc: value.loc,
}); suggestions: null,
}),
);
} }
break; break;
} }
} }
} }
} }
if (errors.hasAnyErrors()) {
fn.env.recordErrors(errors);
}
} }

View File

@@ -6,7 +6,7 @@
*/ */
import {CompilerError, SourceLocation} from '..'; import {CompilerError, SourceLocation} from '..';
import {ErrorCategory} from '../CompilerError'; import {CompilerErrorDetail, ErrorCategory} from '../CompilerError';
import { import {
ArrayExpression, ArrayExpression,
BlockId, BlockId,
@@ -20,6 +20,7 @@ import {
eachInstructionValueOperand, eachInstructionValueOperand,
eachTerminalOperand, eachTerminalOperand,
} from '../HIR/visitors'; } from '../HIR/visitors';
import {Environment} from '../HIR/Environment';
/** /**
* Validates that useEffect is not used for derived computations which could/should * Validates that useEffect is not used for derived computations which could/should
@@ -49,8 +50,6 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
const functions: Map<IdentifierId, FunctionExpression> = new Map(); const functions: Map<IdentifierId, FunctionExpression> = new Map();
const locals: Map<IdentifierId, IdentifierId> = new Map(); const locals: Map<IdentifierId, IdentifierId> = new Map();
const errors = new CompilerError();
for (const block of fn.body.blocks.values()) { for (const block of fn.body.blocks.values()) {
for (const instr of block.instructions) { for (const instr of block.instructions) {
const {lvalue, value} = instr; const {lvalue, value} = instr;
@@ -90,20 +89,19 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
validateEffect( validateEffect(
effectFunction.loweredFunc.func, effectFunction.loweredFunc.func,
dependencies, dependencies,
errors, fn.env,
); );
} }
} }
} }
} }
} }
fn.env.recordErrors(errors);
} }
function validateEffect( function validateEffect(
effectFunction: HIRFunction, effectFunction: HIRFunction,
effectDeps: Array<IdentifierId>, effectDeps: Array<IdentifierId>,
errors: CompilerError, env: Environment,
): void { ): void {
for (const operand of effectFunction.context) { for (const operand of effectFunction.context) {
if (isSetStateType(operand.identifier)) { if (isSetStateType(operand.identifier)) {
@@ -217,13 +215,15 @@ function validateEffect(
} }
for (const loc of setStateLocations) { for (const loc of setStateLocations) {
errors.push({ env.recordError(
category: ErrorCategory.EffectDerivationsOfState, new CompilerErrorDetail({
reason: category: ErrorCategory.EffectDerivationsOfState,
'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)', reason:
description: null, 'Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)',
loc, description: null,
suggestions: null, loc,
}); suggestions: null,
}),
);
} }
} }

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {CompilerDiagnostic, CompilerError, Effect} from '..'; import {CompilerDiagnostic, Effect} from '..';
import {ErrorCategory} from '../CompilerError'; import {ErrorCategory} from '../CompilerError';
import { import {
HIRFunction, HIRFunction,
@@ -43,7 +43,6 @@ import {AliasingEffect} from '../Inference/AliasingEffects';
* that are passed where a frozen value is expected and rejects them. * that are passed where a frozen value is expected and rejects them.
*/ */
export function validateNoFreezingKnownMutableFunctions(fn: HIRFunction): void { export function validateNoFreezingKnownMutableFunctions(fn: HIRFunction): void {
const errors = new CompilerError();
const contextMutationEffects: Map< const contextMutationEffects: Map<
IdentifierId, IdentifierId,
Extract<AliasingEffect, {kind: 'Mutate'} | {kind: 'MutateTransitive'}> Extract<AliasingEffect, {kind: 'Mutate'} | {kind: 'MutateTransitive'}>
@@ -60,7 +59,7 @@ export function validateNoFreezingKnownMutableFunctions(fn: HIRFunction): void {
place.identifier.name.kind === 'named' place.identifier.name.kind === 'named'
? `\`${place.identifier.name.value}\`` ? `\`${place.identifier.name.value}\``
: 'a local variable'; : 'a local variable';
errors.pushDiagnostic( fn.env.recordError(
CompilerDiagnostic.create({ CompilerDiagnostic.create({
category: ErrorCategory.Immutability, category: ErrorCategory.Immutability,
reason: 'Cannot modify local variables after render completes', reason: 'Cannot modify local variables after render completes',
@@ -159,7 +158,4 @@ export function validateNoFreezingKnownMutableFunctions(fn: HIRFunction): void {
visitOperand(operand); visitOperand(operand);
} }
} }
if (errors.hasAnyErrors()) {
fn.env.recordErrors(errors);
}
} }

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import {CompilerDiagnostic, CompilerError} from '..'; import {CompilerDiagnostic} from '..';
import {ErrorCategory} from '../CompilerError'; import {ErrorCategory} from '../CompilerError';
import {HIRFunction} from '../HIR'; import {HIRFunction} from '../HIR';
import {getFunctionCallSignature} from '../Inference/InferMutationAliasingEffects'; import {getFunctionCallSignature} from '../Inference/InferMutationAliasingEffects';
@@ -20,7 +20,6 @@ import {getFunctionCallSignature} from '../Inference/InferMutationAliasingEffect
* and use it here. * and use it here.
*/ */
export function validateNoImpureFunctionsInRender(fn: HIRFunction): void { export function validateNoImpureFunctionsInRender(fn: HIRFunction): void {
const errors = new CompilerError();
for (const [, block] of fn.body.blocks) { for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) { for (const instr of block.instructions) {
const value = instr.value; const value = instr.value;
@@ -32,7 +31,7 @@ export function validateNoImpureFunctionsInRender(fn: HIRFunction): void {
callee.identifier.type, callee.identifier.type,
); );
if (signature != null && signature.impure === true) { if (signature != null && signature.impure === true) {
errors.pushDiagnostic( fn.env.recordError(
CompilerDiagnostic.create({ CompilerDiagnostic.create({
category: ErrorCategory.Purity, category: ErrorCategory.Purity,
reason: 'Cannot call impure function during render', reason: 'Cannot call impure function during render',
@@ -52,7 +51,4 @@ export function validateNoImpureFunctionsInRender(fn: HIRFunction): void {
} }
} }
} }
if (errors.hasAnyErrors()) {
fn.env.recordErrors(errors);
}
} }

View File

@@ -126,8 +126,8 @@ export function validateNoRefAccessInRender(fn: HIRFunction): void {
collectTemporariesSidemap(fn, env); collectTemporariesSidemap(fn, env);
const errors = new CompilerError(); const errors = new CompilerError();
validateNoRefAccessInRenderImpl(fn, env, errors); validateNoRefAccessInRenderImpl(fn, env, errors);
if (errors.hasAnyErrors()) { for (const detail of errors.details) {
fn.env.recordErrors(errors); fn.env.recordError(detail);
} }
} }

View File

@@ -48,8 +48,8 @@ export function validateNoSetStateInRender(fn: HIRFunction): void {
fn, fn,
unconditionalSetStateFunctions, unconditionalSetStateFunctions,
); );
if (errors.hasAnyErrors()) { for (const detail of errors.details) {
fn.env.recordErrors(errors); fn.env.recordError(detail);
} }
} }

View File

@@ -27,6 +27,7 @@ import {
ScopeId, ScopeId,
SourceLocation, SourceLocation,
} from '../HIR'; } from '../HIR';
import {Environment} from '../HIR/Environment';
import {printIdentifier, printManualMemoDependency} from '../HIR/PrintHIR'; import {printIdentifier, printManualMemoDependency} from '../HIR/PrintHIR';
import { import {
eachInstructionValueLValue, eachInstructionValueLValue,
@@ -48,11 +49,10 @@ import {getOrInsertDefault} from '../Utils/utils';
*/ */
export function validatePreservedManualMemoization(fn: ReactiveFunction): void { export function validatePreservedManualMemoization(fn: ReactiveFunction): void {
const state = { const state = {
errors: new CompilerError(), env: fn.env,
manualMemoState: null, manualMemoState: null,
}; };
visitReactiveFunction(fn, new Visitor(), state); visitReactiveFunction(fn, new Visitor(), state);
fn.env.recordErrors(state.errors);
} }
const DEBUG = false; const DEBUG = false;
@@ -110,7 +110,7 @@ type ManualMemoBlockState = {
}; };
type VisitorState = { type VisitorState = {
errors: CompilerError; env: Environment;
manualMemoState: ManualMemoBlockState | null; manualMemoState: ManualMemoBlockState | null;
}; };
@@ -230,7 +230,7 @@ function validateInferredDep(
temporaries: Map<IdentifierId, ManualMemoDependency>, temporaries: Map<IdentifierId, ManualMemoDependency>,
declsWithinMemoBlock: Set<DeclarationId>, declsWithinMemoBlock: Set<DeclarationId>,
validDepsInMemoBlock: Array<ManualMemoDependency>, validDepsInMemoBlock: Array<ManualMemoDependency>,
errorState: CompilerError, errorState: Environment,
memoLocation: SourceLocation, memoLocation: SourceLocation,
): void { ): void {
let normalizedDep: ManualMemoDependency; let normalizedDep: ManualMemoDependency;
@@ -280,7 +280,7 @@ function validateInferredDep(
errorDiagnostic = merge(errorDiagnostic ?? compareResult, compareResult); errorDiagnostic = merge(errorDiagnostic ?? compareResult, compareResult);
} }
} }
errorState.pushDiagnostic( errorState.recordError(
CompilerDiagnostic.create({ CompilerDiagnostic.create({
category: ErrorCategory.PreserveManualMemo, category: ErrorCategory.PreserveManualMemo,
reason: 'Existing memoization could not be preserved', reason: 'Existing memoization could not be preserved',
@@ -426,7 +426,7 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
this.temporaries, this.temporaries,
state.manualMemoState.decls, state.manualMemoState.decls,
state.manualMemoState.depsFromSource, state.manualMemoState.depsFromSource,
state.errors, state.env,
state.manualMemoState.loc, state.manualMemoState.loc,
); );
} }
@@ -529,7 +529,7 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
!this.scopes.has(identifier.scope.id) && !this.scopes.has(identifier.scope.id) &&
!this.prunedScopes.has(identifier.scope.id) !this.prunedScopes.has(identifier.scope.id)
) { ) {
state.errors.pushDiagnostic( state.env.recordError(
CompilerDiagnostic.create({ CompilerDiagnostic.create({
category: ErrorCategory.PreserveManualMemo, category: ErrorCategory.PreserveManualMemo,
reason: 'Existing memoization could not be preserved', reason: 'Existing memoization could not be preserved',
@@ -575,7 +575,7 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
for (const identifier of decls) { for (const identifier of decls) {
if (isUnmemoized(identifier, this.scopes)) { if (isUnmemoized(identifier, this.scopes)) {
state.errors.pushDiagnostic( state.env.recordError(
CompilerDiagnostic.create({ CompilerDiagnostic.create({
category: ErrorCategory.PreserveManualMemo, category: ErrorCategory.PreserveManualMemo,
reason: 'Existing memoization could not be preserved', reason: 'Existing memoization could not be preserved',

View File

@@ -7,7 +7,7 @@
import {NodePath} from '@babel/traverse'; import {NodePath} from '@babel/traverse';
import * as t from '@babel/types'; import * as t from '@babel/types';
import {CompilerDiagnostic, CompilerError, ErrorCategory} from '..'; import {CompilerDiagnostic, ErrorCategory} from '..';
import {CodegenFunction} from '../ReactiveScopes'; import {CodegenFunction} from '../ReactiveScopes';
import {Environment} from '../HIR/Environment'; import {Environment} from '../HIR/Environment';
@@ -125,8 +125,6 @@ export function validateSourceLocations(
generatedAst: CodegenFunction, generatedAst: CodegenFunction,
env: Environment, env: Environment,
): void { ): void {
const errors = new CompilerError();
/* /*
* Step 1: Collect important locations from the original source * Step 1: Collect important locations from the original source
* Note: Multiple node types can share the same location (e.g. VariableDeclarator and Identifier) * Note: Multiple node types can share the same location (e.g. VariableDeclarator and Identifier)
@@ -241,7 +239,7 @@ export function validateSourceLocations(
loc: t.SourceLocation, loc: t.SourceLocation,
nodeType: string, nodeType: string,
): void => { ): void => {
errors.pushDiagnostic( env.recordError(
CompilerDiagnostic.create({ CompilerDiagnostic.create({
category: ErrorCategory.Todo, category: ErrorCategory.Todo,
reason: 'Important source location missing in generated code', reason: 'Important source location missing in generated code',
@@ -261,7 +259,7 @@ export function validateSourceLocations(
expectedType: string, expectedType: string,
actualTypes: Set<string>, actualTypes: Set<string>,
): void => { ): void => {
errors.pushDiagnostic( env.recordError(
CompilerDiagnostic.create({ CompilerDiagnostic.create({
category: ErrorCategory.Todo, category: ErrorCategory.Todo,
reason: reason:
@@ -309,6 +307,4 @@ export function validateSourceLocations(
} }
} }
} }
env.recordErrors(errors);
} }

View File

@@ -16,13 +16,13 @@ import {
IdentifierId, IdentifierId,
SourceLocation, SourceLocation,
} from '../HIR'; } from '../HIR';
import {Environment} from '../HIR/Environment';
import { import {
eachInstructionValueOperand, eachInstructionValueOperand,
eachTerminalOperand, eachTerminalOperand,
} from '../HIR/visitors'; } from '../HIR/visitors';
export function validateUseMemo(fn: HIRFunction): void { export function validateUseMemo(fn: HIRFunction): void {
const errors = new CompilerError();
const voidMemoErrors = new CompilerError(); const voidMemoErrors = new CompilerError();
const useMemos = new Set<IdentifierId>(); const useMemos = new Set<IdentifierId>();
const react = new Set<IdentifierId>(); const react = new Set<IdentifierId>();
@@ -90,7 +90,7 @@ export function validateUseMemo(fn: HIRFunction): void {
firstParam.kind === 'Identifier' firstParam.kind === 'Identifier'
? firstParam.loc ? firstParam.loc
: firstParam.place.loc; : firstParam.place.loc;
errors.pushDiagnostic( fn.env.recordError(
CompilerDiagnostic.create({ CompilerDiagnostic.create({
category: ErrorCategory.UseMemo, category: ErrorCategory.UseMemo,
reason: 'useMemo() callbacks may not accept parameters', reason: 'useMemo() callbacks may not accept parameters',
@@ -106,7 +106,7 @@ export function validateUseMemo(fn: HIRFunction): void {
} }
if (body.loweredFunc.func.async || body.loweredFunc.func.generator) { if (body.loweredFunc.func.async || body.loweredFunc.func.generator) {
errors.pushDiagnostic( fn.env.recordError(
CompilerDiagnostic.create({ CompilerDiagnostic.create({
category: ErrorCategory.UseMemo, category: ErrorCategory.UseMemo,
reason: reason:
@@ -122,7 +122,7 @@ export function validateUseMemo(fn: HIRFunction): void {
); );
} }
validateNoContextVariableAssignment(body.loweredFunc.func, errors); validateNoContextVariableAssignment(body.loweredFunc.func, fn.env);
if (fn.env.config.validateNoVoidUseMemo) { if (fn.env.config.validateNoVoidUseMemo) {
if (!hasNonVoidReturn(body.loweredFunc.func)) { if (!hasNonVoidReturn(body.loweredFunc.func)) {
@@ -176,14 +176,11 @@ export function validateUseMemo(fn: HIRFunction): void {
} }
} }
fn.env.logErrors(voidMemoErrors.asResult()); fn.env.logErrors(voidMemoErrors.asResult());
if (errors.hasAnyErrors()) {
fn.env.recordErrors(errors);
}
} }
function validateNoContextVariableAssignment( function validateNoContextVariableAssignment(
fn: HIRFunction, fn: HIRFunction,
errors: CompilerError, env: Environment,
): void { ): void {
const context = new Set(fn.context.map(place => place.identifier.id)); const context = new Set(fn.context.map(place => place.identifier.id));
for (const block of fn.body.blocks.values()) { for (const block of fn.body.blocks.values()) {
@@ -192,7 +189,7 @@ function validateNoContextVariableAssignment(
switch (value.kind) { switch (value.kind) {
case 'StoreContext': { case 'StoreContext': {
if (context.has(value.lvalue.place.identifier.id)) { if (context.has(value.lvalue.place.identifier.id)) {
errors.pushDiagnostic( env.recordError(
CompilerDiagnostic.create({ CompilerDiagnostic.create({
category: ErrorCategory.UseMemo, category: ErrorCategory.UseMemo,
reason: reason:

View File

@@ -24,7 +24,7 @@ function Component({prop1, bar}) {
## Code ## Code
```javascript ```javascript
import { c as _c, useFire } from "react/compiler-runtime"; // @validateNoCapitalizedCalls @enableFire @panicThreshold:"none" import { useFire } from "react/compiler-runtime"; // @validateNoCapitalizedCalls @enableFire @panicThreshold:"none"
import { fire } from "react"; import { fire } from "react";
const CapitalizedCall = require("shared-runtime").sum; const CapitalizedCall = require("shared-runtime").sum;

View File

@@ -68,7 +68,7 @@ function NonFireComponent({ prop1 }) {
function FireComponent(props) { function FireComponent(props) {
const $ = _c(3); const $ = _c(3);
const foo = _temp3; const foo = _temp;
const t0 = useFire(foo); const t0 = useFire(foo);
let t1; let t1;
if ($[0] !== props || $[1] !== t0) { if ($[0] !== props || $[1] !== t0) {
@@ -85,7 +85,7 @@ function FireComponent(props) {
return null; return null;
} }
function _temp3(props_0) { function _temp(props_0) {
console.log(props_0); console.log(props_0);
} }

View File

@@ -29,7 +29,7 @@ function Component(props) {
## Code ## Code
```javascript ```javascript
import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none" import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none"
import { fire, useEffect } from "react"; import { fire, useEffect } from "react";
import { Stringify } from "shared-runtime"; import { Stringify } from "shared-runtime";
@@ -38,7 +38,7 @@ import { Stringify } from "shared-runtime";
* hook usage) disabled * hook usage) disabled
*/ */
function Component(props) { function Component(props) {
const foo = _temp2; const foo = _temp;
if (props.cond) { if (props.cond) {
const t0 = useFire(foo); const t0 = useFire(foo);
@@ -49,7 +49,7 @@ function Component(props) {
return <Stringify />; return <Stringify />;
} }
function _temp2(props_0) { function _temp(props_0) {
console.log(props_0); console.log(props_0);
} }

View File

@@ -23,15 +23,14 @@ function Component(props, useDynamicHook) {
## Code ## Code
```javascript ```javascript
import { $dispatcherGuard } from "react-compiler-runtime"; import { useFire } from "react/compiler-runtime";
import { c as _c, useFire } from "react/compiler-runtime";
import { useEffect, fire } from "react"; import { useEffect, fire } from "react";
function Component(props, useDynamicHook) { function Component(props, useDynamicHook) {
"use memo"; "use memo";
useDynamicHook(); useDynamicHook();
const foo = _temp2; const foo = _temp;
const t0 = useFire(foo); const t0 = useFire(foo);
useEffect(() => { useEffect(() => {
@@ -40,7 +39,7 @@ function Component(props, useDynamicHook) {
return <div>hello world</div>; return <div>hello world</div>;
} }
function _temp2(props_0) { function _temp(props_0) {
console.log(props_0); console.log(props_0);
} }