From 26180d5f072a450bdf037fdab16b2f6fe7222307 Mon Sep 17 00:00:00 2001 From: Fedor Nezhivoi Date: Tue, 24 Jun 2025 10:36:55 +0700 Subject: [PATCH 1/3] fix: lift unsafe_state_mutation constrained for SvelteSet and SvelteMap created inside the derived --- packages/svelte/src/reactivity/map.js | 6 ++--- packages/svelte/src/reactivity/set.js | 6 ++--- .../side-effect-derived-array/_config.js | 23 ++++++++++++++++ .../side-effect-derived-array/main.svelte | 25 +++++++++++++++++ .../side-effect-derived-map/_config.js | 23 ++++++++++++++++ .../side-effect-derived-map/main.svelte | 27 +++++++++++++++++++ .../side-effect-derived-object/_config.js | 23 ++++++++++++++++ .../side-effect-derived-object/main.svelte | 25 +++++++++++++++++ .../side-effect-derived-primitive/_config.js | 23 ++++++++++++++++ .../side-effect-derived-primitive/main.svelte | 25 +++++++++++++++++ .../side-effect-derived-set/_config.js | 23 ++++++++++++++++ .../side-effect-derived-set/main.svelte | 27 +++++++++++++++++++ 12 files changed, 250 insertions(+), 6 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/main.svelte diff --git a/packages/svelte/src/reactivity/map.js b/packages/svelte/src/reactivity/map.js index eed163dbf29b..cd2fac163fc6 100644 --- a/packages/svelte/src/reactivity/map.js +++ b/packages/svelte/src/reactivity/map.js @@ -1,6 +1,6 @@ /** @import { Source } from '#client' */ import { DEV } from 'esm-env'; -import { set, source } from '../internal/client/reactivity/sources.js'; +import { set, source, state } from '../internal/client/reactivity/sources.js'; import { label, tag } from '../internal/client/dev/tracing.js'; import { get } from '../internal/client/runtime.js'; import { increment } from './utils.js'; @@ -54,8 +54,8 @@ import { increment } from './utils.js'; export class SvelteMap extends Map { /** @type {Map>} */ #sources = new Map(); - #version = source(0); - #size = source(0); + #version = state(0); + #size = state(0); /** * @param {Iterable | null | undefined} [value] diff --git a/packages/svelte/src/reactivity/set.js b/packages/svelte/src/reactivity/set.js index fd22014cb3f6..8a656c2bc14a 100644 --- a/packages/svelte/src/reactivity/set.js +++ b/packages/svelte/src/reactivity/set.js @@ -1,6 +1,6 @@ /** @import { Source } from '#client' */ import { DEV } from 'esm-env'; -import { source, set } from '../internal/client/reactivity/sources.js'; +import { source, set, state } from '../internal/client/reactivity/sources.js'; import { label, tag } from '../internal/client/dev/tracing.js'; import { get } from '../internal/client/runtime.js'; import { increment } from './utils.js'; @@ -48,8 +48,8 @@ var inited = false; export class SvelteSet extends Set { /** @type {Map>} */ #sources = new Map(); - #version = source(0); - #size = source(0); + #version = state(0); + #size = state(0); /** * @param {Iterable | null | undefined} [value] diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/_config.js new file mode 100644 index 000000000000..5ad6f57e311f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/main.svelte new file mode 100644 index 000000000000..6468dbebc9b7 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-array/main.svelte @@ -0,0 +1,25 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/_config.js new file mode 100644 index 000000000000..5ad6f57e311f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/main.svelte new file mode 100644 index 000000000000..bdd5ccb75c91 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-map/main.svelte @@ -0,0 +1,27 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/_config.js new file mode 100644 index 000000000000..5ad6f57e311f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/main.svelte new file mode 100644 index 000000000000..2174bde59f60 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-object/main.svelte @@ -0,0 +1,25 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/_config.js new file mode 100644 index 000000000000..5ad6f57e311f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/main.svelte new file mode 100644 index 000000000000..21de8b9d91c9 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-primitive/main.svelte @@ -0,0 +1,25 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/_config.js new file mode 100644 index 000000000000..5ad6f57e311f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/main.svelte new file mode 100644 index 000000000000..8564f6e7c48e --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-set/main.svelte @@ -0,0 +1,27 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} + From 3c3255fdb98e78b02f52e465a263e581886a14ce Mon Sep 17 00:00:00 2001 From: Fedor Nezhivoi Date: Tue, 24 Jun 2025 10:42:46 +0700 Subject: [PATCH 2/3] add a changeset --- .changeset/fair-bats-visit.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fair-bats-visit.md diff --git a/.changeset/fair-bats-visit.md b/.changeset/fair-bats-visit.md new file mode 100644 index 000000000000..272dbeddc8fe --- /dev/null +++ b/.changeset/fair-bats-visit.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +lift unsafe_state_mutation constrained for SvelteSet and SvelteMap created inside the derived From a78460781192b90768686cde2e03da2f04fe981a Mon Sep 17 00:00:00 2001 From: Fedor Nezhivoi Date: Tue, 24 Jun 2025 11:32:28 +0700 Subject: [PATCH 3/3] fix: lift unsafe_state_mutation constrained for SvelteDate, SvelteURL and SvelteURLSearchParams created inside the derived --- .changeset/fair-bats-visit.md | 2 +- packages/svelte/src/reactivity/date.js | 4 +-- .../src/reactivity/url-search-params.js | 4 +-- packages/svelte/src/reactivity/url.js | 18 ++++++------- .../side-effect-derived-date/_config.js | 23 ++++++++++++++++ .../side-effect-derived-date/main.svelte | 27 +++++++++++++++++++ .../_config.js | 23 ++++++++++++++++ .../main.svelte | 27 +++++++++++++++++++ .../side-effect-derived-url/_config.js | 23 ++++++++++++++++ .../side-effect-derived-url/main.svelte | 27 +++++++++++++++++++ 10 files changed, 164 insertions(+), 14 deletions(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/main.svelte create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/main.svelte diff --git a/.changeset/fair-bats-visit.md b/.changeset/fair-bats-visit.md index 272dbeddc8fe..bc0a42639594 100644 --- a/.changeset/fair-bats-visit.md +++ b/.changeset/fair-bats-visit.md @@ -2,4 +2,4 @@ 'svelte': patch --- -lift unsafe_state_mutation constrained for SvelteSet and SvelteMap created inside the derived +lift unsafe_state_mutation constraints for SvelteSet, SvelteMap, SvelteDate, SvelteURL and SvelteURLSearchParams created inside the derived diff --git a/packages/svelte/src/reactivity/date.js b/packages/svelte/src/reactivity/date.js index 4176f0ceec3f..8e2b5d41ab94 100644 --- a/packages/svelte/src/reactivity/date.js +++ b/packages/svelte/src/reactivity/date.js @@ -1,6 +1,6 @@ /** @import { Source } from '#client' */ import { derived } from '../internal/client/index.js'; -import { source, set } from '../internal/client/reactivity/sources.js'; +import { set, state } from '../internal/client/reactivity/sources.js'; import { tag } from '../internal/client/dev/tracing.js'; import { active_reaction, get, set_active_reaction } from '../internal/client/runtime.js'; import { DEV } from 'esm-env'; @@ -40,7 +40,7 @@ var inited = false; * ``` */ export class SvelteDate extends Date { - #time = source(super.getTime()); + #time = state(super.getTime()); /** @type {Map>} */ #deriveds = new Map(); diff --git a/packages/svelte/src/reactivity/url-search-params.js b/packages/svelte/src/reactivity/url-search-params.js index c77ff9c8225c..389da7cdb67a 100644 --- a/packages/svelte/src/reactivity/url-search-params.js +++ b/packages/svelte/src/reactivity/url-search-params.js @@ -1,5 +1,5 @@ import { DEV } from 'esm-env'; -import { source } from '../internal/client/reactivity/sources.js'; +import { state } from '../internal/client/reactivity/sources.js'; import { tag } from '../internal/client/dev/tracing.js'; import { get } from '../internal/client/runtime.js'; import { get_current_url } from './url.js'; @@ -34,7 +34,7 @@ export const REPLACE = Symbol(); * ``` */ export class SvelteURLSearchParams extends URLSearchParams { - #version = DEV ? tag(source(0), 'SvelteURLSearchParams version') : source(0); + #version = DEV ? tag(state(0), 'SvelteURLSearchParams version') : state(0); #url = get_current_url(); #updating = false; diff --git a/packages/svelte/src/reactivity/url.js b/packages/svelte/src/reactivity/url.js index 56732a040247..dfede23f6ee2 100644 --- a/packages/svelte/src/reactivity/url.js +++ b/packages/svelte/src/reactivity/url.js @@ -1,5 +1,5 @@ import { DEV } from 'esm-env'; -import { source, set } from '../internal/client/reactivity/sources.js'; +import { set, state } from '../internal/client/reactivity/sources.js'; import { tag } from '../internal/client/dev/tracing.js'; import { get } from '../internal/client/runtime.js'; import { REPLACE, SvelteURLSearchParams } from './url-search-params.js'; @@ -40,14 +40,14 @@ export function get_current_url() { * ``` */ export class SvelteURL extends URL { - #protocol = source(super.protocol); - #username = source(super.username); - #password = source(super.password); - #hostname = source(super.hostname); - #port = source(super.port); - #pathname = source(super.pathname); - #hash = source(super.hash); - #search = source(super.search); + #protocol = state(super.protocol); + #username = state(super.username); + #password = state(super.password); + #hostname = state(super.hostname); + #port = state(super.port); + #pathname = state(super.pathname); + #hash = state(super.hash); + #search = state(super.search); #searchParams; /** diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/_config.js new file mode 100644 index 000000000000..5ad6f57e311f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/main.svelte new file mode 100644 index 000000000000..a3c6aa629fec --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-date/main.svelte @@ -0,0 +1,27 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/_config.js new file mode 100644 index 000000000000..5ad6f57e311f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/main.svelte new file mode 100644 index 000000000000..014a1e4e6dcf --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url-search-params/main.svelte @@ -0,0 +1,27 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} + diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/_config.js b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/_config.js new file mode 100644 index 000000000000..5ad6f57e311f --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/_config.js @@ -0,0 +1,23 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + compileOptions: { + dev: true, + runes: true + }, + + test({ assert, target }) { + const [button1, button2] = target.querySelectorAll('button'); + + assert.throws(() => { + button1?.click(); + flushSync(); + }, /state_unsafe_mutation/); + + assert.doesNotThrow(() => { + button2?.click(); + flushSync(); + }); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/main.svelte b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/main.svelte new file mode 100644 index 000000000000..69ead384c311 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/side-effect-derived-url/main.svelte @@ -0,0 +1,27 @@ + + + +{#if visibleExternal} + {throws} +{/if} + +{#if visibleInternal} + {works} +{/if} +