fix: lift unsafe_state_mutation constraints for SvelteSet and SvelteMap created inside the derived (#16221)

Co-authored-by: Fedor Nezhivoi <f.nezhivoi@corp.vk.com>
warn-read-just-created-state
Fedor Nezhivoi 2 months ago committed by GitHub
parent c4b32c2bff
commit 2af7ba2156
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
lift unsafe_state_mutation constraints for SvelteSet, SvelteMap, SvelteDate, SvelteURL and SvelteURLSearchParams created inside the derived

@ -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<keyof Date, Source<unknown>>} */
#deriveds = new Map();

@ -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<K, Source<number>>} */
#sources = new Map();
#version = source(0);
#size = source(0);
#version = state(0);
#size = state(0);
/**
* @param {Iterable<readonly [K, V]> | null | undefined} [value]

@ -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<T, Source<boolean>>} */
#sources = new Map();
#version = source(0);
#size = source(0);
#version = state(0);
#size = state(0);
/**
* @param {Iterable<T> | null | undefined} [value]

@ -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;

@ -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;
/**

@ -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();
});
}
});

@ -0,0 +1,25 @@
<script>
let visibleExternal = $state(false);
let external = $state([]);
const throws = $derived.by(() => {
external.push(1);
return external;
});
let visibleInternal = $state(false)
const works = $derived.by(() => {
let internal = $state([]);
internal.push(1);
return internal;
});
</script>
<button onclick={() => (visibleExternal = true)}>external</button>
{#if visibleExternal}
{throws}
{/if}
<button onclick={() => (visibleInternal = true)}>internal</button>
{#if visibleInternal}
{works}
{/if}

@ -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();
});
}
});

@ -0,0 +1,27 @@
<script>
import { SvelteDate } from 'svelte/reactivity';
let visibleExternal = $state(false);
let external = new SvelteDate();
const throws = $derived.by(() => {
external.setTime(12345);
return external;
})
let visibleInternal = $state(false);
const works = $derived.by(() => {
let internal = new SvelteDate();
internal.setTime(12345);
return internal;
})
</script>
<button onclick={() => (visibleExternal = true)}>external</button>
{#if visibleExternal}
{throws}
{/if}
<button onclick={() => (visibleInternal = true)}>internal</button>
{#if visibleInternal}
{works}
{/if}

@ -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();
});
}
});

@ -0,0 +1,27 @@
<script>
import { SvelteMap } from 'svelte/reactivity';
let visibleExternal = $state(false);
let external = new SvelteMap();
const throws = $derived.by(() => {
external.set(1, 1);
return external;
});
let visibleInternal = $state(false);
const works = $derived.by(() => {
let internal = new SvelteMap();
internal.set(1, 1);
return internal;
});
</script>
<button onclick={() => (visibleExternal = true)}>external</button>
{#if visibleExternal}
{throws}
{/if}
<button onclick={() => (visibleInternal = true)}>internal</button>
{#if visibleInternal}
{works}
{/if}

@ -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();
});
}
});

@ -0,0 +1,25 @@
<script>
let visibleExternal = $state(false);
let external = $state({ v: 1 });
const throws = $derived.by(() => {
external.v = 2;
return external;
});
let visibleInternal = $state(false)
const works = $derived.by(() => {
let internal = $state({ v: 1 });
internal.v = 2;
return internal;
});
</script>
<button onclick={() => (visibleExternal = true)}>external</button>
{#if visibleExternal}
{throws}
{/if}
<button onclick={() => (visibleInternal = true)}>internal</button>
{#if visibleInternal}
{works}
{/if}

@ -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();
});
}
});

@ -0,0 +1,25 @@
<script>
let visibleExternal = $state(false);
let external = $state(1);
const throws = $derived.by(() => {
external = 2;
return external;
});
let visibleInternal = $state(false);
const works = $derived.by(() => {
let internal = $state(1);
internal = 2;
return internal;
});
</script>
<button onclick={() => (visibleExternal = true)}>external</button>
{#if visibleExternal}
{throws}
{/if}
<button onclick={() => (visibleInternal = true)}>internal</button>
{#if visibleInternal}
{works}
{/if}

@ -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();
});
}
});

@ -0,0 +1,27 @@
<script>
import { SvelteSet } from 'svelte/reactivity';
let visibleExternal = $state(false);
let external = new SvelteSet();
const throws = $derived.by(() => {
external.add(1);
return external;
})
let visibleInternal = $state(false);
const works = $derived.by(() => {
let internal = new SvelteSet();
internal.add(1);
return internal;
})
</script>
<button onclick={() => (visibleExternal = true)}>external</button>
{#if visibleExternal}
{throws}
{/if}
<button onclick={() => (visibleInternal = true)}>internal</button>
{#if visibleInternal}
{works}
{/if}

@ -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();
});
}
});

@ -0,0 +1,27 @@
<script>
import { SvelteURLSearchParams } from 'svelte/reactivity';
let visibleExternal = $state(false);
let external = new SvelteURLSearchParams();
const throws = $derived.by(() => {
external.append('foo', 'bar')
return external;
})
let visibleInternal = $state(false);
const works = $derived.by(() => {
let internal = new SvelteURLSearchParams();
internal.append('foo', 'bar')
return internal;
})
</script>
<button onclick={() => (visibleExternal = true)}>external</button>
{#if visibleExternal}
{throws}
{/if}
<button onclick={() => (visibleInternal = true)}>internal</button>
{#if visibleInternal}
{works}
{/if}

@ -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();
});
}
});

@ -0,0 +1,27 @@
<script>
import { SvelteURL } from 'svelte/reactivity';
let visibleExternal = $state(false);
let external = new SvelteURL('https://svelte.dev');
const throws = $derived.by(() => {
external.host = 'kit.svelte.dev'
return external;
})
let visibleInternal = $state(false);
const works = $derived.by(() => {
let internal = new SvelteURL('https://svelte.dev');
internal.host = 'kit.svelte.dev'
return internal;
})
</script>
<button onclick={() => (visibleExternal = true)}>external</button>
{#if visibleExternal}
{throws}
{/if}
<button onclick={() => (visibleInternal = true)}>internal</button>
{#if visibleInternal}
{works}
{/if}
Loading…
Cancel
Save