[compiler] Remove tryRecord, add catch-all error handling, fix remaining throws

Remove `tryRecord()` from the compilation pipeline now that all passes record
errors directly via `env.recordError()` / `env.recordErrors()`. A single
catch-all try/catch in Program.ts provides the safety net for any pass that
incorrectly throws instead of recording.

Key changes:
- Remove all ~64 `env.tryRecord()` wrappers in Pipeline.ts
- Delete `tryRecord()` method from Environment.ts
- Add `CompileUnexpectedThrow` logger event so thrown errors are detectable
- Log `CompileUnexpectedThrow` in Program.ts catch-all for non-invariant throws
- Fail snap tests on `CompileUnexpectedThrow` to surface pass bugs in dev
- Convert throwTodo/throwDiagnostic calls in HIRBuilder (fbt, this),
  CodegenReactiveFunction (for-in/for-of), and BuildReactiveFunction to
  record errors or use invariants as appropriate
- Remove try/catch from BuildHIR's lower() since inner throws are now recorded
- CollectOptionalChainDependencies: return null instead of throwing on
  unsupported optional chain patterns (graceful optimization skip)
This commit is contained in:
Joe Savona
2026-02-20 15:59:36 -08:00
parent 39b5e7c3af
commit 61633098fa
25 changed files with 340 additions and 410 deletions

View File

@@ -254,6 +254,7 @@ export type LoggerEvent =
| CompileErrorEvent
| CompileDiagnosticEvent
| CompileSkipEvent
| CompileUnexpectedThrowEvent
| PipelineErrorEvent
| TimingEvent
| AutoDepsDecorationsEvent
@@ -290,6 +291,11 @@ export type PipelineErrorEvent = {
fnLoc: t.SourceLocation | null;
data: string;
};
export type CompileUnexpectedThrowEvent = {
kind: 'CompileUnexpectedThrow';
fnLoc: t.SourceLocation | null;
data: string;
};
export type TimingEvent = {
kind: 'Timing';
measurement: PerformanceMeasure;

View File

@@ -13,7 +13,6 @@ import {CompilerError} from '../CompilerError';
import {Err, Ok, Result} from '../Utils/Result';
import {
HIRFunction,
IdentifierId,
ReactiveFunction,
assertConsistentIdentifiers,
assertTerminalPredsExist,
@@ -166,17 +165,11 @@ function runWithEnvironment(
const hir = lower(func, env);
log({kind: 'hir', name: 'HIR', value: hir});
env.tryRecord(() => {
pruneMaybeThrows(hir);
});
pruneMaybeThrows(hir);
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
env.tryRecord(() => {
validateContextVariableLValues(hir);
});
env.tryRecord(() => {
validateUseMemo(hir);
});
validateContextVariableLValues(hir);
validateUseMemo(hir);
if (
env.enableDropManualMemoization &&
@@ -188,136 +181,94 @@ function runWithEnvironment(
log({kind: 'hir', name: 'DropManualMemoization', value: hir});
}
env.tryRecord(() => {
inlineImmediatelyInvokedFunctionExpressions(hir);
});
inlineImmediatelyInvokedFunctionExpressions(hir);
log({
kind: 'hir',
name: 'InlineImmediatelyInvokedFunctionExpressions',
value: hir,
});
env.tryRecord(() => {
mergeConsecutiveBlocks(hir);
});
mergeConsecutiveBlocks(hir);
log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir});
assertConsistentIdentifiers(hir);
assertTerminalSuccessorsExist(hir);
env.tryRecord(() => {
enterSSA(hir);
});
enterSSA(hir);
log({kind: 'hir', name: 'SSA', value: hir});
env.tryRecord(() => {
eliminateRedundantPhi(hir);
});
eliminateRedundantPhi(hir);
log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir});
assertConsistentIdentifiers(hir);
env.tryRecord(() => {
constantPropagation(hir);
});
constantPropagation(hir);
log({kind: 'hir', name: 'ConstantPropagation', value: hir});
env.tryRecord(() => {
inferTypes(hir);
});
inferTypes(hir);
log({kind: 'hir', name: 'InferTypes', value: hir});
if (env.enableValidations) {
if (env.config.validateHooksUsage) {
env.tryRecord(() => {
validateHooksUsage(hir);
});
validateHooksUsage(hir);
}
if (env.config.validateNoCapitalizedCalls) {
env.tryRecord(() => {
validateNoCapitalizedCalls(hir);
});
validateNoCapitalizedCalls(hir);
}
}
if (env.config.enableFire) {
env.tryRecord(() => {
transformFire(hir);
});
transformFire(hir);
log({kind: 'hir', name: 'TransformFire', value: hir});
}
if (env.config.lowerContextAccess) {
env.tryRecord(() => {
lowerContextAccess(hir, env.config.lowerContextAccess!);
});
lowerContextAccess(hir, env.config.lowerContextAccess!);
}
env.tryRecord(() => {
optimizePropsMethodCalls(hir);
});
optimizePropsMethodCalls(hir);
log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir});
env.tryRecord(() => {
analyseFunctions(hir);
});
analyseFunctions(hir);
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
env.tryRecord(() => {
inferMutationAliasingEffects(hir);
});
inferMutationAliasingEffects(hir);
log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir});
if (env.outputMode === 'ssr') {
env.tryRecord(() => {
optimizeForSSR(hir);
});
optimizeForSSR(hir);
log({kind: 'hir', name: 'OptimizeForSSR', value: hir});
}
// Note: Has to come after infer reference effects because "dead" code may still affect inference
env.tryRecord(() => {
deadCodeElimination(hir);
});
deadCodeElimination(hir);
log({kind: 'hir', name: 'DeadCodeElimination', value: hir});
if (env.config.enableInstructionReordering) {
env.tryRecord(() => {
instructionReordering(hir);
});
instructionReordering(hir);
log({kind: 'hir', name: 'InstructionReordering', value: hir});
}
env.tryRecord(() => {
pruneMaybeThrows(hir);
});
pruneMaybeThrows(hir);
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
env.tryRecord(() => {
inferMutationAliasingRanges(hir, {
isFunctionExpression: false,
});
inferMutationAliasingRanges(hir, {
isFunctionExpression: false,
});
log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir});
if (env.enableValidations) {
env.tryRecord(() => {
validateLocalsNotReassignedAfterRender(hir);
});
validateLocalsNotReassignedAfterRender(hir);
if (env.config.assertValidMutableRanges) {
assertValidMutableRanges(hir);
}
if (env.config.validateRefAccessDuringRender) {
env.tryRecord(() => {
validateNoRefAccessInRender(hir);
});
validateNoRefAccessInRender(hir);
}
if (env.config.validateNoSetStateInRender) {
env.tryRecord(() => {
validateNoSetStateInRender(hir);
});
validateNoSetStateInRender(hir);
}
if (
@@ -326,9 +277,7 @@ function runWithEnvironment(
) {
env.logErrors(validateNoDerivedComputationsInEffects_exp(hir));
} else if (env.config.validateNoDerivedComputationsInEffects) {
env.tryRecord(() => {
validateNoDerivedComputationsInEffects(hir);
});
validateNoDerivedComputationsInEffects(hir);
}
if (env.config.validateNoSetStateInEffects && env.outputMode === 'lint') {
@@ -340,19 +289,13 @@ function runWithEnvironment(
}
if (env.config.validateNoImpureFunctionsInRender) {
env.tryRecord(() => {
validateNoImpureFunctionsInRender(hir);
});
validateNoImpureFunctionsInRender(hir);
}
env.tryRecord(() => {
validateNoFreezingKnownMutableFunctions(hir);
});
validateNoFreezingKnownMutableFunctions(hir);
}
env.tryRecord(() => {
inferReactivePlaces(hir);
});
inferReactivePlaces(hir);
log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
if (env.enableValidations) {
@@ -361,15 +304,11 @@ function runWithEnvironment(
env.config.validateExhaustiveEffectDependencies
) {
// NOTE: this relies on reactivity inference running first
env.tryRecord(() => {
validateExhaustiveDependencies(hir);
});
validateExhaustiveDependencies(hir);
}
}
env.tryRecord(() => {
rewriteInstructionKindsBasedOnReassignment(hir);
});
rewriteInstructionKindsBasedOnReassignment(hir);
log({
kind: 'hir',
name: 'RewriteInstructionKindsBasedOnReassignment',
@@ -390,16 +329,11 @@ function runWithEnvironment(
* if inferred memoization is enabled. This makes all later passes which
* transform reactive-scope labeled instructions no-ops.
*/
env.tryRecord(() => {
inferReactiveScopeVariables(hir);
});
inferReactiveScopeVariables(hir);
log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
}
let fbtOperands: Set<IdentifierId> = new Set();
env.tryRecord(() => {
fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);
});
const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);
log({
kind: 'hir',
name: 'MemoizeFbtAndMacroOperandsInSameScope',
@@ -407,15 +341,11 @@ function runWithEnvironment(
});
if (env.config.enableJsxOutlining) {
env.tryRecord(() => {
outlineJSX(hir);
});
outlineJSX(hir);
}
if (env.config.enableNameAnonymousFunctions) {
env.tryRecord(() => {
nameAnonymousFunctions(hir);
});
nameAnonymousFunctions(hir);
log({
kind: 'hir',
name: 'NameAnonymousFunctions',
@@ -424,51 +354,39 @@ function runWithEnvironment(
}
if (env.config.enableFunctionOutlining) {
env.tryRecord(() => {
outlineFunctions(hir, fbtOperands);
});
outlineFunctions(hir, fbtOperands);
log({kind: 'hir', name: 'OutlineFunctions', value: hir});
}
env.tryRecord(() => {
alignMethodCallScopes(hir);
});
alignMethodCallScopes(hir);
log({
kind: 'hir',
name: 'AlignMethodCallScopes',
value: hir,
});
env.tryRecord(() => {
alignObjectMethodScopes(hir);
});
alignObjectMethodScopes(hir);
log({
kind: 'hir',
name: 'AlignObjectMethodScopes',
value: hir,
});
env.tryRecord(() => {
pruneUnusedLabelsHIR(hir);
});
pruneUnusedLabelsHIR(hir);
log({
kind: 'hir',
name: 'PruneUnusedLabelsHIR',
value: hir,
});
env.tryRecord(() => {
alignReactiveScopesToBlockScopesHIR(hir);
});
alignReactiveScopesToBlockScopesHIR(hir);
log({
kind: 'hir',
name: 'AlignReactiveScopesToBlockScopesHIR',
value: hir,
});
env.tryRecord(() => {
mergeOverlappingReactiveScopesHIR(hir);
});
mergeOverlappingReactiveScopesHIR(hir);
log({
kind: 'hir',
name: 'MergeOverlappingReactiveScopesHIR',
@@ -476,9 +394,7 @@ function runWithEnvironment(
});
assertValidBlockNesting(hir);
env.tryRecord(() => {
buildReactiveScopeTerminalsHIR(hir);
});
buildReactiveScopeTerminalsHIR(hir);
log({
kind: 'hir',
name: 'BuildReactiveScopeTerminalsHIR',
@@ -487,18 +403,14 @@ function runWithEnvironment(
assertValidBlockNesting(hir);
env.tryRecord(() => {
flattenReactiveLoopsHIR(hir);
});
flattenReactiveLoopsHIR(hir);
log({
kind: 'hir',
name: 'FlattenReactiveLoopsHIR',
value: hir,
});
env.tryRecord(() => {
flattenScopesWithHooksOrUseHIR(hir);
});
flattenScopesWithHooksOrUseHIR(hir);
log({
kind: 'hir',
name: 'FlattenScopesWithHooksOrUseHIR',
@@ -506,9 +418,8 @@ function runWithEnvironment(
});
assertTerminalSuccessorsExist(hir);
assertTerminalPredsExist(hir);
env.tryRecord(() => {
propagateScopeDependenciesHIR(hir);
});
propagateScopeDependenciesHIR(hir);
log({
kind: 'hir',
name: 'PropagateScopeDependenciesHIR',
@@ -516,9 +427,7 @@ function runWithEnvironment(
});
if (env.config.inferEffectDependencies) {
env.tryRecord(() => {
inferEffectDependencies(hir);
});
inferEffectDependencies(hir);
log({
kind: 'hir',
name: 'InferEffectDependencies',
@@ -527,9 +436,7 @@ function runWithEnvironment(
}
if (env.config.inlineJsxTransform) {
env.tryRecord(() => {
inlineJsxTransform(hir, env.config.inlineJsxTransform!);
});
inlineJsxTransform(hir, env.config.inlineJsxTransform!);
log({
kind: 'hir',
name: 'inlineJsxTransform',
@@ -537,10 +444,7 @@ function runWithEnvironment(
});
}
let reactiveFunction!: ReactiveFunction;
env.tryRecord(() => {
reactiveFunction = buildReactiveFunction(hir);
});
const reactiveFunction = buildReactiveFunction(hir);
log({
kind: 'reactive',
name: 'BuildReactiveFunction',
@@ -549,9 +453,7 @@ function runWithEnvironment(
assertWellFormedBreakTargets(reactiveFunction);
env.tryRecord(() => {
pruneUnusedLabels(reactiveFunction);
});
pruneUnusedLabels(reactiveFunction);
log({
kind: 'reactive',
name: 'PruneUnusedLabels',
@@ -559,45 +461,35 @@ function runWithEnvironment(
});
assertScopeInstructionsWithinScopes(reactiveFunction);
env.tryRecord(() => {
pruneNonEscapingScopes(reactiveFunction);
});
pruneNonEscapingScopes(reactiveFunction);
log({
kind: 'reactive',
name: 'PruneNonEscapingScopes',
value: reactiveFunction,
});
env.tryRecord(() => {
pruneNonReactiveDependencies(reactiveFunction);
});
pruneNonReactiveDependencies(reactiveFunction);
log({
kind: 'reactive',
name: 'PruneNonReactiveDependencies',
value: reactiveFunction,
});
env.tryRecord(() => {
pruneUnusedScopes(reactiveFunction);
});
pruneUnusedScopes(reactiveFunction);
log({
kind: 'reactive',
name: 'PruneUnusedScopes',
value: reactiveFunction,
});
env.tryRecord(() => {
mergeReactiveScopesThatInvalidateTogether(reactiveFunction);
});
mergeReactiveScopesThatInvalidateTogether(reactiveFunction);
log({
kind: 'reactive',
name: 'MergeReactiveScopesThatInvalidateTogether',
value: reactiveFunction,
});
env.tryRecord(() => {
pruneAlwaysInvalidatingScopes(reactiveFunction);
});
pruneAlwaysInvalidatingScopes(reactiveFunction);
log({
kind: 'reactive',
name: 'PruneAlwaysInvalidatingScopes',
@@ -605,9 +497,7 @@ function runWithEnvironment(
});
if (env.config.enableChangeDetectionForDebugging != null) {
env.tryRecord(() => {
pruneInitializationDependencies(reactiveFunction);
});
pruneInitializationDependencies(reactiveFunction);
log({
kind: 'reactive',
name: 'PruneInitializationDependencies',
@@ -615,64 +505,49 @@ function runWithEnvironment(
});
}
env.tryRecord(() => {
propagateEarlyReturns(reactiveFunction);
});
propagateEarlyReturns(reactiveFunction);
log({
kind: 'reactive',
name: 'PropagateEarlyReturns',
value: reactiveFunction,
});
env.tryRecord(() => {
pruneUnusedLValues(reactiveFunction);
});
pruneUnusedLValues(reactiveFunction);
log({
kind: 'reactive',
name: 'PruneUnusedLValues',
value: reactiveFunction,
});
env.tryRecord(() => {
promoteUsedTemporaries(reactiveFunction);
});
promoteUsedTemporaries(reactiveFunction);
log({
kind: 'reactive',
name: 'PromoteUsedTemporaries',
value: reactiveFunction,
});
env.tryRecord(() => {
extractScopeDeclarationsFromDestructuring(reactiveFunction);
});
extractScopeDeclarationsFromDestructuring(reactiveFunction);
log({
kind: 'reactive',
name: 'ExtractScopeDeclarationsFromDestructuring',
value: reactiveFunction,
});
env.tryRecord(() => {
stabilizeBlockIds(reactiveFunction);
});
stabilizeBlockIds(reactiveFunction);
log({
kind: 'reactive',
name: 'StabilizeBlockIds',
value: reactiveFunction,
});
let uniqueIdentifiers: Set<string> = new Set();
env.tryRecord(() => {
uniqueIdentifiers = renameVariables(reactiveFunction);
});
const uniqueIdentifiers = renameVariables(reactiveFunction);
log({
kind: 'reactive',
name: 'RenameVariables',
value: reactiveFunction,
});
env.tryRecord(() => {
pruneHoistedContexts(reactiveFunction);
});
pruneHoistedContexts(reactiveFunction);
log({
kind: 'reactive',
name: 'PruneHoistedContexts',
@@ -680,18 +555,14 @@ function runWithEnvironment(
});
if (env.config.validateMemoizedEffectDependencies) {
env.tryRecord(() => {
validateMemoizedEffectDependencies(reactiveFunction);
});
validateMemoizedEffectDependencies(reactiveFunction);
}
if (
env.config.enablePreserveExistingMemoizationGuarantees ||
env.config.validatePreserveExistingMemoizationGuarantees
) {
env.tryRecord(() => {
validatePreservedManualMemoization(reactiveFunction);
});
validatePreservedManualMemoization(reactiveFunction);
}
const ast = codegenFunction(reactiveFunction, {
@@ -704,9 +575,7 @@ function runWithEnvironment(
}
if (env.config.validateSourceLocations) {
env.tryRecord(() => {
validateSourceLocations(func, ast, env);
});
validateSourceLocations(func, ast, env);
}
/**

View File

@@ -744,6 +744,20 @@ function tryCompileFunction(
return {kind: 'error', error: result.unwrapErr()};
}
} catch (err) {
/**
* A pass incorrectly threw instead of recording the error.
* Log for detection in development.
*/
if (
err instanceof CompilerError &&
err.details.every(detail => detail.category !== ErrorCategory.Invariant)
) {
programContext.logEvent({
kind: 'CompileUnexpectedThrow',
fnLoc: fn.node.loc ?? null,
data: err.toString(),
});
}
return {kind: 'error', error: err};
}
}
@@ -792,7 +806,20 @@ function retryCompileFunction(
}
return compiledFn;
} catch (err) {
// TODO: we might want to log error here, but this will also result in duplicate logging
/**
* A pass incorrectly threw instead of recording the error.
* Log for detection in development.
*/
if (
err instanceof CompilerError &&
err.details.every(detail => detail.category !== ErrorCategory.Invariant)
) {
programContext.logEvent({
kind: 'CompileUnexpectedThrow',
fnLoc: fn.node.loc ?? null,
data: err.toString(),
});
}
if (err instanceof CompilerError) {
programContext.retryErrors.push({fn, error: err});
}

View File

@@ -185,47 +185,32 @@ export function lower(
let directives: Array<string> = [];
const body = func.get('body');
try {
if (body.isExpression()) {
const fallthrough = builder.reserve('block');
const terminal: ReturnTerminal = {
kind: 'return',
returnVariant: 'Implicit',
loc: GeneratedSource,
value: lowerExpressionToTemporary(builder, body),
id: makeInstructionId(0),
effects: null,
};
builder.terminateWithContinuation(terminal, fallthrough);
} else if (body.isBlockStatement()) {
lowerStatement(builder, body);
directives = body.get('directives').map(d => d.node.value.value);
} else {
builder.errors.pushDiagnostic(
CompilerDiagnostic.create({
category: ErrorCategory.Syntax,
reason: `Unexpected function body kind`,
description: `Expected function body to be an expression or a block statement, got \`${body.type}\``,
}).withDetails({
kind: 'error',
loc: body.node.loc ?? null,
message: 'Expected a block statement or expression',
}),
);
}
} catch (err) {
if (err instanceof CompilerError) {
// Re-throw invariant errors immediately
for (const detail of err.details) {
if (detail.category === ErrorCategory.Invariant) {
throw err;
}
}
// Record non-invariant errors and continue to produce partial HIR
builder.errors.merge(err);
} else {
throw err;
}
if (body.isExpression()) {
const fallthrough = builder.reserve('block');
const terminal: ReturnTerminal = {
kind: 'return',
returnVariant: 'Implicit',
loc: GeneratedSource,
value: lowerExpressionToTemporary(builder, body),
id: makeInstructionId(0),
effects: null,
};
builder.terminateWithContinuation(terminal, fallthrough);
} else if (body.isBlockStatement()) {
lowerStatement(builder, body);
directives = body.get('directives').map(d => d.node.value.value);
} else {
builder.errors.pushDiagnostic(
CompilerDiagnostic.create({
category: ErrorCategory.Syntax,
reason: `Unexpected function body kind`,
description: `Expected function body to be an expression or a block statement, got \`${body.type}\``,
}).withDetails({
kind: 'error',
loc: body.node.loc ?? null,
message: 'Expected a block statement or expression',
}),
);
}
let validatedId: HIRFunction['id'] = null;

View File

@@ -310,16 +310,13 @@ function traverseOptionalBlock(
* - a optional base block with a separate nested optional-chain (e.g. a(c?.d)?.d)
*/
const testBlock = context.blocks.get(maybeTest.terminal.fallthrough)!;
if (testBlock!.terminal.kind !== 'branch') {
/**
* Fallthrough of the inner optional should be a block with no
* instructions, terminating with Test($<temporary written to from
* StoreLocal>)
*/
CompilerError.throwTodo({
reason: `Unexpected terminal kind \`${testBlock.terminal.kind}\` for optional fallthrough block`,
loc: maybeTest.terminal.loc,
});
/**
* Fallthrough of the inner optional should be a block with no
* instructions, terminating with Test($<temporary written to from
* StoreLocal>)
*/
if (testBlock.terminal.kind !== 'branch') {
return null;
}
/**
* Recurse into inner optional blocks to collect inner optional-chain

View File

@@ -1019,29 +1019,6 @@ export class Environment {
return this.#errors;
}
/**
* Wraps a callback in try/catch: if the callback throws a CompilerError
* that is NOT an invariant, the error is recorded and execution continues.
* Non-CompilerError exceptions and invariants are re-thrown.
*/
tryRecord(fn: () => void): void {
try {
fn();
} catch (err) {
if (err instanceof CompilerError) {
// Check if any detail is an invariant — if so, re-throw
for (const detail of err.details) {
if (detail.category === ErrorCategory.Invariant) {
throw err;
}
}
this.recordErrors(err);
} else {
throw err;
}
}
}
isContextIdentifier(node: t.Identifier): boolean {
return this.#contextIdentifiers.has(node);
}

View File

@@ -308,33 +308,23 @@ export default class HIRBuilder {
resolveBinding(node: t.Identifier): Identifier {
if (node.name === 'fbt') {
CompilerError.throwDiagnostic({
this.errors.push({
category: ErrorCategory.Todo,
reason: 'Support local variables named `fbt`',
description:
'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported',
details: [
{
kind: 'error',
message: 'Rename to avoid conflict with fbt plugin',
loc: node.loc ?? GeneratedSource,
},
],
loc: node.loc ?? GeneratedSource,
suggestions: null,
});
}
if (node.name === 'this') {
CompilerError.throwDiagnostic({
this.errors.push({
category: ErrorCategory.UnsupportedSyntax,
reason: '`this` is not supported syntax',
description:
'React Compiler does not support compiling functions that use `this`',
details: [
{
kind: 'error',
message: '`this` was used here',
loc: node.loc ?? GeneratedSource,
},
],
loc: node.loc ?? GeneratedSource,
suggestions: null,
});
}
const originalName = node.name;

View File

@@ -1007,11 +1007,10 @@ class Driver {
const test = this.visitValueBlock(testBlockId, loc);
const testBlock = this.cx.ir.blocks.get(test.block)!;
if (testBlock.terminal.kind !== 'branch') {
CompilerError.throwTodo({
reason: `Unexpected terminal kind \`${testBlock.terminal.kind}\` for ${terminalKind} test block`,
description: null,
CompilerError.invariant(false, {
reason: `Expected a branch terminal for ${terminalKind} test block`,
description: `Got \`${testBlock.terminal.kind}\``,
loc: testBlock.terminal.loc,
suggestions: null,
});
}
return {

View File

@@ -972,12 +972,13 @@ function codegenTerminal(
loc: terminal.init.loc,
});
if (terminal.init.instructions.length !== 2) {
CompilerError.throwTodo({
cx.errors.push({
reason: 'Support non-trivial for..in inits',
description: null,
category: ErrorCategory.Todo,
loc: terminal.init.loc,
suggestions: null,
});
return t.emptyStatement();
}
const iterableCollection = terminal.init.instructions[0];
const iterableItem = terminal.init.instructions[1];
@@ -992,12 +993,13 @@ function codegenTerminal(
break;
}
case 'StoreContext': {
CompilerError.throwTodo({
cx.errors.push({
reason: 'Support non-trivial for..in inits',
description: null,
category: ErrorCategory.Todo,
loc: terminal.init.loc,
suggestions: null,
});
return t.emptyStatement();
}
default:
CompilerError.invariant(false, {
@@ -1067,12 +1069,13 @@ function codegenTerminal(
loc: terminal.test.loc,
});
if (terminal.test.instructions.length !== 2) {
CompilerError.throwTodo({
cx.errors.push({
reason: 'Support non-trivial for..of inits',
description: null,
category: ErrorCategory.Todo,
loc: terminal.init.loc,
suggestions: null,
});
return t.emptyStatement();
}
const iterableItem = terminal.test.instructions[1];
let lval: t.LVal;
@@ -1086,12 +1089,13 @@ function codegenTerminal(
break;
}
case 'StoreContext': {
CompilerError.throwTodo({
cx.errors.push({
reason: 'Support non-trivial for..of inits',
description: null,
category: ErrorCategory.Todo,
loc: terminal.init.loc,
suggestions: null,
});
return t.emptyStatement();
}
default:
CompilerError.invariant(false, {

View File

@@ -24,9 +24,9 @@ function useThing(fn) {
```
Found 1 error:
Invariant: [HIRBuilder] Unexpected null block
Error: Expected a non-reserved identifier name
expected block 0 to exist.
`this` is a reserved word in JavaScript and cannot be used as an identifier name.
```

View File

@@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized
Todo: [hoisting] EnterSSA: Expected identifier to be defined before being used
<unknown> x$1.
Identifier x$1 is undefined.
error.dont-hoist-inline-reference.ts:3:21
error.dont-hoist-inline-reference.ts:3:2
1 | import {identity} from 'shared-runtime';
2 | function useInvalid() {
> 3 | const x = identity(x);
| ^ this is uninitialized
| ^^^^^^^^^^^^^^^^^^^^^^ [hoisting] EnterSSA: Expected identifier to be defined before being used
4 | return x;
5 | }
6 |

View File

@@ -1,39 +0,0 @@
## Input
```javascript
function useFoo(props: {value: {x: string; y: string} | null}) {
const value = props.value;
return createArray(value?.x, value?.y)?.join(', ');
}
function createArray<T>(...args: Array<T>): Array<T> {
return args;
}
export const FIXTURE_ENTRYPONT = {
fn: useFoo,
props: [{value: null}],
};
```
## Error
```
Found 1 error:
Todo: Unexpected terminal kind `optional` for optional fallthrough block
error.todo-optional-call-chain-in-optional.ts:3:21
1 | function useFoo(props: {value: {x: string; y: string} | null}) {
2 | const value = props.value;
> 3 | return createArray(value?.x, value?.y)?.join(', ');
| ^^^^^^^^ Unexpected terminal kind `optional` for optional fallthrough block
4 | }
5 |
6 | function createArray<T>(...args: Array<T>): Array<T> {
```

View File

@@ -50,7 +50,7 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Found 4 errors:
Todo: Support local variables named `fbt`
@@ -60,10 +60,49 @@ error.todo-fbt-as-local.ts:18:19
16 |
17 | function Foo(props) {
> 18 | const getText1 = fbt =>
| ^^^ Rename to avoid conflict with fbt plugin
| ^^^ Support local variables named `fbt`
19 | fbt(
20 | `Hello, ${fbt.param('(key) name', identity(props.name))}!`,
21 | '(description) Greeting'
Todo: Support local variables named `fbt`
Local variables named `fbt` may conflict with the fbt plugin and are not yet supported.
error.todo-fbt-as-local.ts:18:19
16 |
17 | function Foo(props) {
> 18 | const getText1 = fbt =>
| ^^^ Support local variables named `fbt`
19 | fbt(
20 | `Hello, ${fbt.param('(key) name', identity(props.name))}!`,
21 | '(description) Greeting'
Todo: Support local variables named `fbt`
Local variables named `fbt` may conflict with the fbt plugin and are not yet supported.
error.todo-fbt-as-local.ts:18:19
16 |
17 | function Foo(props) {
> 18 | const getText1 = fbt =>
| ^^^ Support local variables named `fbt`
19 | fbt(
20 | `Hello, ${fbt.param('(key) name', identity(props.name))}!`,
21 | '(description) Greeting'
Todo: Support local variables named `fbt`
Local variables named `fbt` may conflict with the fbt plugin and are not yet supported.
error.todo-fbt-as-local.ts:24:19
22 | );
23 |
> 24 | const getText2 = fbt =>
| ^^^ Support local variables named `fbt`
25 | fbt(
26 | `Goodbye, ${fbt.param('(key) name', identity(props.name))}!`,
27 | '(description) Greeting2'
```

View File

@@ -16,17 +16,15 @@ function Component(props) {
```
Found 1 error:
Todo: Support local variables named `fbt`
Invariant: <fbt> tags should be module-level imports
Local variables named `fbt` may conflict with the fbt plugin and are not yet supported.
error.todo-locally-require-fbt.ts:2:8
1 | function Component(props) {
> 2 | const fbt = require('fbt');
| ^^^ Rename to avoid conflict with fbt plugin
error.todo-locally-require-fbt.ts:4:10
2 | const fbt = require('fbt');
3 |
4 | return <fbt desc="Description">{'Text'}</fbt>;
> 4 | return <fbt desc="Description">{'Text'}</fbt>;
| ^^^ <fbt> tags should be module-level imports
5 | }
6 |
```

View File

@@ -1,40 +0,0 @@
## Input
```javascript
// @enablePropagateDepsInHIR
function useFoo(props: {value: {x: string; y: string} | null}) {
const value = props.value;
return createArray(value?.x, value?.y)?.join(', ');
}
function createArray<T>(...args: Array<T>): Array<T> {
return args;
}
export const FIXTURE_ENTRYPONT = {
fn: useFoo,
props: [{value: null}],
};
```
## Error
```
Found 1 error:
Todo: Unexpected terminal kind `optional` for optional fallthrough block
error.todo-optional-call-chain-in-optional.ts:4:21
2 | function useFoo(props: {value: {x: string; y: string} | null}) {
3 | const value = props.value;
> 4 | return createArray(value?.x, value?.y)?.join(', ');
| ^^^^^^^^ Unexpected terminal kind `optional` for optional fallthrough block
5 | }
6 |
7 | function createArray<T>(...args: Array<T>): Array<T> {
```

View File

@@ -0,0 +1,54 @@
## Input
```javascript
// @enablePropagateDepsInHIR
function useFoo(props: {value: {x: string; y: string} | null}) {
const value = props.value;
return createArray(value?.x, value?.y)?.join(', ');
}
function createArray<T>(...args: Array<T>): Array<T> {
return args;
}
export const FIXTURE_ENTRYPONT = {
fn: useFoo,
props: [{value: null}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
function useFoo(props) {
const $ = _c(3);
const value = props.value;
let t0;
if ($[0] !== value?.x || $[1] !== value?.y) {
t0 = createArray(value?.x, value?.y)?.join(", ");
$[0] = value?.x;
$[1] = value?.y;
$[2] = t0;
} else {
t0 = $[2];
}
return t0;
}
function createArray(...t0) {
const args = t0;
return args;
}
export const FIXTURE_ENTRYPONT = {
fn: useFoo,
props: [{ value: null }],
};
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -0,0 +1,53 @@
## Input
```javascript
function useFoo(props: {value: {x: string; y: string} | null}) {
const value = props.value;
return createArray(value?.x, value?.y)?.join(', ');
}
function createArray<T>(...args: Array<T>): Array<T> {
return args;
}
export const FIXTURE_ENTRYPONT = {
fn: useFoo,
props: [{value: null}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function useFoo(props) {
const $ = _c(3);
const value = props.value;
let t0;
if ($[0] !== value?.x || $[1] !== value?.y) {
t0 = createArray(value?.x, value?.y)?.join(", ");
$[0] = value?.x;
$[1] = value?.y;
$[2] = t0;
} else {
t0 = $[2];
}
return t0;
}
function createArray(...t0) {
const args = t0;
return args;
}
export const FIXTURE_ENTRYPONT = {
fn: useFoo,
props: [{ value: null }],
};
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -29,15 +29,15 @@ function Component(props) {
```
Found 1 error:
Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized
Error: Cannot compile `fire`
<unknown> foo$42:TFunction<BuiltInFunction>(): :TPrimitive.
All uses of foo must be either used with a fire() call in this effect or not used with a fire() call at all. foo was used with fire() on line 10:10 in this effect.
error.invalid-mix-fire-and-no-fire.ts:11:6
9 | function nested() {
10 | fire(foo(props));
> 11 | foo(props);
| ^^^ this is uninitialized
| ^^^ Cannot compile `fire`
12 | }
13 |
14 | nested();

View File

@@ -24,15 +24,15 @@ function Component({bar, baz}) {
```
Found 1 error:
Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized
Error: Cannot compile `fire`
<unknown> $43:TFunction<BuiltInFire>(): :TFunction<BuiltInFireFunction>(): :TPoly.
fire() can only take in a single call expression as an argument but received multiple arguments.
error.invalid-multiple-args.ts:9:4
7 | };
8 | useEffect(() => {
> 9 | fire(foo(bar), baz);
| ^^^^ this is uninitialized
| ^^^^^^^^^^^^^^^^^^^ Cannot compile `fire`
10 | });
11 |
12 | return null;

View File

@@ -24,15 +24,15 @@ function Component(props) {
```
Found 1 error:
Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized
Error: Cannot compile `fire`
<unknown> $32:TFunction<BuiltInFire>(): :TFunction<BuiltInFireFunction>(): :TPoly.
`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed.
error.invalid-not-call.ts:9:4
7 | };
8 | useEffect(() => {
> 9 | fire(props);
| ^^^^ this is uninitialized
| ^^^^^^^^^^^ Cannot compile `fire`
10 | });
11 |
12 | return null;

View File

@@ -24,15 +24,15 @@ function Component(props) {
```
Found 1 error:
Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized
Error: Cannot compile `fire`
<unknown> $32:TFunction<BuiltInFire>(): :TFunction<BuiltInFireFunction>(): :TPoly.
fire() can only take in a single call expression as an argument but received a spread argument.
error.invalid-spread.ts:9:4
7 | };
8 | useEffect(() => {
> 9 | fire(...foo);
| ^^^^ this is uninitialized
| ^^^^^^^^^^^^ Cannot compile `fire`
10 | });
11 |
12 | return null;

View File

@@ -24,15 +24,15 @@ function Component(props) {
```
Found 1 error:
Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized
Error: Cannot compile `fire`
<unknown> $34:TFunction<BuiltInFire>(): :TFunction<BuiltInFireFunction>(): :TPoly.
`fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed.
error.todo-method.ts:9:4
7 | };
8 | useEffect(() => {
> 9 | fire(props.foo());
| ^^^^ this is uninitialized
| ^^^^^^^^^^^^^^^^^ Cannot compile `fire`
10 | });
11 |
12 | return null;

View File

@@ -378,6 +378,17 @@ export async function transformFixtureInput(
msg: 'Expected nothing to be compiled (from `// @expectNothingCompiled`), but some functions compiled or errored',
};
}
const unexpectedThrows = logs.filter(
log => log.event.kind === 'CompileUnexpectedThrow',
);
if (unexpectedThrows.length > 0) {
return {
kind: 'err',
msg:
`Compiler pass(es) threw instead of recording errors:\n` +
unexpectedThrows.map(l => (l.event as any).data).join('\n'),
};
}
return {
kind: 'ok',
value: {