Skip to content

Commit 4b59ef3

Browse files
authored
fix: widen ownership when sub state is assigned to new state (#11217)
Ownership was not widened when assigning a sub state to a different top level state. The set of owners for the state was zero because the owner was on the original parent, but that one was reset to null because it's now the top level of a different state. That meant that there was no owner but also no parent to check for the owner, which is an invalid combination resulting in a nullpointer (and also potentially false positive warnings in other situations). fixes #11204
1 parent c44234d commit 4b59ef3

File tree

6 files changed

+74
-2
lines changed

6 files changed

+74
-2
lines changed

.changeset/weak-frogs-bow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte": patch
3+
---
4+
5+
fix: widen ownership when sub state is assigned to new state

packages/svelte/src/internal/client/dev/ownership.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ export function add_owner(object, owner, global = false) {
127127
}
128128

129129
/**
130-
* @param {import('#client').ProxyMetadata | null} from
131-
* @param {import('#client').ProxyMetadata} to
130+
* @param {import('#client').ProxyMetadata<any> | null} from
131+
* @param {import('#client').ProxyMetadata<any>} to
132132
*/
133133
export function widen_ownership(from, to) {
134134
if (to.owners === null) {

packages/svelte/src/internal/client/proxy.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ export function proxy(value, immutable = true, parent = null) {
3838
// someone copied the state symbol using `Reflect.ownKeys(...)`
3939
if (metadata.t === value || metadata.p === value) {
4040
if (DEV) {
41+
// Since original parent relationship gets lost, we need to copy over ancestor owners
42+
// into current metadata. The object might still exist on both, so we need to widen it.
43+
widen_ownership(metadata, metadata);
4144
metadata.parent = parent;
4245
}
4346

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script>
2+
let { item } = $props();
3+
4+
function onclick() {
5+
item.name = `${item.name} edited`
6+
}
7+
</script>
8+
9+
<div>{item?.name}</div>
10+
<button {onclick}>Then click here</button>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { tick } from 'svelte';
2+
import { test } from '../../test';
3+
4+
/** @type {typeof console.warn} */
5+
let warn;
6+
7+
/** @type {any[]} */
8+
let warnings = [];
9+
10+
export default test({
11+
compileOptions: {
12+
dev: true
13+
},
14+
15+
before_test: () => {
16+
warn = console.warn;
17+
18+
console.warn = (...args) => {
19+
warnings.push(...args);
20+
};
21+
},
22+
23+
after_test: () => {
24+
console.warn = warn;
25+
warnings = [];
26+
},
27+
28+
async test({ assert, target }) {
29+
const [btn1, btn2] = target.querySelectorAll('button');
30+
31+
btn1.click();
32+
await tick();
33+
34+
assert.deepEqual(warnings.length, 0);
35+
36+
btn2.click();
37+
await tick();
38+
39+
assert.deepEqual(warnings.length, 1);
40+
}
41+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
import Child from './Child.svelte';
3+
4+
let items = $state([{ id: "test", name: "this is a test"}, { id:"test2", name: "this is a second test"}]);
5+
let found = $state();
6+
7+
function onclick() {
8+
found = items.find(c => c.id === 'test2');
9+
}
10+
</script>
11+
12+
<button {onclick}>First click here</button>
13+
<Child item={found} />

0 commit comments

Comments
 (0)