From 6b113b7bd11f7fa432a0a8f9375013d30c884494 Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Thu, 26 Feb 2026 12:40:55 -0800 Subject: [PATCH 1/2] [compiler] Deduplicate errors between ValidateExhaustiveDependencies and ValidatePreservedManualMemoization (#35917) With the recent changes to make the compiler fault tolerant and always continue through all passes, we can now sometimes report duplicative errors. Specifically, when `ValidateExhaustiveDependencies` finds incorrect deps for a useMemo/useCallback call, `ValidatePreservedManualMemoization` will generally also error for the same block, producing duplicate errors. The exhaustive deps error is strictly more informative, so if we've already reported the earlier error we don't need the later one. This adds a `hasInvalidDeps` flag to StartMemoize that is set when ValidateExhaustiveDependencies produces a diagnostic. ValidatePreservedManualMemoization then skips validation for memo blocks with this flag set. --- .../src/HIR/HIR.ts | 1 + .../ValidateExhaustiveDependencies.ts | 1 + .../ValidatePreservedManualMemoization.ts | 26 ++++++++--- ...alid-ReactUseMemo-async-callback.expect.md | 18 +------- ...r.invalid-useMemo-async-callback.expect.md | 18 +------- ...or.invalid-useMemo-callback-args.expect.md | 14 +----- .../error.invalid-exhaustive-deps.expect.md | 44 +------------------ ...ssing-nonreactive-dep-unmemoized.expect.md | 15 +------ ...o-unrelated-mutation-in-depslist.expect.md | 19 +------- 9 files changed, 27 insertions(+), 129 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index bf2af5f6834c..ec274b45add2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -826,6 +826,7 @@ export type StartMemoize = { * emitting diagnostics with a suggested replacement */ depsLoc: SourceLocation | null; + hasInvalidDeps?: true; loc: SourceLocation; }; export type FinishMemoize = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts index e8a64a624aad..c418b7770387 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts @@ -143,6 +143,7 @@ export function validateExhaustiveDependencies(fn: HIRFunction): void { ); if (diagnostic != null) { fn.env.recordError(diagnostic); + startMemo.hasInvalidDeps = true; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts index 99085872f48f..d39aa307dfb3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts @@ -486,16 +486,25 @@ class Visitor extends ReactiveFunctionVisitor { ids.add(value.place.identifier); } if (value.kind === 'StartMemoize') { - let depsFromSource: Array | null = null; - if (value.deps != null) { - depsFromSource = value.deps; - } CompilerError.invariant(state.manualMemoState == null, { reason: 'Unexpected nested StartMemoize instructions', description: `Bad manual memoization ids: ${state.manualMemoState?.manualMemoId}, ${value.manualMemoId}`, loc: value.loc, }); + if (value.hasInvalidDeps === true) { + /* + * ValidateExhaustiveDependencies already reported an error for this + * memo block, skip validation to avoid duplicate errors + */ + return; + } + + let depsFromSource: Array | null = null; + if (value.deps != null) { + depsFromSource = value.deps; + } + state.manualMemoState = { loc: instruction.loc, decls: new Set(), @@ -547,12 +556,15 @@ class Visitor extends ReactiveFunctionVisitor { } } if (value.kind === 'FinishMemoize') { + if (state.manualMemoState == null) { + // StartMemoize had invalid deps, skip validation + return; + } CompilerError.invariant( - state.manualMemoState != null && - state.manualMemoState.manualMemoId === value.manualMemoId, + state.manualMemoState.manualMemoId === value.manualMemoId, { reason: 'Unexpected mismatch between StartMemoize and FinishMemoize', - description: `Encountered StartMemoize id=${state.manualMemoState?.manualMemoId} followed by FinishMemoize id=${value.manualMemoId}`, + description: `Encountered StartMemoize id=${state.manualMemoState.manualMemoId} followed by FinishMemoize id=${value.manualMemoId}`, loc: value.loc, }, ); 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 be7732333e01..733a62a0a2e9 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 3 errors: +Found 2 errors: Error: useMemo() callbacks may not be async or generator functions @@ -47,22 +47,6 @@ error.invalid-ReactUseMemo-async-callback.ts:3:10 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-useMemo-async-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-async-callback.expect.md index 922119f1f1df..273da427a062 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 3 errors: +Found 2 errors: Error: useMemo() callbacks may not be async or generator functions @@ -47,22 +47,6 @@ error.invalid-useMemo-async-callback.ts:3:10 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 9bbf4ac8cd37..ecde7590abe9 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 3 errors: +Found 2 errors: Error: useMemo() callbacks may not accept parameters @@ -40,18 +40,6 @@ error.invalid-useMemo-callback-args.ts:2:23 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/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 567d59e4546e..2c864f56aff7 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 6 errors: +Found 4 errors: Error: Found missing/extra memoization dependencies @@ -157,48 +157,6 @@ 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 626240b1ae8d..bb991d17dadb 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 2 errors: +Found 1 error: Error: Found missing memoization dependencies @@ -38,19 +38,6 @@ 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/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 c311f862128a..fe0bf6c22f66 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 2 errors: +Found 1 error: Error: Found missing memoization dependencies @@ -46,23 +46,6 @@ 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 From b4a8d298450fd1fd274445fe8554e5fc18c5e12c Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com> Date: Thu, 26 Feb 2026 21:48:35 +0000 Subject: [PATCH 2/2] fix: remove unused variable to fix linter (#35919) --- .../src/__tests__/ReactFabric-test.internal.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js b/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js index 5d0c8b8b9e97..925103f2d75a 100644 --- a/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js +++ b/packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js @@ -1189,7 +1189,6 @@ describe('ReactFabric', () => { const ref1 = React.createRef(); const ref2 = React.createRef(); - const ref3 = React.createRef(); const explicitTimeStampCamelCase = 'explicit-timestamp-camelcase'; const explicitTimeStampLowerCase = 'explicit-timestamp-lowercase';