diff --git a/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts index 556ace5e92..830a0d283d 100644 --- a/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts +++ b/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts @@ -189,24 +189,17 @@ class Context { (currentDeclaration.scope == null || !this.#isScopeActive(currentDeclaration.scope)) ) { + // Below logic ensures that `operand` is either added to `this.#dependencies` + // directly, or is covered by an existing dependency. + // Check if there is an existing dependency that describes this operand for (const dep of this.#dependencies) { // not the same identifier if (dep.place.identifier.id !== maybeDependency.place.identifier.id) { continue; } - const depPath = dep.path; - // existing dep covers all paths - if (depPath === null) { - return; - } - const operandPath = maybeDependency.path; - // existing dep is for a path, this operand covers all paths so swap them - if (operandPath === null) { - this.#dependencies.delete(dep); - this.#dependencies.add(maybeDependency); - return; - } + const depPath = dep.path ?? []; + const operandPath = maybeDependency.path ?? []; // both the operand and dep have paths, determine if the existing path // is a subset of the new path let commonPathIndex = 0; @@ -218,7 +211,13 @@ class Context { commonPathIndex++; } if (commonPathIndex === depPath.length) { + // existing dep is a subpath of the operand, so we don't need to + // add the operand return; + } else if (commonPathIndex === operandPath.length) { + // operand is a subpath of the existing path, delete the existing + // path + this.#dependencies.delete(dep); } } this.#dependencies.add(maybeDependency); diff --git a/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-nonoverlap-descendant.expect.md b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-nonoverlap-descendant.expect.md new file mode 100644 index 0000000000..6759d8c732 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-nonoverlap-descendant.expect.md @@ -0,0 +1,44 @@ + +## Input + +```javascript +// Test that we can track non-overlapping dependencies separately. +// (not needed for correctness but for dependency granularity) +function TestNonOverlappingDescendantTracked(props) { + let x = {}; + x.a = props.a.x.y; + x.b = props.b; + x.c = props.a.c.x.y.z; + return x; +} + +``` + +## Code + +```javascript +// Test that we can track non-overlapping dependencies separately. +// (not needed for correctness but for dependency granularity) +function TestNonOverlappingDescendantTracked(props) { + const $ = React.unstable_useMemoCache(4); + const c_0 = $[0] !== props.a.x.y; + const c_1 = $[1] !== props.b; + const c_2 = $[2] !== props.a.c.x.y.z; + let x; + if (c_0 || c_1 || c_2) { + x = {}; + x.a = props.a.x.y; + x.b = props.b; + x.c = props.a.c.x.y.z; + $[0] = props.a.x.y; + $[1] = props.b; + $[2] = props.a.c.x.y.z; + $[3] = x; + } else { + x = $[3]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-nonoverlap-descendant.js b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-nonoverlap-descendant.js new file mode 100644 index 0000000000..65557df725 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-nonoverlap-descendant.js @@ -0,0 +1,9 @@ +// Test that we can track non-overlapping dependencies separately. +// (not needed for correctness but for dependency granularity) +function TestNonOverlappingDescendantTracked(props) { + let x = {}; + x.a = props.a.x.y; + x.b = props.b; + x.c = props.a.c.x.y.z; + return x; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-nonoverlap-direct.expect.md b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-nonoverlap-direct.expect.md new file mode 100644 index 0000000000..0336312c4a --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-nonoverlap-direct.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +// Test that we can track non-overlapping dependencies separately. +// (not needed for correctness but for dependency granularity) +function TestNonOverlappingTracked(props) { + let x = {}; + x.b = props.a.b; + x.c = props.a.c; + return x; +} + +``` + +## Code + +```javascript +// Test that we can track non-overlapping dependencies separately. +// (not needed for correctness but for dependency granularity) +function TestNonOverlappingTracked(props) { + const $ = React.unstable_useMemoCache(3); + const c_0 = $[0] !== props.a.b; + const c_1 = $[1] !== props.a.c; + let x; + if (c_0 || c_1) { + x = {}; + x.b = props.a.b; + x.c = props.a.c; + $[0] = props.a.b; + $[1] = props.a.c; + $[2] = x; + } else { + x = $[2]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-nonoverlap-direct.js b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-nonoverlap-direct.js new file mode 100644 index 0000000000..9a4bfabeb8 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-nonoverlap-direct.js @@ -0,0 +1,8 @@ +// Test that we can track non-overlapping dependencies separately. +// (not needed for correctness but for dependency granularity) +function TestNonOverlappingTracked(props) { + let x = {}; + x.b = props.a.b; + x.c = props.a.c; + return x; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-overlap-descendant.expect.md b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-overlap-descendant.expect.md new file mode 100644 index 0000000000..75cb92c532 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-overlap-descendant.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +// Test that we correctly track a subpath if the subpath itself is accessed as +// a dependency +function TestOverlappingDescendantTracked(props) { + let x = {}; + x.b = props.a.b.c; + x.c = props.a.b.c.x.y; + x.a = props.a; + return x; +} + +``` + +## Code + +```javascript +// Test that we correctly track a subpath if the subpath itself is accessed as +// a dependency +function TestOverlappingDescendantTracked(props) { + const $ = React.unstable_useMemoCache(2); + const c_0 = $[0] !== props.a; + let x; + if (c_0) { + x = {}; + x.b = props.a.b.c; + x.c = props.a.b.c.x.y; + x.a = props.a; + $[0] = props.a; + $[1] = x; + } else { + x = $[1]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-overlap-descendant.js b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-overlap-descendant.js new file mode 100644 index 0000000000..0ebc163e7c --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-overlap-descendant.js @@ -0,0 +1,9 @@ +// Test that we correctly track a subpath if the subpath itself is accessed as +// a dependency +function TestOverlappingDescendantTracked(props) { + let x = {}; + x.b = props.a.b.c; + x.c = props.a.b.c.x.y; + x.a = props.a; + return x; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-overlap-direct.expect.md b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-overlap-direct.expect.md new file mode 100644 index 0000000000..84da7c119e --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-overlap-direct.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +// Test that we correctly track a subpath if the subpath itself is accessed as +// a dependency +function TestOverlappingTracked(props) { + let x = {}; + x.b = props.a.b; + x.c = props.a.c; + x.a = props.a; + return x; +} + +``` + +## Code + +```javascript +// Test that we correctly track a subpath if the subpath itself is accessed as +// a dependency +function TestOverlappingTracked(props) { + const $ = React.unstable_useMemoCache(2); + const c_0 = $[0] !== props.a; + let x; + if (c_0) { + x = {}; + x.b = props.a.b; + x.c = props.a.c; + x.a = props.a; + $[0] = props.a; + $[1] = x; + } else { + x = $[1]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-overlap-direct.js b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-overlap-direct.js new file mode 100644 index 0000000000..9dc0b31043 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-overlap-direct.js @@ -0,0 +1,9 @@ +// Test that we correctly track a subpath if the subpath itself is accessed as +// a dependency +function TestOverlappingTracked(props) { + let x = {}; + x.b = props.a.b; + x.c = props.a.c; + x.a = props.a; + return x; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order1.expect.md b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order1.expect.md new file mode 100644 index 0000000000..964fe4f067 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order1.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +// Determine that we only need to track p.a here +// Ordering of access should not matter +function TestDepsSubpathOrder1(props) { + let x = {}; + x.b = props.a.b; + x.a = props.a; + x.c = props.a.b.c; + return x; +} + +``` + +## Code + +```javascript +// Determine that we only need to track p.a here +// Ordering of access should not matter +function TestDepsSubpathOrder1(props) { + const $ = React.unstable_useMemoCache(2); + const c_0 = $[0] !== props.a; + let x; + if (c_0) { + x = {}; + x.b = props.a.b; + x.a = props.a; + x.c = props.a.b.c; + $[0] = props.a; + $[1] = x; + } else { + x = $[1]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order1.js b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order1.js new file mode 100644 index 0000000000..ccf4afec5d --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order1.js @@ -0,0 +1,9 @@ +// Determine that we only need to track p.a here +// Ordering of access should not matter +function TestDepsSubpathOrder1(props) { + let x = {}; + x.b = props.a.b; + x.a = props.a; + x.c = props.a.b.c; + return x; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order2.expect.md b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order2.expect.md new file mode 100644 index 0000000000..ec0e124308 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order2.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +// Determine that we only need to track p.a here +// Ordering of access should not matter +function TestDepsSubpathOrder2(props) { + let x = {}; + x.a = props.a; + x.b = props.a.b; + x.c = props.a.b.c; + return x; +} + +``` + +## Code + +```javascript +// Determine that we only need to track p.a here +// Ordering of access should not matter +function TestDepsSubpathOrder2(props) { + const $ = React.unstable_useMemoCache(2); + const c_0 = $[0] !== props.a; + let x; + if (c_0) { + x = {}; + x.a = props.a; + x.b = props.a.b; + x.c = props.a.b.c; + $[0] = props.a; + $[1] = x; + } else { + x = $[1]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order2.js b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order2.js new file mode 100644 index 0000000000..1b078ea8ea --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order2.js @@ -0,0 +1,9 @@ +// Determine that we only need to track p.a here +// Ordering of access should not matter +function TestDepsSubpathOrder2(props) { + let x = {}; + x.a = props.a; + x.b = props.a.b; + x.c = props.a.b.c; + return x; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order3.expect.md b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order3.expect.md new file mode 100644 index 0000000000..4b5e191a35 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order3.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +// Determine that we only need to track p.a here +// Ordering of access should not matter +function TestDepsSubpathOrder3(props) { + let x = {}; + x.c = props.a.b.c; + x.a = props.a; + x.b = props.a.b; + return x; +} + +``` + +## Code + +```javascript +// Determine that we only need to track p.a here +// Ordering of access should not matter +function TestDepsSubpathOrder3(props) { + const $ = React.unstable_useMemoCache(2); + const c_0 = $[0] !== props.a; + let x; + if (c_0) { + x = {}; + x.c = props.a.b.c; + x.a = props.a; + x.b = props.a.b; + $[0] = props.a; + $[1] = x; + } else { + x = $[1]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order3.js b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order3.js new file mode 100644 index 0000000000..5d72ff2deb --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/reduce-reactive-uncond-deps-subpath-order3.js @@ -0,0 +1,9 @@ +// Determine that we only need to track p.a here +// Ordering of access should not matter +function TestDepsSubpathOrder3(props) { + let x = {}; + x.c = props.a.b.c; + x.a = props.a; + x.b = props.a.b; + return x; +}