[compiler] Phase 2+7: Wrap pipeline passes in tryRecord for fault tolerance

- Change runWithEnvironment/run/compileFn to return Result<CodegenFunction, CompilerError>
- Wrap all pipeline passes in env.tryRecord() to catch and record CompilerErrors
- Record inference pass errors via env.recordErrors() instead of throwing
- Handle codegen Result explicitly, returning Err on failure
- Add final error check: return Err(env.aggregateErrors()) if any errors accumulated
- Update tryCompileFunction and retryCompileFunction in Program.ts to handle Result
- Keep lint-only passes using env.logErrors() (non-blocking)
- Update 52 test fixture expectations that now report additional errors

This is the core integration that enables fault tolerance: errors are caught,
recorded, and the pipeline continues to discover more errors.
This commit is contained in:
Joe Savona
2026-02-20 11:01:43 -08:00
parent af88b0b778
commit cb44b4f395
55 changed files with 977 additions and 179 deletions

View File

@@ -51,20 +51,20 @@ Add error accumulation to the `Environment` class so that any pass can record er
Change `runWithEnvironment` to run all passes and check for errors at the end instead of letting exceptions propagate.
- [ ] **2.1 Change `runWithEnvironment` return type** (`src/Entrypoint/Pipeline.ts`)
- [x] **2.1 Change `runWithEnvironment` return type** (`src/Entrypoint/Pipeline.ts`)
- Change return type from `CodegenFunction` to `Result<CodegenFunction, CompilerError>`
- At the end of the pipeline, check `env.hasErrors()`:
- If no errors: return `Ok(ast)`
- If errors: return `Err(env.aggregateErrors())`
- [ ] **2.2 Update `compileFn` to propagate the Result** (`src/Entrypoint/Pipeline.ts`)
- [x] **2.2 Update `compileFn` to propagate the Result** (`src/Entrypoint/Pipeline.ts`)
- Change `compileFn` return type from `CodegenFunction` to `Result<CodegenFunction, CompilerError>`
- Propagate the Result from `runWithEnvironment`
- [ ] **2.3 Update `run` to propagate the Result** (`src/Entrypoint/Pipeline.ts`)
- [x] **2.3 Update `run` to propagate the Result** (`src/Entrypoint/Pipeline.ts`)
- Same change for the internal `run` function
- [ ] **2.4 Update callers in Program.ts** (`src/Entrypoint/Program.ts`)
- [x] **2.4 Update callers in Program.ts** (`src/Entrypoint/Program.ts`)
- In `tryCompileFunction`, change from try/catch around `compileFn` to handling the `Result`:
- If `Ok(codegenFn)`: return the compiled function
- If `Err(compilerError)`: return `{kind: 'error', error: compilerError}`
@@ -248,31 +248,31 @@ The inference passes are the most critical to handle correctly because they prod
Walk through `runWithEnvironment` and wrap each pass call site. This is the integration work tying Phases 3-6 together.
- [ ] **7.1 Wrap `lower()` call** (line 163)
- [x] **7.1 Wrap `lower()` call** (line 163)
- Change from `lower(func, env).unwrap()` to `lower(func, env)` (direct return after Phase 3.1)
- [ ] **7.2 Wrap validation calls that use `.unwrap()`** (lines 169-303)
- [x] **7.2 Wrap validation calls that use `.unwrap()`** (lines 169-303)
- Remove `.unwrap()` from all validation calls after they're updated in Phase 4
- For validations guarded by `env.enableValidations`, keep the guard but remove the `.unwrap()`
- [ ] **7.3 Wrap inference calls** (lines 233-267)
- [x] **7.3 Wrap inference calls** (lines 233-267)
- After Phase 5, `inferMutationAliasingEffects` and `inferMutationAliasingRanges` record errors directly
- Remove the `mutabilityAliasingErrors` / `mutabilityAliasingRangeErrors` variables and their conditional throw logic
- [ ] **7.4 Wrap `env.logErrors()` calls** (lines 286-331)
- [x] **7.4 Wrap `env.logErrors()` calls** (lines 286-331)
- After Phase 4.13-4.16, these passes record on env directly
- Remove the `env.logErrors()` wrapper calls
- [ ] **7.5 Wrap codegen** (lines 575-578)
- [x] **7.5 Wrap codegen** (lines 575-578)
- After Phase 6.1, `codegenFunction` returns directly
- Remove the `.unwrap()`
- [ ] **7.6 Add final error check** (end of `runWithEnvironment`)
- [x] **7.6 Add final error check** (end of `runWithEnvironment`)
- After all passes complete, check `env.hasErrors()`
- If no errors: return `Ok(ast)`
- If errors: return `Err(env.aggregateErrors())`
- [ ] **7.7 Consider wrapping each pass in `env.tryRecord()`** as a safety net
- [x] **7.7 Consider wrapping each pass in `env.tryRecord()`** as a safety net
- Even after individual passes are updated, wrapping each pass call in `env.tryRecord()` provides defense-in-depth
- If a pass unexpectedly throws a CompilerError (e.g., from a code path we missed), it gets caught and recorded rather than aborting the pipeline
- Non-CompilerError exceptions and invariants still propagate immediately
@@ -318,3 +318,10 @@ Walk through `runWithEnvironment` and wrap each pass call site. This is the inte
- The `assertConsistentIdentifiers`, `assertTerminalSuccessorsExist`, `assertTerminalPredsExist`, `assertValidBlockNesting`, `assertValidMutableRanges`, `assertWellFormedBreakTargets`, `assertScopeInstructionsWithinScopes` assertion functions should continue to throw — they are invariant checks on internal data structure consistency
- The `panicThreshold` mechanism in Program.ts should continue to work — it now operates on the aggregated error from the Result rather than a caught exception, but the behavior is the same
## Key Learnings
* **Phase 2+7 (Pipeline tryRecord wrapping) was sufficient for basic fault tolerance.** Wrapping all passes in `env.tryRecord()` immediately enabled the compiler to continue past errors that previously threw. This caused 52 test fixtures to produce additional errors that were previously masked by the first error bailing out. For example, `error.todo-reassign-const` previously reported only "Support destructuring of context variables" but now also reports the immutability violation.
* **Lint-only passes (Pattern B: `env.logErrors()`) should not use `tryRecord()`/`recordError()`** because those errors are intentionally non-blocking. They are reported via the logger only and should not cause the pipeline to return `Err`. The `logErrors` pattern was kept for `validateNoDerivedComputationsInEffects_exp`, `validateNoSetStateInEffects`, `validateNoJSXInTryStatement`, and `validateStaticComponents`.
* **Inference passes that return `Result` with validation errors** (`inferMutationAliasingEffects`, `inferMutationAliasingRanges`) were changed to record errors via `env.recordErrors()` instead of throwing, allowing subsequent passes to proceed.
* **Value-producing passes** (`memoizeFbtAndMacroOperandsInSameScope`, `renameVariables`, `buildReactiveFunction`) need safe default values when wrapped in `tryRecord()` since the callback can't return values. We initialize with empty defaults (e.g., `new Set()`) before the `tryRecord()` call.

View File

@@ -9,8 +9,11 @@ import {NodePath} from '@babel/traverse';
import * as t from '@babel/types';
import prettyFormat from 'pretty-format';
import {CompilerOutputMode, Logger, ProgramContext} from '.';
import {CompilerError} from '../CompilerError';
import {Err, Ok, Result} from '../Utils/Result';
import {
HIRFunction,
IdentifierId,
ReactiveFunction,
assertConsistentIdentifiers,
assertTerminalPredsExist,
@@ -125,7 +128,7 @@ function run(
logger: Logger | null,
filename: string | null,
code: string | null,
): CodegenFunction {
): Result<CodegenFunction, CompilerError> {
const contextIdentifiers = findContextIdentifiers(func);
const env = new Environment(
func.scope,
@@ -156,18 +159,24 @@ function runWithEnvironment(
t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
>,
env: Environment,
): CodegenFunction {
): Result<CodegenFunction, CompilerError> {
const log = (value: CompilerPipelineValue): void => {
env.logger?.debugLogIRs?.(value);
};
const hir = lower(func, env).unwrap();
log({kind: 'hir', name: 'HIR', value: hir});
pruneMaybeThrows(hir);
env.tryRecord(() => {
pruneMaybeThrows(hir);
});
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
validateContextVariableLValues(hir);
validateUseMemo(hir).unwrap();
env.tryRecord(() => {
validateContextVariableLValues(hir);
});
env.tryRecord(() => {
validateUseMemo(hir).unwrap();
});
if (
env.enableDropManualMemoization &&
@@ -175,84 +184,118 @@ function runWithEnvironment(
!env.config.disableMemoizationForDebugging &&
!env.config.enableChangeDetectionForDebugging
) {
dropManualMemoization(hir).unwrap();
env.tryRecord(() => {
dropManualMemoization(hir).unwrap();
});
log({kind: 'hir', name: 'DropManualMemoization', value: hir});
}
inlineImmediatelyInvokedFunctionExpressions(hir);
env.tryRecord(() => {
inlineImmediatelyInvokedFunctionExpressions(hir);
});
log({
kind: 'hir',
name: 'InlineImmediatelyInvokedFunctionExpressions',
value: hir,
});
mergeConsecutiveBlocks(hir);
env.tryRecord(() => {
mergeConsecutiveBlocks(hir);
});
log({kind: 'hir', name: 'MergeConsecutiveBlocks', value: hir});
assertConsistentIdentifiers(hir);
assertTerminalSuccessorsExist(hir);
enterSSA(hir);
env.tryRecord(() => {
enterSSA(hir);
});
log({kind: 'hir', name: 'SSA', value: hir});
eliminateRedundantPhi(hir);
env.tryRecord(() => {
eliminateRedundantPhi(hir);
});
log({kind: 'hir', name: 'EliminateRedundantPhi', value: hir});
assertConsistentIdentifiers(hir);
constantPropagation(hir);
env.tryRecord(() => {
constantPropagation(hir);
});
log({kind: 'hir', name: 'ConstantPropagation', value: hir});
inferTypes(hir);
env.tryRecord(() => {
inferTypes(hir);
});
log({kind: 'hir', name: 'InferTypes', value: hir});
if (env.enableValidations) {
if (env.config.validateHooksUsage) {
validateHooksUsage(hir).unwrap();
env.tryRecord(() => {
validateHooksUsage(hir).unwrap();
});
}
if (env.config.validateNoCapitalizedCalls) {
validateNoCapitalizedCalls(hir).unwrap();
env.tryRecord(() => {
validateNoCapitalizedCalls(hir).unwrap();
});
}
}
if (env.config.enableFire) {
transformFire(hir);
env.tryRecord(() => {
transformFire(hir);
});
log({kind: 'hir', name: 'TransformFire', value: hir});
}
if (env.config.lowerContextAccess) {
lowerContextAccess(hir, env.config.lowerContextAccess);
env.tryRecord(() => {
lowerContextAccess(hir, env.config.lowerContextAccess!);
});
}
optimizePropsMethodCalls(hir);
env.tryRecord(() => {
optimizePropsMethodCalls(hir);
});
log({kind: 'hir', name: 'OptimizePropsMethodCalls', value: hir});
analyseFunctions(hir);
env.tryRecord(() => {
analyseFunctions(hir);
});
log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
const mutabilityAliasingErrors = inferMutationAliasingEffects(hir);
log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir});
if (env.enableValidations) {
if (mutabilityAliasingErrors.isErr()) {
throw mutabilityAliasingErrors.unwrapErr();
env.recordErrors(mutabilityAliasingErrors.unwrapErr());
}
}
if (env.outputMode === 'ssr') {
optimizeForSSR(hir);
env.tryRecord(() => {
optimizeForSSR(hir);
});
log({kind: 'hir', name: 'OptimizeForSSR', value: hir});
}
// Note: Has to come after infer reference effects because "dead" code may still affect inference
deadCodeElimination(hir);
env.tryRecord(() => {
deadCodeElimination(hir);
});
log({kind: 'hir', name: 'DeadCodeElimination', value: hir});
if (env.config.enableInstructionReordering) {
instructionReordering(hir);
env.tryRecord(() => {
instructionReordering(hir);
});
log({kind: 'hir', name: 'InstructionReordering', value: hir});
}
pruneMaybeThrows(hir);
env.tryRecord(() => {
pruneMaybeThrows(hir);
});
log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
const mutabilityAliasingRangeErrors = inferMutationAliasingRanges(hir, {
@@ -261,9 +304,11 @@ function runWithEnvironment(
log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir});
if (env.enableValidations) {
if (mutabilityAliasingRangeErrors.isErr()) {
throw mutabilityAliasingRangeErrors.unwrapErr();
env.recordErrors(mutabilityAliasingRangeErrors.unwrapErr());
}
validateLocalsNotReassignedAfterRender(hir);
env.tryRecord(() => {
validateLocalsNotReassignedAfterRender(hir);
});
}
if (env.enableValidations) {
@@ -272,11 +317,15 @@ function runWithEnvironment(
}
if (env.config.validateRefAccessDuringRender) {
validateNoRefAccessInRender(hir).unwrap();
env.tryRecord(() => {
validateNoRefAccessInRender(hir).unwrap();
});
}
if (env.config.validateNoSetStateInRender) {
validateNoSetStateInRender(hir).unwrap();
env.tryRecord(() => {
validateNoSetStateInRender(hir).unwrap();
});
}
if (
@@ -285,7 +334,9 @@ function runWithEnvironment(
) {
env.logErrors(validateNoDerivedComputationsInEffects_exp(hir));
} else if (env.config.validateNoDerivedComputationsInEffects) {
validateNoDerivedComputationsInEffects(hir);
env.tryRecord(() => {
validateNoDerivedComputationsInEffects(hir);
});
}
if (env.config.validateNoSetStateInEffects && env.outputMode === 'lint') {
@@ -297,13 +348,19 @@ function runWithEnvironment(
}
if (env.config.validateNoImpureFunctionsInRender) {
validateNoImpureFunctionsInRender(hir).unwrap();
env.tryRecord(() => {
validateNoImpureFunctionsInRender(hir).unwrap();
});
}
validateNoFreezingKnownMutableFunctions(hir).unwrap();
env.tryRecord(() => {
validateNoFreezingKnownMutableFunctions(hir).unwrap();
});
}
inferReactivePlaces(hir);
env.tryRecord(() => {
inferReactivePlaces(hir);
});
log({kind: 'hir', name: 'InferReactivePlaces', value: hir});
if (env.enableValidations) {
@@ -312,11 +369,15 @@ function runWithEnvironment(
env.config.validateExhaustiveEffectDependencies
) {
// NOTE: this relies on reactivity inference running first
validateExhaustiveDependencies(hir).unwrap();
env.tryRecord(() => {
validateExhaustiveDependencies(hir).unwrap();
});
}
}
rewriteInstructionKindsBasedOnReassignment(hir);
env.tryRecord(() => {
rewriteInstructionKindsBasedOnReassignment(hir);
});
log({
kind: 'hir',
name: 'RewriteInstructionKindsBasedOnReassignment',
@@ -337,11 +398,16 @@ function runWithEnvironment(
* if inferred memoization is enabled. This makes all later passes which
* transform reactive-scope labeled instructions no-ops.
*/
inferReactiveScopeVariables(hir);
env.tryRecord(() => {
inferReactiveScopeVariables(hir);
});
log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
}
const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);
let fbtOperands: Set<IdentifierId> = new Set();
env.tryRecord(() => {
fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);
});
log({
kind: 'hir',
name: 'MemoizeFbtAndMacroOperandsInSameScope',
@@ -349,11 +415,15 @@ function runWithEnvironment(
});
if (env.config.enableJsxOutlining) {
outlineJSX(hir);
env.tryRecord(() => {
outlineJSX(hir);
});
}
if (env.config.enableNameAnonymousFunctions) {
nameAnonymousFunctions(hir);
env.tryRecord(() => {
nameAnonymousFunctions(hir);
});
log({
kind: 'hir',
name: 'NameAnonymousFunctions',
@@ -362,39 +432,51 @@ function runWithEnvironment(
}
if (env.config.enableFunctionOutlining) {
outlineFunctions(hir, fbtOperands);
env.tryRecord(() => {
outlineFunctions(hir, fbtOperands);
});
log({kind: 'hir', name: 'OutlineFunctions', value: hir});
}
alignMethodCallScopes(hir);
env.tryRecord(() => {
alignMethodCallScopes(hir);
});
log({
kind: 'hir',
name: 'AlignMethodCallScopes',
value: hir,
});
alignObjectMethodScopes(hir);
env.tryRecord(() => {
alignObjectMethodScopes(hir);
});
log({
kind: 'hir',
name: 'AlignObjectMethodScopes',
value: hir,
});
pruneUnusedLabelsHIR(hir);
env.tryRecord(() => {
pruneUnusedLabelsHIR(hir);
});
log({
kind: 'hir',
name: 'PruneUnusedLabelsHIR',
value: hir,
});
alignReactiveScopesToBlockScopesHIR(hir);
env.tryRecord(() => {
alignReactiveScopesToBlockScopesHIR(hir);
});
log({
kind: 'hir',
name: 'AlignReactiveScopesToBlockScopesHIR',
value: hir,
});
mergeOverlappingReactiveScopesHIR(hir);
env.tryRecord(() => {
mergeOverlappingReactiveScopesHIR(hir);
});
log({
kind: 'hir',
name: 'MergeOverlappingReactiveScopesHIR',
@@ -402,7 +484,9 @@ function runWithEnvironment(
});
assertValidBlockNesting(hir);
buildReactiveScopeTerminalsHIR(hir);
env.tryRecord(() => {
buildReactiveScopeTerminalsHIR(hir);
});
log({
kind: 'hir',
name: 'BuildReactiveScopeTerminalsHIR',
@@ -411,14 +495,18 @@ function runWithEnvironment(
assertValidBlockNesting(hir);
flattenReactiveLoopsHIR(hir);
env.tryRecord(() => {
flattenReactiveLoopsHIR(hir);
});
log({
kind: 'hir',
name: 'FlattenReactiveLoopsHIR',
value: hir,
});
flattenScopesWithHooksOrUseHIR(hir);
env.tryRecord(() => {
flattenScopesWithHooksOrUseHIR(hir);
});
log({
kind: 'hir',
name: 'FlattenScopesWithHooksOrUseHIR',
@@ -426,7 +514,9 @@ function runWithEnvironment(
});
assertTerminalSuccessorsExist(hir);
assertTerminalPredsExist(hir);
propagateScopeDependenciesHIR(hir);
env.tryRecord(() => {
propagateScopeDependenciesHIR(hir);
});
log({
kind: 'hir',
name: 'PropagateScopeDependenciesHIR',
@@ -434,7 +524,9 @@ function runWithEnvironment(
});
if (env.config.inferEffectDependencies) {
inferEffectDependencies(hir);
env.tryRecord(() => {
inferEffectDependencies(hir);
});
log({
kind: 'hir',
name: 'InferEffectDependencies',
@@ -443,7 +535,9 @@ function runWithEnvironment(
}
if (env.config.inlineJsxTransform) {
inlineJsxTransform(hir, env.config.inlineJsxTransform);
env.tryRecord(() => {
inlineJsxTransform(hir, env.config.inlineJsxTransform!);
});
log({
kind: 'hir',
name: 'inlineJsxTransform',
@@ -451,7 +545,10 @@ function runWithEnvironment(
});
}
const reactiveFunction = buildReactiveFunction(hir);
let reactiveFunction!: ReactiveFunction;
env.tryRecord(() => {
reactiveFunction = buildReactiveFunction(hir);
});
log({
kind: 'reactive',
name: 'BuildReactiveFunction',
@@ -460,7 +557,9 @@ function runWithEnvironment(
assertWellFormedBreakTargets(reactiveFunction);
pruneUnusedLabels(reactiveFunction);
env.tryRecord(() => {
pruneUnusedLabels(reactiveFunction);
});
log({
kind: 'reactive',
name: 'PruneUnusedLabels',
@@ -468,35 +567,45 @@ function runWithEnvironment(
});
assertScopeInstructionsWithinScopes(reactiveFunction);
pruneNonEscapingScopes(reactiveFunction);
env.tryRecord(() => {
pruneNonEscapingScopes(reactiveFunction);
});
log({
kind: 'reactive',
name: 'PruneNonEscapingScopes',
value: reactiveFunction,
});
pruneNonReactiveDependencies(reactiveFunction);
env.tryRecord(() => {
pruneNonReactiveDependencies(reactiveFunction);
});
log({
kind: 'reactive',
name: 'PruneNonReactiveDependencies',
value: reactiveFunction,
});
pruneUnusedScopes(reactiveFunction);
env.tryRecord(() => {
pruneUnusedScopes(reactiveFunction);
});
log({
kind: 'reactive',
name: 'PruneUnusedScopes',
value: reactiveFunction,
});
mergeReactiveScopesThatInvalidateTogether(reactiveFunction);
env.tryRecord(() => {
mergeReactiveScopesThatInvalidateTogether(reactiveFunction);
});
log({
kind: 'reactive',
name: 'MergeReactiveScopesThatInvalidateTogether',
value: reactiveFunction,
});
pruneAlwaysInvalidatingScopes(reactiveFunction);
env.tryRecord(() => {
pruneAlwaysInvalidatingScopes(reactiveFunction);
});
log({
kind: 'reactive',
name: 'PruneAlwaysInvalidatingScopes',
@@ -504,7 +613,9 @@ function runWithEnvironment(
});
if (env.config.enableChangeDetectionForDebugging != null) {
pruneInitializationDependencies(reactiveFunction);
env.tryRecord(() => {
pruneInitializationDependencies(reactiveFunction);
});
log({
kind: 'reactive',
name: 'PruneInitializationDependencies',
@@ -512,49 +623,64 @@ function runWithEnvironment(
});
}
propagateEarlyReturns(reactiveFunction);
env.tryRecord(() => {
propagateEarlyReturns(reactiveFunction);
});
log({
kind: 'reactive',
name: 'PropagateEarlyReturns',
value: reactiveFunction,
});
pruneUnusedLValues(reactiveFunction);
env.tryRecord(() => {
pruneUnusedLValues(reactiveFunction);
});
log({
kind: 'reactive',
name: 'PruneUnusedLValues',
value: reactiveFunction,
});
promoteUsedTemporaries(reactiveFunction);
env.tryRecord(() => {
promoteUsedTemporaries(reactiveFunction);
});
log({
kind: 'reactive',
name: 'PromoteUsedTemporaries',
value: reactiveFunction,
});
extractScopeDeclarationsFromDestructuring(reactiveFunction);
env.tryRecord(() => {
extractScopeDeclarationsFromDestructuring(reactiveFunction);
});
log({
kind: 'reactive',
name: 'ExtractScopeDeclarationsFromDestructuring',
value: reactiveFunction,
});
stabilizeBlockIds(reactiveFunction);
env.tryRecord(() => {
stabilizeBlockIds(reactiveFunction);
});
log({
kind: 'reactive',
name: 'StabilizeBlockIds',
value: reactiveFunction,
});
const uniqueIdentifiers = renameVariables(reactiveFunction);
let uniqueIdentifiers: Set<string> = new Set();
env.tryRecord(() => {
uniqueIdentifiers = renameVariables(reactiveFunction);
});
log({
kind: 'reactive',
name: 'RenameVariables',
value: reactiveFunction,
});
pruneHoistedContexts(reactiveFunction);
env.tryRecord(() => {
pruneHoistedContexts(reactiveFunction);
});
log({
kind: 'reactive',
name: 'PruneHoistedContexts',
@@ -562,27 +688,38 @@ function runWithEnvironment(
});
if (env.config.validateMemoizedEffectDependencies) {
validateMemoizedEffectDependencies(reactiveFunction).unwrap();
env.tryRecord(() => {
validateMemoizedEffectDependencies(reactiveFunction).unwrap();
});
}
if (
env.config.enablePreserveExistingMemoizationGuarantees ||
env.config.validatePreserveExistingMemoizationGuarantees
) {
validatePreservedManualMemoization(reactiveFunction).unwrap();
env.tryRecord(() => {
validatePreservedManualMemoization(reactiveFunction).unwrap();
});
}
const ast = codegenFunction(reactiveFunction, {
const codegenResult = codegenFunction(reactiveFunction, {
uniqueIdentifiers,
fbtOperands,
}).unwrap();
});
if (codegenResult.isErr()) {
env.recordErrors(codegenResult.unwrapErr());
return Err(env.aggregateErrors());
}
const ast = codegenResult.unwrap();
log({kind: 'ast', name: 'Codegen', value: ast});
for (const outlined of ast.outlined) {
log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn});
}
if (env.config.validateSourceLocations) {
validateSourceLocations(func, ast).unwrap();
env.tryRecord(() => {
validateSourceLocations(func, ast).unwrap();
});
}
/**
@@ -594,7 +731,10 @@ function runWithEnvironment(
throw new Error('unexpected error');
}
return ast;
if (env.hasErrors()) {
return Err(env.aggregateErrors());
}
return Ok(ast);
}
export function compileFn(
@@ -608,7 +748,7 @@ export function compileFn(
logger: Logger | null,
filename: string | null,
code: string | null,
): CodegenFunction {
): Result<CodegenFunction, CompilerError> {
return run(
func,
config,

View File

@@ -728,19 +728,21 @@ function tryCompileFunction(
}
try {
return {
kind: 'compile',
compiledFn: compileFn(
fn,
programContext.opts.environment,
fnType,
outputMode,
programContext,
programContext.opts.logger,
programContext.filename,
programContext.code,
),
};
const result = compileFn(
fn,
programContext.opts.environment,
fnType,
outputMode,
programContext,
programContext.opts.logger,
programContext.filename,
programContext.code,
);
if (result.isOk()) {
return {kind: 'compile', compiledFn: result.unwrap()};
} else {
return {kind: 'error', error: result.unwrapErr()};
}
} catch (err) {
return {kind: 'error', error: err};
}
@@ -779,10 +781,16 @@ function retryCompileFunction(
programContext.code,
);
if (!retryResult.hasFireRewrite && !retryResult.hasInferredEffect) {
if (retryResult.isErr()) {
const err = retryResult.unwrapErr();
programContext.retryErrors.push({fn, error: err});
return null;
}
return retryResult;
const compiledFn = retryResult.unwrap();
if (!compiledFn.hasFireRewrite && !compiledFn.hasInferredEffect) {
return null;
}
return compiledFn;
} catch (err) {
// TODO: we might want to log error here, but this will also result in duplicate logging
if (err instanceof CompilerError) {

View File

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

View File

@@ -29,7 +29,7 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Found 2 errors:
Error: This value cannot be modified
@@ -43,6 +43,32 @@ error.hook-call-freezes-captured-memberexpr.ts:13:2
14 | return <Stringify x={x} cb={cb} />;
15 | }
16 |
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate `x` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
error.hook-call-freezes-captured-memberexpr.ts:9:25
7 | * After this custom hook call, it's no longer valid to mutate x.
8 | */
> 9 | const cb = useIdentity(() => {
| ^^^^^^^
> 10 | x.value++;
| ^^^^^^^^^^^^^^
> 11 | });
| ^^^^ This function may (indirectly) reassign or modify `x` after render
12 |
13 | x.value += count;
14 | return <Stringify x={x} cb={cb} />;
error.hook-call-freezes-captured-memberexpr.ts:10:4
8 | */
9 | const cb = useIdentity(() => {
> 10 | x.value++;
| ^ This modifies `x`
11 | });
12 |
13 | x.value += count;
```

View File

@@ -15,7 +15,7 @@ function component(a, b) {
## Error
```
Found 1 error:
Found 3 errors:
Error: useMemo() callbacks may not be async or generator functions
@@ -32,6 +32,37 @@ error.invalid-ReactUseMemo-async-callback.ts:2:24
5 | return x;
6 | }
7 |
Error: Found missing memoization dependencies
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
error.invalid-ReactUseMemo-async-callback.ts:3:10
1 | function component(a, b) {
2 | let x = React.useMemo(async () => {
> 3 | await a;
| ^ Missing dependency `a`
4 | }, []);
5 | return x;
6 | }
Inferred dependencies: `[a]`
Compilation Skipped: Existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `a`, but the source dependencies were []. Inferred dependency not present in source.
error.invalid-ReactUseMemo-async-callback.ts:2:24
1 | function component(a, b) {
> 2 | let x = React.useMemo(async () => {
| ^^^^^^^^^^^^^
> 3 | await a;
| ^^^^^^^^^^^^
> 4 | }, []);
| ^^^^ Could not preserve existing manual memoization
5 | return x;
6 | }
7 |
```

View File

@@ -22,7 +22,7 @@ function Component({item, cond}) {
## Error
```
Found 2 errors:
Found 3 errors:
Error: Calling setState from useMemo may trigger an infinite loop
@@ -49,6 +49,39 @@ error.invalid-conditional-setState-in-useMemo.ts:8:6
9 | }
10 | }, [cond, key, init]);
11 |
Error: Found missing/extra memoization dependencies
Missing dependencies can cause a value to update less often than it should, resulting in stale UI. Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often.
error.invalid-conditional-setState-in-useMemo.ts:7:18
5 | useMemo(() => {
6 | if (cond) {
> 7 | setPrevItem(item);
| ^^^^ Missing dependency `item`
8 | setState(0);
9 | }
10 | }, [cond, key, init]);
error.invalid-conditional-setState-in-useMemo.ts:10:12
8 | setState(0);
9 | }
> 10 | }, [cond, key, init]);
| ^^^ Unnecessary dependency `key`. Values declared outside of a component/hook should not be listed as dependencies as the component will not re-render if they change
11 |
12 | return state;
13 | }
error.invalid-conditional-setState-in-useMemo.ts:10:17
8 | setState(0);
9 | }
> 10 | }, [cond, key, init]);
| ^^^^ Unnecessary dependency `init`. Values declared outside of a component/hook should not be listed as dependencies as the component will not re-render if they change
11 |
12 | return state;
13 | }
Inferred dependencies: `[cond, item]`
```

View File

@@ -17,7 +17,7 @@ function Component() {
## Error
```
Found 3 errors:
Found 6 errors:
Error: Cannot call impure function during render
@@ -57,6 +57,45 @@ error.invalid-impure-functions-in-render.ts:6:15
7 | return <Foo date={date} now={now} rand={rand} />;
8 | }
9 |
Error: Cannot call impure function during render
`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:4:15
2 |
3 | function Component() {
> 4 | const date = Date.now();
| ^^^^^^^^ Cannot call impure function
5 | const now = performance.now();
6 | const rand = Math.random();
7 | return <Foo date={date} now={now} rand={rand} />;
Error: Cannot call impure function during render
`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:5:14
3 | function Component() {
4 | const date = Date.now();
> 5 | const now = performance.now();
| ^^^^^^^^^^^^^^^ Cannot call impure function
6 | const rand = Math.random();
7 | return <Foo date={date} now={now} rand={rand} />;
8 | }
Error: Cannot call impure function during render
`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:6:15
4 | const date = Date.now();
5 | const now = performance.now();
> 6 | const rand = Math.random();
| ^^^^^^^^^^^ Cannot call impure function
7 | return <Foo date={date} now={now} rand={rand} />;
8 | }
9 |
```

View File

@@ -16,7 +16,7 @@ function useInvalidMutation(options) {
## Error
```
Found 1 error:
Found 2 errors:
Error: This value cannot be modified
@@ -30,6 +30,27 @@ error.invalid-mutation-in-closure.ts:4:4
5 | }
6 | return test;
7 | }
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate `options` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
error.invalid-mutation-in-closure.ts:6:9
4 | options.foo = 'bar';
5 | }
> 6 | return test;
| ^^^^ This function may (indirectly) reassign or modify `options` after render
7 | }
8 |
error.invalid-mutation-in-closure.ts:4:4
2 | function test() {
3 | foo(options.foo); // error should not point on this line
> 4 | options.foo = 'bar';
| ^^^^^^^ This modifies `options`
5 | }
6 | return test;
7 | }
```

View File

@@ -15,7 +15,7 @@ function useFoo() {
## Error
```
Found 1 error:
Found 2 errors:
Error: Cannot reassign variable after render completes
@@ -29,6 +29,31 @@ error.invalid-reassign-local-in-hook-return-value.ts:4:4
5 | };
6 | }
7 |
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate `x` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
error.invalid-reassign-local-in-hook-return-value.ts:3:9
1 | function useFoo() {
2 | let x = 0;
> 3 | return value => {
| ^^^^^^^^^^
> 4 | x = value;
| ^^^^^^^^^^^^^^
> 5 | };
| ^^^^ This function may (indirectly) reassign or modify `x` after render
6 | }
7 |
error.invalid-reassign-local-in-hook-return-value.ts:4:4
2 | let x = 0;
3 | return value => {
> 4 | x = value;
| ^ This modifies `x`
5 | };
6 | }
7 |
```

View File

@@ -47,7 +47,7 @@ function Component() {
## Error
```
Found 1 error:
Found 2 errors:
Error: Cannot reassign variable after render completes
@@ -61,6 +61,32 @@ error.invalid-reassign-local-variable-in-effect.ts:7:4
8 | };
9 |
10 | const onMount = newValue => {
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate `local` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
error.invalid-reassign-local-variable-in-effect.ts:33:12
31 | };
32 |
> 33 | useEffect(() => {
| ^^^^^^^
> 34 | onMount();
| ^^^^^^^^^^^^^^
> 35 | }, [onMount]);
| ^^^^ This function may (indirectly) reassign or modify `local` after render
36 |
37 | return 'ok';
38 | }
error.invalid-reassign-local-variable-in-effect.ts:7:4
5 |
6 | const reassignLocal = newValue => {
> 7 | local = newValue;
| ^^^^^ This modifies `local`
8 | };
9 |
10 | const onMount = newValue => {
```

View File

@@ -48,7 +48,7 @@ function Component() {
## Error
```
Found 1 error:
Found 2 errors:
Error: Cannot reassign variable after render completes
@@ -62,6 +62,32 @@ error.invalid-reassign-local-variable-in-hook-argument.ts:8:4
9 | };
10 |
11 | const callback = newValue => {
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate `local` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
error.invalid-reassign-local-variable-in-hook-argument.ts:34:14
32 | };
33 |
> 34 | useIdentity(() => {
| ^^^^^^^
> 35 | callback();
| ^^^^^^^^^^^^^^^
> 36 | });
| ^^^^ This function may (indirectly) reassign or modify `local` after render
37 |
38 | return 'ok';
39 | }
error.invalid-reassign-local-variable-in-hook-argument.ts:8:4
6 |
7 | const reassignLocal = newValue => {
> 8 | local = newValue;
| ^^^^^ This modifies `local`
9 | };
10 |
11 | const callback = newValue => {
```

View File

@@ -41,7 +41,7 @@ function Component() {
## Error
```
Found 1 error:
Found 2 errors:
Error: Cannot reassign variable after render completes
@@ -55,6 +55,27 @@ error.invalid-reassign-local-variable-in-jsx-callback.ts:5:4
6 | };
7 |
8 | const onClick = newValue => {
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate `local` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
error.invalid-reassign-local-variable-in-jsx-callback.ts:31:26
29 | };
30 |
> 31 | return <button onClick={onClick}>Submit</button>;
| ^^^^^^^ This function may (indirectly) reassign or modify `local` after render
32 | }
33 |
error.invalid-reassign-local-variable-in-jsx-callback.ts:5:4
3 |
4 | const reassignLocal = newValue => {
> 5 | local = newValue;
| ^^^^^ This modifies `local`
6 | };
7 |
8 | const onClick = newValue => {
```

View File

@@ -26,7 +26,7 @@ function useKeyedState({key, init}) {
## Error
```
Found 1 error:
Found 3 errors:
Error: Calling setState from useMemo may trigger an infinite loop
@@ -40,6 +40,61 @@ error.invalid-setState-in-useMemo-indirect-useCallback.ts:13:4
14 | }, [key, init]);
15 |
16 | return state;
Error: Found missing memoization dependencies
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
error.invalid-setState-in-useMemo-indirect-useCallback.ts:9:13
7 | const fn = useCallback(() => {
8 | setPrevKey(key);
> 9 | setState(init);
| ^^^^ Missing dependency `init`
10 | });
11 |
12 | useMemo(() => {
error.invalid-setState-in-useMemo-indirect-useCallback.ts:8:15
6 |
7 | const fn = useCallback(() => {
> 8 | setPrevKey(key);
| ^^^ Missing dependency `key`
9 | setState(init);
10 | });
11 |
Error: Found missing/extra memoization dependencies
Missing dependencies can cause a value to update less often than it should, resulting in stale UI. Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often.
error.invalid-setState-in-useMemo-indirect-useCallback.ts:13:4
11 |
12 | useMemo(() => {
> 13 | fn();
| ^^ Missing dependency `fn`
14 | }, [key, init]);
15 |
16 | return state;
error.invalid-setState-in-useMemo-indirect-useCallback.ts:14:6
12 | useMemo(() => {
13 | fn();
> 14 | }, [key, init]);
| ^^^ Unnecessary dependency `key`
15 |
16 | return state;
17 | }
error.invalid-setState-in-useMemo-indirect-useCallback.ts:14:11
12 | useMemo(() => {
13 | fn();
> 14 | }, [key, init]);
| ^^^^ Unnecessary dependency `init`
15 |
16 | return state;
17 | }
Inferred dependencies: `[fn]`
```

View File

@@ -14,7 +14,19 @@ function Component() {
## Error
```
Found 1 error:
Found 2 errors:
Error: Invalid type configuration for module
Expected type for object property 'useHookNotTypedAsHook' from module 'ReactCompilerTest' to be a hook based on the property name.
error.invalid-type-provider-hook-name-not-typed-as-hook-namespace.ts:4:9
2 |
3 | function Component() {
> 4 | return ReactCompilerTest.useHookNotTypedAsHook();
| ^^^^^^^^^^^^^^^^^ Invalid type configuration for module
5 | }
6 |
Error: Invalid type configuration for module

View File

@@ -14,7 +14,19 @@ function Component() {
## Error
```
Found 1 error:
Found 2 errors:
Error: Invalid type configuration for module
Expected type for object property 'useHookNotTypedAsHook' from module 'ReactCompilerTest' to be a hook based on the property name.
error.invalid-type-provider-hook-name-not-typed-as-hook.ts:4:9
2 |
3 | function Component() {
> 4 | return useHookNotTypedAsHook();
| ^^^^^^^^^^^^^^^^^^^^^ Invalid type configuration for module
5 | }
6 |
Error: Invalid type configuration for module

View File

@@ -14,7 +14,19 @@ function Component() {
## Error
```
Found 1 error:
Found 2 errors:
Error: Invalid type configuration for module
Expected type for `import ... from 'useDefaultExportNotTypedAsHook'` to be a hook based on the module name.
error.invalid-type-provider-hooklike-module-default-not-hook.ts:4:15
2 |
3 | function Component() {
> 4 | return <div>{foo()}</div>;
| ^^^ Invalid type configuration for module
5 | }
6 |
Error: Invalid type configuration for module

View File

@@ -14,7 +14,19 @@ function Component() {
## Error
```
Found 1 error:
Found 2 errors:
Error: Invalid type configuration for module
Expected type for object property 'useHookNotTypedAsHook' from module 'ReactCompilerTest' to be a hook based on the property name.
error.invalid-type-provider-nonhook-name-typed-as-hook.ts:4:15
2 |
3 | function Component() {
> 4 | return <div>{notAhookTypedAsHook()}</div>;
| ^^^^^^^^^^^^^^^^^^^ Invalid type configuration for module
5 | }
6 |
Error: Invalid type configuration for module

View File

@@ -15,7 +15,7 @@ function component(a, b) {
## Error
```
Found 1 error:
Found 3 errors:
Error: useMemo() callbacks may not be async or generator functions
@@ -32,6 +32,37 @@ error.invalid-useMemo-async-callback.ts:2:18
5 | return x;
6 | }
7 |
Error: Found missing memoization dependencies
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
error.invalid-useMemo-async-callback.ts:3:10
1 | function component(a, b) {
2 | let x = useMemo(async () => {
> 3 | await a;
| ^ Missing dependency `a`
4 | }, []);
5 | return x;
6 | }
Inferred dependencies: `[a]`
Compilation Skipped: Existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `a`, but the source dependencies were []. Inferred dependency not present in source.
error.invalid-useMemo-async-callback.ts:2:18
1 | function component(a, b) {
> 2 | let x = useMemo(async () => {
| ^^^^^^^^^^^^^
> 3 | await a;
| ^^^^^^^^^^^^
> 4 | }, []);
| ^^^^ Could not preserve existing manual memoization
5 | return x;
6 | }
7 |
```

View File

@@ -13,7 +13,7 @@ function component(a, b) {
## Error
```
Found 1 error:
Found 3 errors:
Error: useMemo() callbacks may not accept parameters
@@ -26,6 +26,32 @@ error.invalid-useMemo-callback-args.ts:2:18
3 | return x;
4 | }
5 |
Error: Found missing memoization dependencies
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
error.invalid-useMemo-callback-args.ts:2:23
1 | function component(a, b) {
> 2 | let x = useMemo(c => a, []);
| ^ Missing dependency `a`
3 | return x;
4 | }
5 |
Inferred dependencies: `[a]`
Compilation Skipped: Existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `a`, but the source dependencies were []. Inferred dependency not present in source.
error.invalid-useMemo-callback-args.ts:2:18
1 | function component(a, b) {
> 2 | let x = useMemo(c => a, []);
| ^^^^^^ Could not preserve existing manual memoization
3 | return x;
4 | }
5 |
```

View File

@@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Found 2 errors:
Error: Cannot reassign variable after render completes
@@ -46,6 +46,28 @@ error.mutable-range-shared-inner-outer-function.ts:8:6
9 | b = [];
10 | } else {
11 | a = {};
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate `a` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
error.mutable-range-shared-inner-outer-function.ts:17:23
15 | b.push(false);
16 | };
> 17 | return <div onClick={f} />;
| ^ This function may (indirectly) reassign or modify `a` after render
18 | }
19 |
20 | export const FIXTURE_ENTRYPOINT = {
error.mutable-range-shared-inner-outer-function.ts:8:6
6 | const f = () => {
7 | if (cond) {
> 8 | a = {};
| ^ This modifies `a`
9 | b = [];
10 | } else {
11 | a = {};
```

View File

@@ -29,7 +29,7 @@ function useHook(parentRef) {
## Error
```
Found 1 error:
Found 2 errors:
Error: This value cannot be modified
@@ -43,6 +43,27 @@ error.todo-allow-assigning-to-inferred-ref-prop-in-callback.ts:15:8
16 | }
17 | }
18 | };
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate `parentRef` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
error.todo-allow-assigning-to-inferred-ref-prop-in-callback.ts:19:9
17 | }
18 | };
> 19 | return handler;
| ^^^^^^^ This function may (indirectly) reassign or modify `parentRef` after render
20 | }
21 |
error.todo-allow-assigning-to-inferred-ref-prop-in-callback.ts:15:8
13 | } else {
14 | // So this assignment fails since we don't know its a ref
> 15 | parentRef.current = instance;
| ^^^^^^^^^ This modifies `parentRef`
16 | }
17 | }
18 | };
```

View File

@@ -17,7 +17,7 @@ function Component() {
## Error
```
Found 1 error:
Found 2 errors:
Error: Cannot reassign variable after render completes
@@ -31,6 +31,27 @@ error.todo-function-expression-references-later-variable-declaration.ts:3:4
4 | };
5 | let onClick;
6 |
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate `onClick` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
error.todo-function-expression-references-later-variable-declaration.ts:7:23
5 | let onClick;
6 |
> 7 | return <div onClick={callback} />;
| ^^^^^^^^ This function may (indirectly) reassign or modify `onClick` after render
8 | }
9 |
error.todo-function-expression-references-later-variable-declaration.ts:3:4
1 | function Component() {
2 | let callback = () => {
> 3 | onClick = () => {};
| ^^^^^^^ This modifies `onClick`
4 | };
5 | let onClick;
6 |
```

View File

@@ -21,7 +21,7 @@ function Component({foo}) {
## Error
```
Found 1 error:
Found 2 errors:
Todo: Support destructuring of context variables
@@ -33,6 +33,19 @@ error.todo-reassign-const.ts:3:20
4 | let bar = foo.bar;
5 | return (
6 | <Stringify
Error: This value cannot be modified
Modifying component props or hook arguments is not allowed. Consider using a local variable instead.
error.todo-reassign-const.ts:8:8
6 | <Stringify
7 | handler={() => {
> 8 | foo = true;
| ^^^ `foo` cannot be modified
9 | }}
10 | />
11 | );
```

View File

@@ -51,7 +51,7 @@ function Component({x, y, z}) {
## Error
```
Found 4 errors:
Found 6 errors:
Error: Found missing/extra memoization dependencies
@@ -157,6 +157,48 @@ error.invalid-exhaustive-deps.ts:37:13
40 | }, []);
Inferred dependencies: `[ref]`
Compilation Skipped: Existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `x.y.z.a.b`, but the source dependencies were [x?.y.z.a?.b.z]. Inferred different dependency than source.
error.invalid-exhaustive-deps.ts:14:20
12 | // ok, not our job to type check nullability
13 | }, [x.y.z.a]);
> 14 | const c = useMemo(() => {
| ^^^^^^^
> 15 | return x?.y.z.a?.b;
| ^^^^^^^^^^^^^^^^^^^^^^^
> 16 | // error: too precise
| ^^^^^^^^^^^^^^^^^^^^^^^
> 17 | }, [x?.y.z.a?.b.z]);
| ^^^^ Could not preserve existing manual memoization
18 | const d = useMemo(() => {
19 | return x?.y?.[(console.log(y), z?.b)];
20 | // ok
Compilation Skipped: Existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `ref`, but the source dependencies were []. Inferred dependency not present in source.
error.invalid-exhaustive-deps.ts:35:21
33 | const ref2 = useRef(null);
34 | const ref = z ? ref1 : ref2;
> 35 | const cb = useMemo(() => {
| ^^^^^^^
> 36 | return () => {
| ^^^^^^^^^^^^^^^^^^
> 37 | return ref.current;
| ^^^^^^^^^^^^^^^^^^
> 38 | };
| ^^^^^^^^^^^^^^^^^^
> 39 | // error: ref is a stable type but reactive
| ^^^^^^^^^^^^^^^^^^
> 40 | }, []);
| ^^^^ Could not preserve existing manual memoization
41 | return <Stringify results={[a, b, c, d, e, f, cb]} />;
42 | }
43 |
```

View File

@@ -22,7 +22,7 @@ function useHook() {
## Error
```
Found 1 error:
Found 2 errors:
Error: Found missing memoization dependencies
@@ -38,6 +38,19 @@ error.invalid-missing-nonreactive-dep-unmemoized.ts:11:31
14 |
Inferred dependencies: `[object]`
Compilation Skipped: Existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `object`, but the source dependencies were []. Inferred dependency not present in source.
error.invalid-missing-nonreactive-dep-unmemoized.ts:11:24
9 | useIdentity();
10 | object.x = 0;
> 11 | const array = useMemo(() => [object], []);
| ^^^^^^^^^^^^^^ Could not preserve existing manual memoization
12 | return array;
13 | }
14 |
```

View File

@@ -58,6 +58,7 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","detail":{"options":{"category":"PreserveManualMemo","reason":"Existing memoization could not be preserved","description":"React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":337},"end":{"line":9,"column":52,"index":358},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}},"fnLoc":null}
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":255},"end":{"line":16,"column":1,"index":482},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"options":{"category":"PreserveManualMemo","reason":"Existing memoization could not be preserved","description":"React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":337},"end":{"line":9,"column":52,"index":358},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}}}
```

View File

@@ -20,7 +20,7 @@ function Component({foo}) {
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none"
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies @panicThreshold:"none"
import { useEffect, AUTODEPS } from "react";
import { print } from "shared-runtime";

View File

@@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
import { useEffect, AUTODEPS } from "react";
import { print } from "shared-runtime";
@@ -48,6 +48,8 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","detail":{"options":{"category":"Immutability","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()","details":[{"kind":"error","loc":{"start":{"line":10,"column":2,"index":365},"end":{"line":10,"column":5,"index":368},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"message":"value cannot be modified"}]}},"fnLoc":null}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":314},"end":{"line":9,"column":49,"index":361},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":336},"end":{"line":9,"column":27,"index":339},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]}
{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":149},"end":{"line":12,"column":1,"index":404},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"options":{"category":"Immutability","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()","details":[{"kind":"error","loc":{"start":{"line":10,"column":2,"index":365},"end":{"line":10,"column":5,"index":368},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":314},"end":{"line":9,"column":49,"index":361},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":336},"end":{"line":9,"column":27,"index":339},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":149},"end":{"line":12,"column":1,"index":404},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}

View File

@@ -24,7 +24,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
import { useEffect, useRef, AUTODEPS } from "react";
import { print } from "shared-runtime";
@@ -47,6 +47,8 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","detail":{"options":{"category":"Refs","reason":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":289},"end":{"line":9,"column":16,"index":303},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}},"fnLoc":null}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":237},"end":{"line":8,"column":50,"index":285},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":259},"end":{"line":8,"column":30,"index":265},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]}
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","reason":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":289},"end":{"line":9,"column":16,"index":303},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":237},"end":{"line":8,"column":50,"index":285},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":259},"end":{"line":8,"column":30,"index":265},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":158},"end":{"line":11,"column":1,"index":331},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}

View File

@@ -24,7 +24,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly
import { useEffect, AUTODEPS } from "react";
function Component(t0) {
@@ -47,7 +47,11 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","detail":{"options":{"category":"Immutability","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":214},"end":{"line":9,"column":5,"index":217},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"value cannot be modified"}]}},"fnLoc":null}
{"kind":"CompileError","detail":{"options":{"category":"Immutability","reason":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate `arr` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead","details":[{"kind":"error","loc":{"start":{"line":6,"column":12,"index":169},"end":{"line":8,"column":3,"index":199},"filename":"mutate-after-useeffect.ts"},"message":"This function may (indirectly) reassign or modify `arr` after render"},{"kind":"error","loc":{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"This modifies `arr`"}]}},"fnLoc":null}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":159},"end":{"line":8,"column":14,"index":210},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":190},"end":{"line":7,"column":16,"index":193},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]}
{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":11,"column":1,"index":242},"filename":"mutate-after-useeffect.ts"},"detail":{"options":{"category":"Immutability","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":214},"end":{"line":9,"column":5,"index":217},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}}
{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":11,"column":1,"index":242},"filename":"mutate-after-useeffect.ts"},"detail":{"options":{"category":"Immutability","reason":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate `arr` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead","details":[{"kind":"error","loc":{"start":{"line":6,"column":12,"index":169},"end":{"line":8,"column":3,"index":199},"filename":"mutate-after-useeffect.ts"},"message":"This function may (indirectly) reassign or modify `arr` after render"},{"kind":"error","loc":{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"This modifies `arr`"}]}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":159},"end":{"line":8,"column":14,"index":210},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":181},"end":{"line":7,"column":7,"index":184},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":190},"end":{"line":7,"column":16,"index":193},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":111},"end":{"line":11,"column":1,"index":242},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
```

View File

@@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none"
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies @panicThreshold:"none"
import { print } from "shared-runtime";
import useEffectWrapper from "useEffectWrapper";
import { AUTODEPS } from "react";

View File

@@ -29,7 +29,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none"
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies @panicThreshold:"none"
import { print } from "shared-runtime";
import useEffectWrapper from "useEffectWrapper";
import { AUTODEPS } from "react";

View File

@@ -17,7 +17,7 @@ function Component() {
## Error
```
Found 3 errors:
Found 6 errors:
Error: Cannot call impure function during render
@@ -57,6 +57,45 @@ error.invalid-impure-functions-in-render.ts:6:15
7 | return <Foo date={date} now={now} rand={rand} />;
8 | }
9 |
Error: Cannot call impure function during render
`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:4:15
2 |
3 | function Component() {
> 4 | const date = Date.now();
| ^^^^^^^^ Cannot call impure function
5 | const now = performance.now();
6 | const rand = Math.random();
7 | return <Foo date={date} now={now} rand={rand} />;
Error: Cannot call impure function during render
`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:5:14
3 | function Component() {
4 | const date = Date.now();
> 5 | const now = performance.now();
| ^^^^^^^^^^^^^^^ Cannot call impure function
6 | const rand = Math.random();
7 | return <Foo date={date} now={now} rand={rand} />;
8 | }
Error: Cannot call impure function during render
`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).
error.invalid-impure-functions-in-render.ts:6:15
4 | const date = Date.now();
5 | const now = performance.now();
> 6 | const rand = Math.random();
| ^^^^^^^^^^^ Cannot call impure function
7 | return <Foo date={date} now={now} rand={rand} />;
8 | }
9 |
```

View File

@@ -42,7 +42,7 @@ function Component() {
## Error
```
Found 1 error:
Found 2 errors:
Error: Cannot reassign variable after render completes
@@ -56,6 +56,27 @@ error.invalid-reassign-local-variable-in-jsx-callback.ts:6:4
7 | };
8 |
9 | const onClick = newValue => {
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate `local` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
error.invalid-reassign-local-variable-in-jsx-callback.ts:32:26
30 | };
31 |
> 32 | return <button onClick={onClick}>Submit</button>;
| ^^^^^^^ This function may (indirectly) reassign or modify `local` after render
33 | }
34 |
error.invalid-reassign-local-variable-in-jsx-callback.ts:6:4
4 |
5 | const reassignLocal = newValue => {
> 6 | local = newValue;
| ^^^^^ This modifies `local`
7 | };
8 |
9 | const onClick = newValue => {
```

View File

@@ -31,7 +31,7 @@ function Component({content, refetch}) {
## Error
```
Found 1 error:
Found 2 errors:
Error: Cannot access variable before it is declared
@@ -52,6 +52,18 @@ Error: Cannot access variable before it is declared
20 |
21 | return <Foo data={data} onSubmit={onSubmit} />;
22 | }
Error: Found missing memoization dependencies
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
9 | // TDZ violation!
10 | const onRefetch = useCallback(() => {
> 11 | refetch(data);
| ^^^^ Missing dependency `data`
12 | }, [refetch]);
13 |
14 | // The context variable gets frozen here since it's passed to a hook
```

View File

@@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import { useEffect, AUTODEPS } from "react";
import { print } from "shared-runtime";
@@ -48,6 +48,8 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","detail":{"options":{"category":"Immutability","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()","details":[{"kind":"error","loc":{"start":{"line":10,"column":2,"index":397},"end":{"line":10,"column":5,"index":400},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"message":"value cannot be modified"}]}},"fnLoc":null}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":346},"end":{"line":9,"column":49,"index":393},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":368},"end":{"line":9,"column":27,"index":371},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]}
{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":181},"end":{"line":12,"column":1,"index":436},"filename":"mutate-after-useeffect-optional-chain.ts"},"detail":{"options":{"category":"Immutability","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()","details":[{"kind":"error","loc":{"start":{"line":10,"column":2,"index":397},"end":{"line":10,"column":5,"index":400},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":9,"column":2,"index":346},"end":{"line":9,"column":49,"index":393},"filename":"mutate-after-useeffect-optional-chain.ts"},"decorations":[{"start":{"line":9,"column":24,"index":368},"end":{"line":9,"column":27,"index":371},"filename":"mutate-after-useeffect-optional-chain.ts","identifierName":"arr"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":181},"end":{"line":12,"column":1,"index":436},"filename":"mutate-after-useeffect-optional-chain.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}

View File

@@ -24,7 +24,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import { useEffect, useRef, AUTODEPS } from "react";
import { print } from "shared-runtime";
@@ -47,6 +47,8 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","detail":{"options":{"category":"Refs","reason":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":321},"end":{"line":9,"column":16,"index":335},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}},"fnLoc":null}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":269},"end":{"line":8,"column":50,"index":317},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":291},"end":{"line":8,"column":30,"index":297},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]}
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"detail":{"options":{"category":"Refs","reason":"Cannot access refs during render","description":"React refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the `current` property) during render can cause your component not to update as expected (https://react.dev/reference/react/useRef)","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":321},"end":{"line":9,"column":16,"index":335},"filename":"mutate-after-useeffect-ref-access.ts"},"message":"Cannot update ref during render"}]}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":8,"column":2,"index":269},"end":{"line":8,"column":50,"index":317},"filename":"mutate-after-useeffect-ref-access.ts"},"decorations":[{"start":{"line":8,"column":24,"index":291},"end":{"line":8,"column":30,"index":297},"filename":"mutate-after-useeffect-ref-access.ts","identifierName":"arrRef"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":6,"column":0,"index":190},"end":{"line":11,"column":1,"index":363},"filename":"mutate-after-useeffect-ref-access.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}

View File

@@ -24,7 +24,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies @panicThreshold:"none" @loggerTestOnly @enableNewMutationAliasingModel
import { useEffect, AUTODEPS } from "react";
function Component(t0) {
@@ -47,7 +47,11 @@ export const FIXTURE_ENTRYPOINT = {
## Logs
```
{"kind":"CompileError","detail":{"options":{"category":"Immutability","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":246},"end":{"line":9,"column":5,"index":249},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"value cannot be modified"}]}},"fnLoc":null}
{"kind":"CompileError","detail":{"options":{"category":"Immutability","reason":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate `arr` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead","details":[{"kind":"error","loc":{"start":{"line":6,"column":12,"index":201},"end":{"line":8,"column":3,"index":231},"filename":"mutate-after-useeffect.ts"},"message":"This function may (indirectly) reassign or modify `arr` after render"},{"kind":"error","loc":{"start":{"line":7,"column":4,"index":213},"end":{"line":7,"column":7,"index":216},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"This modifies `arr`"}]}},"fnLoc":null}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":191},"end":{"line":8,"column":14,"index":242},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":213},"end":{"line":7,"column":7,"index":216},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":213},"end":{"line":7,"column":7,"index":216},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":222},"end":{"line":7,"column":16,"index":225},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]}
{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":143},"end":{"line":11,"column":1,"index":274},"filename":"mutate-after-useeffect.ts"},"detail":{"options":{"category":"Immutability","reason":"This value cannot be modified","description":"Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()","details":[{"kind":"error","loc":{"start":{"line":9,"column":2,"index":246},"end":{"line":9,"column":5,"index":249},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"value cannot be modified"}]}}}
{"kind":"CompileError","fnLoc":{"start":{"line":4,"column":0,"index":143},"end":{"line":11,"column":1,"index":274},"filename":"mutate-after-useeffect.ts"},"detail":{"options":{"category":"Immutability","reason":"Cannot modify local variables after render completes","description":"This argument is a function which may reassign or mutate `arr` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead","details":[{"kind":"error","loc":{"start":{"line":6,"column":12,"index":201},"end":{"line":8,"column":3,"index":231},"filename":"mutate-after-useeffect.ts"},"message":"This function may (indirectly) reassign or modify `arr` after render"},{"kind":"error","loc":{"start":{"line":7,"column":4,"index":213},"end":{"line":7,"column":7,"index":216},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},"message":"This modifies `arr`"}]}}}
{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":6,"column":2,"index":191},"end":{"line":8,"column":14,"index":242},"filename":"mutate-after-useeffect.ts"},"decorations":[{"start":{"line":7,"column":4,"index":213},"end":{"line":7,"column":7,"index":216},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":4,"index":213},"end":{"line":7,"column":7,"index":216},"filename":"mutate-after-useeffect.ts","identifierName":"arr"},{"start":{"line":7,"column":13,"index":222},"end":{"line":7,"column":16,"index":225},"filename":"mutate-after-useeffect.ts","identifierName":"foo"}]}
{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":143},"end":{"line":11,"column":1,"index":274},"filename":"mutate-after-useeffect.ts"},"fnName":"Component","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0}
```

View File

@@ -30,7 +30,7 @@ function useFoo(input1) {
## Error
```
Found 1 error:
Found 2 errors:
Error: Found missing memoization dependencies
@@ -46,6 +46,23 @@ error.useMemo-unrelated-mutation-in-depslist.ts:18:14
21 | }
Inferred dependencies: `[x, y]`
Compilation Skipped: Existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `input1`, but the source dependencies were [y]. Inferred different dependency than source.
error.useMemo-unrelated-mutation-in-depslist.ts:16:27
14 | const x = {};
15 | const y = [input1];
> 16 | const memoized = useMemo(() => {
| ^^^^^^^
> 17 | return [y];
| ^^^^^^^^^^^^^^^
> 18 | }, [(mutate(x), y)]);
| ^^^^ Could not preserve existing manual memoization
19 |
20 | return [x, memoized];
21 | }
```

View File

@@ -16,29 +16,24 @@ function Component(props) {
## Error
```
Found 2 errors:
Found 1 error:
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
Invariant: Unexpected empty block with `goto` terminal
error.invalid-hook-for.ts:4:9
2 | let i = 0;
3 | for (let x = 0; useHook(x) < 10; useHook(i), x++) {
> 4 | i += useHook(x);
| ^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
5 | }
6 | return i;
7 | }
Block bb5 is empty.
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-hook-for.ts:3:35
error.invalid-hook-for.ts:3:2
1 | function Component(props) {
2 | let i = 0;
> 3 | for (let x = 0; useHook(x) < 10; useHook(i), x++) {
| ^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
4 | i += useHook(x);
5 | }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 4 | i += useHook(x);
| ^^^^^^^^^^^^^^^^^^^^
> 5 | }
| ^^^^ Unexpected empty block with `goto` terminal
6 | return i;
7 | }
8 |
```

View File

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

View File

@@ -26,7 +26,7 @@ function Component({props, bar}) {
## Code
```javascript
import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none"
import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none"
import { useRef } from "react";
function Component(t0) {

View File

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

View File

@@ -20,7 +20,7 @@ function Component({prop1}) {
## Code
```javascript
import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none"
import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none"
import { fire } from "react";
function Component(t0) {

View File

@@ -25,7 +25,7 @@ component Component(prop1, ref) {
## Code
```javascript
import { useFire } from "react/compiler-runtime";
import { c as _c, useFire } from "react/compiler-runtime";
import { fire } from "react";
import { print } from "shared-runtime";

View File

@@ -29,7 +29,7 @@ function useFoo({cond}) {
## Code
```javascript
// @inferEffectDependencies @panicThreshold:"none"
import { c as _c } from "react/compiler-runtime"; // @inferEffectDependencies @panicThreshold:"none"
import { useRef, AUTODEPS } from "react";
import { useSpecialEffect } from "shared-runtime";

View File

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

View File

@@ -29,15 +29,15 @@ function Component(props) {
```
Found 1 error:
Error: Cannot compile `fire`
Invariant: [InferMutationAliasingEffects] Expected value kind to be initialized
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.
<unknown> foo$42:TFunction<BuiltInFunction>(): :TPrimitive.
error.invalid-mix-fire-and-no-fire.ts:11:6
9 | function nested() {
10 | fire(foo(props));
> 11 | foo(props);
| ^^^ Cannot compile `fire`
| ^^^ this is uninitialized
12 | }
13 |
14 | nested();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,6 +25,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useRef } from "react";
const useControllableState = (options) => {};