From cb44b4f39569f86cfe67bd47c232b9410f8bf68a Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Fri, 20 Feb 2026 11:01:43 -0800 Subject: [PATCH] [compiler] Phase 2+7: Wrap pipeline passes in tryRecord for fault tolerance - Change runWithEnvironment/run/compileFn to return Result - 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. --- compiler/fault-tolerance-overview.md | 29 +- .../src/Entrypoint/Pipeline.ts | 282 +++++++++++++----- .../src/Entrypoint/Program.ts | 38 ++- ...rror.dont-hoist-inline-reference.expect.md | 8 +- ...call-freezes-captured-memberexpr.expect.md | 28 +- ...alid-ReactUseMemo-async-callback.expect.md | 33 +- ...-conditional-setState-in-useMemo.expect.md | 35 ++- ...valid-impure-functions-in-render.expect.md | 41 ++- ...rror.invalid-mutation-in-closure.expect.md | 23 +- ...ssign-local-in-hook-return-value.expect.md | 27 +- ...eassign-local-variable-in-effect.expect.md | 28 +- ...-local-variable-in-hook-argument.expect.md | 28 +- ...n-local-variable-in-jsx-callback.expect.md | 23 +- ...-in-useMemo-indirect-useCallback.expect.md | 57 +++- ...name-not-typed-as-hook-namespace.expect.md | 14 +- ...ider-hook-name-not-typed-as-hook.expect.md | 14 +- ...hooklike-module-default-not-hook.expect.md | 14 +- ...vider-nonhook-name-typed-as-hook.expect.md | 14 +- ...r.invalid-useMemo-async-callback.expect.md | 33 +- ...or.invalid-useMemo-callback-args.expect.md | 28 +- ...ange-shared-inner-outer-function.expect.md | 24 +- ...to-inferred-ref-prop-in-callback.expect.md | 23 +- ...ences-later-variable-declaration.expect.md | 23 +- .../error.todo-reassign-const.expect.md | 15 +- .../error.invalid-exhaustive-deps.expect.md | 44 ++- ...ssing-nonreactive-dep-unmemoized.expect.md | 15 +- .../dynamic-gating-bailout-nopanic.expect.md | 1 + ...-after-useeffect-granular-access.expect.md | 2 +- ...e-after-useeffect-optional-chain.expect.md | 4 +- ...utate-after-useeffect-ref-access.expect.md | 4 +- .../mutate-after-useeffect.expect.md | 6 +- ...eps-with-rule-violation--compile.expect.md | 2 +- ...olation-use-memo-opt-in--compile.expect.md | 2 +- ...valid-impure-functions-in-render.expect.md | 41 ++- ...n-local-variable-in-jsx-callback.expect.md | 23 +- ...rozen-hoisted-storecontext-const.expect.md | 14 +- ...e-after-useeffect-optional-chain.expect.md | 4 +- ...utate-after-useeffect-ref-access.expect.md | 4 +- .../mutate-after-useeffect.expect.md | 6 +- ...o-unrelated-mutation-in-depslist.expect.md | 19 +- .../error.invalid-hook-for.expect.md | 27 +- .../bailout-capitalized-fn-call.expect.md | 2 +- .../bailout-eslint-suppressions.expect.md | 2 +- .../bailout-validate-preserve-memo.expect.md | 2 +- .../bailout-validate-prop-write.expect.md | 2 +- ...lout-validate-ref-current-access.expect.md | 2 +- .../infer-deps-on-retry.expect.md | 2 +- ...ailout-validate-conditional-hook.expect.md | 6 +- ...ror.invalid-mix-fire-and-no-fire.expect.md | 6 +- .../error.invalid-multiple-args.expect.md | 6 +- .../error.invalid-not-call.expect.md | 6 +- .../error.invalid-spread.expect.md | 6 +- .../error.todo-method.expect.md | 6 +- ...ro-dont-add-hook-guards-on-retry.expect.md | 7 +- ...multiple-with-eslint-suppression.expect.md | 1 + 55 files changed, 977 insertions(+), 179 deletions(-) diff --git a/compiler/fault-tolerance-overview.md b/compiler/fault-tolerance-overview.md index 57d68d1a78..1485bb6bd5 100644 --- a/compiler/fault-tolerance-overview.md +++ b/compiler/fault-tolerance-overview.md @@ -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` - 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` - 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. + diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index 30d6652271..7f4cbca7ce 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -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 { 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 { 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 = 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 = 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 { return run( func, config, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 80ce909f35..d93c814913 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -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) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.dont-hoist-inline-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.dont-hoist-inline-reference.expect.md index 00a68405f8..bbb66dd6b5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.dont-hoist-inline-reference.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.dont-hoist-inline-reference.expect.md @@ -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. + 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 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.expect.md index c57d55e29a..a7f36aac18 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.expect.md @@ -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 ; 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 ; + +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; ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ReactUseMemo-async-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ReactUseMemo-async-callback.expect.md index 4aac70a933..be7732333e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ReactUseMemo-async-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ReactUseMemo-async-callback.expect.md @@ -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 | ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-setState-in-useMemo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-setState-in-useMemo.expect.md index e4a9424962..ee2c56fe22 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-setState-in-useMemo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-setState-in-useMemo.expect.md @@ -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]` ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md index 255da7389b..e438782d60 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md @@ -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 ; 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 ; + +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 ; + 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 ; + 8 | } + 9 | ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-in-closure.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-in-closure.expect.md index 62792df4d8..bd0d587f55 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-in-closure.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-in-closure.expect.md @@ -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 | } ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md index 6379515a05..8eb3f6ce6e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md @@ -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 | ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md index 368b312022..1f87cf411b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md @@ -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 => { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md index 8c7973377d..61b8ef46c5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md @@ -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 => { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index 3ecbcc97c3..feb3449bef 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -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 ; + | ^^^^^^^ 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 => { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo-indirect-useCallback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo-indirect-useCallback.expect.md index e284a9367f..b245c5324f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo-indirect-useCallback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo-indirect-useCallback.expect.md @@ -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]` ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-hook-name-not-typed-as-hook-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-hook-name-not-typed-as-hook-namespace.expect.md index 3359db541a..4992a1a176 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-hook-name-not-typed-as-hook-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-hook-name-not-typed-as-hook-namespace.expect.md @@ -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 diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-hook-name-not-typed-as-hook.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-hook-name-not-typed-as-hook.expect.md index 136d19d122..b34b5bf967 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-hook-name-not-typed-as-hook.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-hook-name-not-typed-as-hook.expect.md @@ -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 diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-hooklike-module-default-not-hook.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-hooklike-module-default-not-hook.expect.md index 4e9cd18474..916bbda2e3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-hooklike-module-default-not-hook.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-hooklike-module-default-not-hook.expect.md @@ -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
{foo()}
; + | ^^^ Invalid type configuration for module + 5 | } + 6 | Error: Invalid type configuration for module diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-nonhook-name-typed-as-hook.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-nonhook-name-typed-as-hook.expect.md index 182f9a64a1..a4dea70e74 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-nonhook-name-typed-as-hook.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-type-provider-nonhook-name-typed-as-hook.expect.md @@ -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
{notAhookTypedAsHook()}
; + | ^^^^^^^^^^^^^^^^^^^ Invalid type configuration for module + 5 | } + 6 | Error: Invalid type configuration for module diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-async-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-async-callback.expect.md index 7146a57869..922119f1f1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-async-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-async-callback.expect.md @@ -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 | ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-callback-args.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-callback-args.expect.md index 933315073f..9bbf4ac8cd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-callback-args.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-callback-args.expect.md @@ -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 | ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md index 337b9dd30c..b0a1a67123 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md @@ -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
; + | ^ 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 = {}; ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-allow-assigning-to-inferred-ref-prop-in-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-allow-assigning-to-inferred-ref-prop-in-callback.expect.md index ec187f6bc2..757e038c67 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-allow-assigning-to-inferred-ref-prop-in-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-allow-assigning-to-inferred-ref-prop-in-callback.expect.md @@ -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 | }; ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md index a88d43b352..0cd493836b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md @@ -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
; + | ^^^^^^^^ 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 | ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-reassign-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-reassign-const.expect.md index 4b8ee0e4ed..9c1dd91bb3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-reassign-const.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-reassign-const.expect.md @@ -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 | { +> 8 | foo = true; + | ^^^ `foo` cannot be modified + 9 | }} + 10 | /> + 11 | ); ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-exhaustive-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-exhaustive-deps.expect.md index 2c864f56af..567d59e454 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-exhaustive-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-exhaustive-deps.expect.md @@ -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 ; + 42 | } + 43 | ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-missing-nonreactive-dep-unmemoized.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-missing-nonreactive-dep-unmemoized.expect.md index bb991d17da..626240b1ae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-missing-nonreactive-dep-unmemoized.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-missing-nonreactive-dep-unmemoized.expect.md @@ -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 | ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md index 535f98e574..2725079626 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/dynamic-gating-bailout-nopanic.expect.md @@ -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"}]}}} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md index b8cf1bc40a..b3c1cdab7a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md @@ -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"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md index 4606d49f37..635b7c9993 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-optional-chain.expect.md @@ -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} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md index ea5a887b8b..2e68251bbd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md @@ -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} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md index 71cd9fb620..27a2954a49 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md @@ -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} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation--compile.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation--compile.expect.md index 47de4a1d19..40ac8116fc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation--compile.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation--compile.expect.md @@ -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"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation-use-memo-opt-in--compile.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation-use-memo-opt-in--compile.expect.md index 610ad34890..89718f6042 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation-use-memo-opt-in--compile.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/retry-lint-comparison/infer-effect-deps-with-rule-violation-use-memo-opt-in--compile.expect.md @@ -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"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md index 1241971d82..45e36ea6a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-impure-functions-in-render.expect.md @@ -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 ; 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 ; + +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 ; + 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 ; + 8 | } + 9 | ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index babb4e8969..5cd2cf7b97 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -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 ; + | ^^^^^^^ 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 => { ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md index d78e4becec..d50943f677 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md @@ -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 ; 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 ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md index edc266b9f3..cd874a0214 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-optional-chain.expect.md @@ -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} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md index acf9c28cab..d4bfabf552 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect-ref-access.expect.md @@ -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} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md index 93e7f1e060..6988c094f5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/mutate-after-useeffect.expect.md @@ -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} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-unrelated-mutation-in-depslist.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-unrelated-mutation-in-depslist.expect.md index fe0bf6c22f..c311f86212 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-unrelated-mutation-in-depslist.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-unrelated-mutation-in-depslist.expect.md @@ -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 | } ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-for.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-for.expect.md index 84db054148..cd05dccfe6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-for.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-for.expect.md @@ -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 | ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md index d1fd19c005..5f02716d95 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md @@ -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; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md index 99774bdd3e..742b4ce8ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md @@ -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) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md index 0a39f9b32a..8a223a580b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md @@ -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"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md index 0f1d745537..855746c146 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md @@ -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) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md index b55526e921..9bc86bf358 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md @@ -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"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md index 3caeef386f..148a5f4f55 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md @@ -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"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md index 0099c65c92..cdd56fe7af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md @@ -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 ; } -function _temp(props_0) { +function _temp2(props_0) { console.log(props_0); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-mix-fire-and-no-fire.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-mix-fire-and-no-fire.expect.md index 1eb6bf66e9..e4b0057ac6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-mix-fire-and-no-fire.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-mix-fire-and-no-fire.expect.md @@ -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. + foo$42:TFunction(): :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(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-multiple-args.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-multiple-args.expect.md index c519d43fb4..03172c74cc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-multiple-args.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-multiple-args.expect.md @@ -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. + $43:TFunction(): :TFunction(): :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; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-not-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-not-call.expect.md index 40c4bc5394..17a27c4e63 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-not-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-not-call.expect.md @@ -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. + $32:TFunction(): :TFunction(): :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; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-spread.expect.md index dcd302bbe1..087b0b8e72 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-spread.expect.md @@ -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. + $32:TFunction(): :TFunction(): :TPoly. error.invalid-spread.ts:9:4 7 | }; 8 | useEffect(() => { > 9 | fire(...foo); - | ^^^^^^^^^^^^ Cannot compile `fire` + | ^^^^ this is uninitialized 10 | }); 11 | 12 | return null; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.todo-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.todo-method.expect.md index 67410297f3..d7c56ffb33 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.todo-method.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.todo-method.expect.md @@ -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. + $34:TFunction(): :TFunction(): :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; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md index 2ad6dea688..2887c071ef 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md @@ -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
hello world
; } -function _temp(props_0) { +function _temp2(props_0) { console.log(props_0); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-forget-multiple-with-eslint-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-forget-multiple-with-eslint-suppression.expect.md index 22386c5205..b46d71fdf4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-forget-multiple-with-eslint-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-forget-multiple-with-eslint-suppression.expect.md @@ -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) => {};