diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts index f249466431..d38a5f4f0a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/CollectHoistablePropertyLoads.ts @@ -33,6 +33,7 @@ import { ScopeId, TInstruction, } from './HIR'; +import {printManualMemoDependency} from './PrintHIR'; const DEBUG_PRINT = false; @@ -454,6 +455,25 @@ function collectNonNullsInBlocks( assumedNonNullObjects.add(entry); } } + } else if ( + fn.env.config.enablePreserveExistingMemoizationGuarantees && + instr.value.kind === 'StartMemoize' && + instr.value.deps != null + ) { + for (const dep of instr.value.deps) { + if (dep.root.kind === 'NamedLocal') { + const depNode = context.registry.getOrCreateProperty({ + identifier: dep.root.value.identifier, + path: dep.path.slice(0, -1), + reactive: dep.root.value.reactive, + }); + if ( + isImmutableAtInstr(depNode.fullPath.identifier, instr.id, context) + ) { + assumedNonNullObjects.add(depNode); + } + } + } } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.expect.md new file mode 100644 index 0000000000..84c611dec3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.expect.md @@ -0,0 +1,122 @@ + +## Input + +```javascript +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import {useMemo} from 'react'; +import {identity, ValidateMemoization} from 'shared-runtime'; + +function Component({x}) { + const object = useMemo(() => { + return identity({ + callback: () => { + return identity(x.y.z); // accesses more levels of properties than the manual memo + }, + }); + // x.y as a manual dep only tells us that x is non-nullable, not that x.y is non-nullable + // we can only take a dep on x.y, not x.y.z + }, [x.y]); + const result = useMemo(() => { + return [object.callback()]; + }, [object]); + return ; +} + +const input1 = {x: {y: {z: 42}}}; +const input1b = {x: {y: {z: 42}}}; +const input2 = {x: {y: {z: 3.14}}}; +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [input1], + sequentialRenders: [ + input1, + input1, + input1b, // should reset even though .z didn't change + input1, + input2, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import { useMemo } from "react"; +import { identity, ValidateMemoization } from "shared-runtime"; + +function Component(t0) { + const $ = _c(11); + const { x } = t0; + let t1; + if ($[0] !== x.y) { + t1 = identity({ callback: () => identity(x.y.z) }); + $[0] = x.y; + $[1] = t1; + } else { + t1 = $[1]; + } + const object = t1; + let t2; + if ($[2] !== object) { + t2 = object.callback(); + $[2] = object; + $[3] = t2; + } else { + t2 = $[3]; + } + let t3; + if ($[4] !== t2) { + t3 = [t2]; + $[4] = t2; + $[5] = t3; + } else { + t3 = $[5]; + } + const result = t3; + let t4; + if ($[6] !== x.y) { + t4 = [x.y]; + $[6] = x.y; + $[7] = t4; + } else { + t4 = $[7]; + } + let t5; + if ($[8] !== result || $[9] !== t4) { + t5 = ; + $[8] = result; + $[9] = t4; + $[10] = t5; + } else { + t5 = $[10]; + } + return t5; +} + +const input1 = { x: { y: { z: 42 } } }; +const input1b = { x: { y: { z: 42 } } }; +const input2 = { x: { y: { z: 3.14 } } }; +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [input1], + sequentialRenders: [ + input1, + input1, + input1b, // should reset even though .z didn't change + input1, + input2, + ], +}; + +``` + +### Eval output +(kind: ok)
{"inputs":[{"z":42}],"output":[42]}
+
{"inputs":[{"z":42}],"output":[42]}
+
{"inputs":[{"z":42}],"output":[42]}
+
{"inputs":[{"z":42}],"output":[42]}
+
{"inputs":[{"z":3.14}],"output":[3.14]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.js new file mode 100644 index 0000000000..373fdc53fa --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain-less-precise-deps.js @@ -0,0 +1,35 @@ +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import {useMemo} from 'react'; +import {identity, ValidateMemoization} from 'shared-runtime'; + +function Component({x}) { + const object = useMemo(() => { + return identity({ + callback: () => { + return identity(x.y.z); // accesses more levels of properties than the manual memo + }, + }); + // x.y as a manual dep only tells us that x is non-nullable, not that x.y is non-nullable + // we can only take a dep on x.y, not x.y.z + }, [x.y]); + const result = useMemo(() => { + return [object.callback()]; + }, [object]); + return ; +} + +const input1 = {x: {y: {z: 42}}}; +const input1b = {x: {y: {z: 42}}}; +const input2 = {x: {y: {z: 3.14}}}; +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [input1], + sequentialRenders: [ + input1, + input1, + input1b, // should reset even though .z didn't change + input1, + input2, + ], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.expect.md new file mode 100644 index 0000000000..82c11f7783 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.expect.md @@ -0,0 +1,117 @@ + +## Input + +```javascript +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import {useMemo} from 'react'; +import {identity, ValidateMemoization} from 'shared-runtime'; + +function Component({x}) { + const object = useMemo(() => { + return identity({ + callback: () => { + return identity(x.y.z); + }, + }); + }, [x.y.z]); + const result = useMemo(() => { + return [object.callback()]; + }, [object]); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{x: {y: {z: 42}}}], + sequentialRenders: [ + {x: {y: {z: 42}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + ], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import { useMemo } from "react"; +import { identity, ValidateMemoization } from "shared-runtime"; + +function Component(t0) { + const $ = _c(11); + const { x } = t0; + let t1; + if ($[0] !== x.y.z) { + t1 = identity({ callback: () => identity(x.y.z) }); + $[0] = x.y.z; + $[1] = t1; + } else { + t1 = $[1]; + } + const object = t1; + let t2; + if ($[2] !== object) { + t2 = object.callback(); + $[2] = object; + $[3] = t2; + } else { + t2 = $[3]; + } + let t3; + if ($[4] !== t2) { + t3 = [t2]; + $[4] = t2; + $[5] = t3; + } else { + t3 = $[5]; + } + const result = t3; + let t4; + if ($[6] !== x.y.z) { + t4 = [x.y.z]; + $[6] = x.y.z; + $[7] = t4; + } else { + t4 = $[7]; + } + let t5; + if ($[8] !== result || $[9] !== t4) { + t5 = ; + $[8] = result; + $[9] = t4; + $[10] = t5; + } else { + t5 = $[10]; + } + return t5; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ x: { y: { z: 42 } } }], + sequentialRenders: [ + { x: { y: { z: 42 } } }, + { x: { y: { z: 42 } } }, + { x: { y: { z: 3.14 } } }, + { x: { y: { z: 42 } } }, + { x: { y: { z: 3.14 } } }, + { x: { y: { z: 42 } } }, + ], +}; + +``` + +### Eval output +(kind: ok)
{"inputs":[42],"output":[42]}
+
{"inputs":[42],"output":[42]}
+
{"inputs":[3.14],"output":[3.14]}
+
{"inputs":[42],"output":[42]}
+
{"inputs":[3.14],"output":[3.14]}
+
{"inputs":[42],"output":[42]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.js new file mode 100644 index 0000000000..6b55e68bb0 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-deps-conditional-property-chain.js @@ -0,0 +1,31 @@ +// @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enableTreatFunctionDepsAsConditional:false + +import {useMemo} from 'react'; +import {identity, ValidateMemoization} from 'shared-runtime'; + +function Component({x}) { + const object = useMemo(() => { + return identity({ + callback: () => { + return identity(x.y.z); + }, + }); + }, [x.y.z]); + const result = useMemo(() => { + return [object.callback()]; + }, [object]); + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{x: {y: {z: 42}}}], + sequentialRenders: [ + {x: {y: {z: 42}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + {x: {y: {z: 3.14}}}, + {x: {y: {z: 42}}}, + ], +};