Skip to content

Commit d71cc71

Browse files
committed
fix: prevent false positive ownership warning when reassigning state
When a proxy is reassigned, we call `$.proxy` again. There are cases where there's a component context set but the reassignment actually happens for variable that is ownerless within shared state or somewhere else. In that case we get false positives right now. The fix is to pass the previous value in to copy over the owners from it. Fixes #11525
1 parent b1e01bf commit d71cc71

File tree

5 files changed

+37
-14
lines changed

5 files changed

+37
-14
lines changed

.changeset/modern-apricots-promise.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: prevent false positive ownership warning when reassigning state

packages/svelte/src/compiler/phases/3-transform/client/utils.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ export function serialize_set_binding(node, context, fallback, options) {
203203
assignment.right =
204204
private_state.kind === 'frozen_state'
205205
? b.call('$.freeze', value)
206-
: b.call('$.proxy', value);
206+
: b.call('$.proxy', value, b.true, b.null, b.member(b.this, private_state.id));
207207
return assignment;
208208
}
209209
}
@@ -216,7 +216,7 @@ export function serialize_set_binding(node, context, fallback, options) {
216216
should_proxy_or_freeze(value, context.state.scope)
217217
? private_state.kind === 'frozen_state'
218218
? b.call('$.freeze', value)
219-
: b.call('$.proxy', value)
219+
: b.call('$.proxy', value, b.true, b.null, b.member(b.this, private_state.id))
220220
: value
221221
);
222222
}
@@ -240,7 +240,7 @@ export function serialize_set_binding(node, context, fallback, options) {
240240
assignment.right =
241241
public_state.kind === 'frozen_state'
242242
? b.call('$.freeze', value)
243-
: b.call('$.proxy', value);
243+
: b.call('$.proxy', value, b.true, b.null, b.member(b.this, public_state.id));
244244
return assignment;
245245
}
246246
}
@@ -305,7 +305,7 @@ export function serialize_set_binding(node, context, fallback, options) {
305305
context.state.analysis.runes &&
306306
!options?.skip_proxy_and_freeze &&
307307
should_proxy_or_freeze(value, context.state.scope)
308-
? b.call('$.proxy', value)
308+
? b.call('$.proxy', value, b.true, b.null, b.id(left_name))
309309
: value
310310
);
311311
} else if (binding.kind === 'frozen_state') {

packages/svelte/src/compiler/phases/3-transform/client/visitors/javascript-runes.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,15 @@ export const javascript_visitors_runes = {
139139
'set',
140140
definition.key,
141141
[value],
142-
[b.stmt(b.call('$.set', member, b.call('$.proxy', value)))]
142+
[
143+
b.stmt(
144+
b.call(
145+
'$.set',
146+
member,
147+
b.call('$.proxy', value, b.true, b.null, b.member(b.this, field.id))
148+
)
149+
)
150+
]
143151
)
144152
);
145153
}

packages/svelte/src/compiler/utils/builders.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export function labeled(name, body) {
9999

100100
/**
101101
* @param {string | import('estree').Expression} callee
102-
* @param {...(import('estree').Expression | import('estree').SpreadElement | false | undefined)} args
102+
* @param {...(import('estree').Expression | import('estree').SpreadElement | import('estree').PrivateIdentifier | false | undefined)} args
103103
* @returns {import('estree').CallExpression}
104104
*/
105105
export function call(callee, ...args) {
@@ -469,6 +469,7 @@ export function do_while(test, body) {
469469

470470
const true_instance = literal(true);
471471
const false_instance = literal(false);
472+
const null_instane = literal(null);
472473

473474
/** @type {import('estree').DebuggerStatement} */
474475
const debugger_builder = {
@@ -630,6 +631,7 @@ export {
630631
return_builder as return,
631632
if_builder as if,
632633
this_instance as this,
634+
null_instane as null,
633635
debugger_builder as debugger
634636
};
635637

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ import * as e from './errors.js';
2727
* @param {T} value
2828
* @param {boolean} [immutable]
2929
* @param {import('#client').ProxyMetadata | null} [parent]
30+
* @param {import('#client').Source<T>} [prev]
3031
* @returns {import('#client').ProxyStateObject<T> | T}
3132
*/
32-
export function proxy(value, immutable = true, parent = null) {
33+
export function proxy(value, immutable = true, parent = null, prev) {
3334
if (typeof value === 'object' && value != null && !is_frozen(value)) {
3435
// If we have an existing proxy, return it...
3536
if (STATE_SYMBOL in value) {
@@ -70,14 +71,21 @@ export function proxy(value, immutable = true, parent = null) {
7071
if (DEV) {
7172
// @ts-expect-error
7273
value[STATE_SYMBOL].parent = parent;
73-
7474
// @ts-expect-error
75-
value[STATE_SYMBOL].owners =
76-
parent === null
77-
? current_component_context !== null
78-
? new Set([current_component_context.function])
79-
: null
80-
: new Set();
75+
prev = prev?.v?.[STATE_SYMBOL];
76+
77+
if (prev) {
78+
// @ts-expect-error reuse owners from previous state; necessary because creation could happen in the wrong context
79+
value[STATE_SYMBOL].owners = prev.owners ? new Set(prev.owners) : null;
80+
} else {
81+
// @ts-expect-error
82+
value[STATE_SYMBOL].owners =
83+
parent === null
84+
? current_component_context !== null
85+
? new Set([current_component_context.function])
86+
: null
87+
: new Set();
88+
}
8189
}
8290

8391
return proxy;

0 commit comments

Comments
 (0)