From cf0ee553b786d65b2fe6fad62ad69a614df50da2 Mon Sep 17 00:00:00 2001 From: Kuroda Kayn Date: Thu, 7 May 2026 10:04:16 +0800 Subject: [PATCH] fix: ignore stable deps in memo preservation --- .../ValidatePreservedManualMemoization.ts | 4 + ...ve-use-callback-namespace-setter.expect.md | 124 ++++++++++++++++++ .../preserve-use-callback-namespace-setter.js | 34 +++++ 3 files changed, 162 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-use-callback-namespace-setter.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-use-callback-namespace-setter.js 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 d39aa307dfb3..6229260827c5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts @@ -26,6 +26,7 @@ import { ReactiveValue, ScopeId, SourceLocation, + isStableType, } from '../HIR'; import {Environment} from '../HIR/Environment'; import {printIdentifier, printManualMemoDependency} from '../HIR/PrintHIR'; @@ -233,6 +234,9 @@ function validateInferredDep( env: Environment, memoLocation: SourceLocation, ): void { + if (!dep.reactive && dep.path.length === 0 && isStableType(dep.identifier)) { + return; + } let normalizedDep: ManualMemoDependency; const maybeNormalizedRoot = temporaries.get(dep.identifier.id); if (maybeNormalizedRoot != null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-use-callback-namespace-setter.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-use-callback-namespace-setter.expect.md new file mode 100644 index 000000000000..94402c1f8e51 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-use-callback-namespace-setter.expect.md @@ -0,0 +1,124 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import * as React from 'react'; + +function Component(options = {}) { + const {skip} = options; + + const recreate = () => (skip ? undefined : {}); + + let [observable, setObservable] = React.useState( + options.skip ? null : recreate + ); + + const recreateRef = React.useRef(recreate); + React.useLayoutEffect(() => { + recreateRef.current = recreate; + }); + + if (!skip && !observable) { + setObservable((observable = recreate())); + } + + const restart = React.useCallback(() => { + if (observable) { + setObservable(recreateRef.current()); + } + }, [observable]); + + return React.useMemo(() => ({restart}), [restart]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees +import * as React from "react"; + +function Component(t0) { + const $ = _c(11); + let t1; + if ($[0] !== t0) { + t1 = t0 === undefined ? {} : t0; + $[0] = t0; + $[1] = t1; + } else { + t1 = $[1]; + } + const options = t1; + const { skip } = options; + let t2; + if ($[2] !== skip) { + t2 = () => (skip ? undefined : {}); + $[2] = skip; + $[3] = t2; + } else { + t2 = $[3]; + } + const recreate = t2; + + const [t3, setObservable] = React.useState(options.skip ? null : recreate); + let observable = t3; + + const recreateRef = React.useRef(recreate); + let t4; + if ($[4] !== recreate) { + t4 = () => { + recreateRef.current = recreate; + }; + $[4] = recreate; + $[5] = t4; + } else { + t4 = $[5]; + } + React.useLayoutEffect(t4); + + if (!skip && !observable) { + setObservable((observable = recreate())); + } + let t5; + if ($[6] !== observable || $[7] !== setObservable) { + t5 = () => { + if (observable) { + setObservable(recreateRef.current()); + } + }; + $[6] = observable; + $[7] = setObservable; + $[8] = t5; + } else { + t5 = $[8]; + } + + observable; + const restart = t5; + let t6; + if ($[9] !== restart) { + t6 = { restart }; + $[9] = restart; + $[10] = t6; + } else { + t6 = $[10]; + } + return t6; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{}], +}; + +``` + +### Eval output +(kind: ok) {"restart":"[[ function params=0 ]]"} \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-use-callback-namespace-setter.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-use-callback-namespace-setter.js new file mode 100644 index 000000000000..91fdf03f0ac4 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-use-callback-namespace-setter.js @@ -0,0 +1,34 @@ +// @validatePreserveExistingMemoizationGuarantees +import * as React from 'react'; + +function Component(options = {}) { + const {skip} = options; + + const recreate = () => (skip ? undefined : {}); + + let [observable, setObservable] = React.useState( + options.skip ? null : recreate + ); + + const recreateRef = React.useRef(recreate); + React.useLayoutEffect(() => { + recreateRef.current = recreate; + }); + + if (!skip && !observable) { + setObservable((observable = recreate())); + } + + const restart = React.useCallback(() => { + if (observable) { + setObservable(recreateRef.current()); + } + }, [observable]); + + return React.useMemo(() => ({restart}), [restart]); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{}], +};