diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js
index e116b0fb5c..28c95e61f4 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/BindDirective.js
@@ -2,6 +2,7 @@
/** @import { Context } from '../types' */
import {
extract_all_identifiers_from_expression,
+ is_binding_pattern_declaration,
is_text_attribute,
object
} from '../../../utils/ast.js';
@@ -45,8 +46,13 @@ export function BindDirective(node, context) {
e.bind_invalid_value(node.expression);
}
- if (binding?.kind === 'derived' && binding.declaration_kind === 'const') {
- e.constant_binding(node.expression, 'derived state');
+ if (binding?.kind === 'derived') {
+ if (binding.declaration_kind === 'const') {
+ e.constant_binding(node.expression, 'constant derived state');
+ }
+ if (is_binding_pattern_declaration(binding)) {
+ e.constant_binding(node.expression, 'destructured derived state');
+ }
}
if (context.state.analysis.runes && binding?.kind === 'each') {
diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js
index dd3e05d57e..c24201c157 100644
--- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js
+++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js
@@ -4,7 +4,7 @@
/** @import { Scope } from '../../../scope' */
/** @import { NodeLike } from '../../../../errors.js' */
import * as e from '../../../../errors.js';
-import { extract_identifiers, object } from '../../../../utils/ast.js';
+import { extract_identifiers, is_binding_pattern_declaration, object } from '../../../../utils/ast.js';
import * as w from '../../../../warnings.js';
/**
@@ -23,17 +23,8 @@ export function validate_assignment(node, argument, state) {
if (binding.declaration_kind === 'const') {
e.constant_assignment(node, 'constant derived state');
}
- const binding_path = binding.references.find((r) => r.node === binding.node);
-
- if (binding_path) {
- let declarator_path = binding_path.path.findLast((n) => n.type === 'VariableDeclarator');
- if (
- declarator_path &&
- (declarator_path.id.type === 'ObjectPattern' ||
- declarator_path.id.type === 'ArrayPattern')
- ) {
- e.constant_assignment(node, 'destructured derived state');
- }
+ if (is_binding_pattern_declaration(binding)) {
+ e.constant_assignment(node, 'destructured derived state');
}
}
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/utils.js b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
index 0f3f450a46..7d662629da 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/utils.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/utils.js
@@ -50,7 +50,6 @@ export function build_getter(node, state) {
* @param {PrivateIdentifier | string} proxy_reference
*/
export function build_proxy_reassignment(value, proxy_reference) {
- debugger
return dev
? b.call(
'$.proxy',
diff --git a/packages/svelte/src/compiler/utils/ast.js b/packages/svelte/src/compiler/utils/ast.js
index 273701a0d1..671105b811 100644
--- a/packages/svelte/src/compiler/utils/ast.js
+++ b/packages/svelte/src/compiler/utils/ast.js
@@ -20,6 +20,23 @@ export function object(expression) {
return expression;
}
+/**
+ * @param {import("#compiler").Binding} binding
+ * @returns {boolean}
+ */
+export function is_binding_pattern_declaration(binding) {
+ const binding_path = binding.references.find((r) => r.node === binding.node);
+
+ if (binding_path) {
+ let declarator_path = binding_path.path.findLast((n) => n.type === 'VariableDeclarator');
+
+ return (
+ declarator_path?.id.type === 'ObjectPattern' || declarator_path?.id.type === 'ArrayPattern'
+ );
+ }
+ return false;
+}
+
/**
* Returns true if the attribute contains a single static text node.
* @param {Attribute} attribute
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-binding/_config.js b/packages/svelte/tests/compiler-errors/samples/runes-no-destructured-derived-binding/_config.js
similarity index 65%
rename from packages/svelte/tests/compiler-errors/samples/runes-no-derived-binding/_config.js
rename to packages/svelte/tests/compiler-errors/samples/runes-no-destructured-derived-binding/_config.js
index 87b88d79cc..819f044d20 100644
--- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-binding/_config.js
+++ b/packages/svelte/tests/compiler-errors/samples/runes-no-destructured-derived-binding/_config.js
@@ -3,6 +3,6 @@ import { test } from '../../test';
export default test({
error: {
code: 'constant_binding',
- message: 'Cannot bind to derived state'
+ message: 'Cannot bind to destructured derived state'
}
});
diff --git a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-binding/main.svelte b/packages/svelte/tests/compiler-errors/samples/runes-no-destructured-derived-binding/main.svelte
similarity index 73%
rename from packages/svelte/tests/compiler-errors/samples/runes-no-derived-binding/main.svelte
rename to packages/svelte/tests/compiler-errors/samples/runes-no-destructured-derived-binding/main.svelte
index 6c198dc068..5a1ce987aa 100644
--- a/packages/svelte/tests/compiler-errors/samples/runes-no-derived-binding/main.svelte
+++ b/packages/svelte/tests/compiler-errors/samples/runes-no-destructured-derived-binding/main.svelte
@@ -1,6 +1,6 @@
diff --git a/packages/svelte/tests/runtime-runes/samples/derived-update-class/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-update-class/_config.js
new file mode 100644
index 0000000000..4628d4f5b4
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/derived-update-class/_config.js
@@ -0,0 +1,19 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ html: ``,
+
+ test({ assert, target }) {
+ const [btn1, btn2] = target.querySelectorAll('button');
+
+ flushSync(() => btn1.click());
+ assert.htmlEqual(target.innerHTML, ``);
+
+ flushSync(() => btn2.click());
+ assert.htmlEqual(target.innerHTML, ``);
+
+ flushSync(() => btn1.click());
+ assert.htmlEqual(target.innerHTML, ``);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/derived-update-class/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-update-class/main.svelte
new file mode 100644
index 0000000000..42a1e7f34d
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/derived-update-class/main.svelte
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/packages/svelte/tests/runtime-runes/samples/derived-update/_config.js b/packages/svelte/tests/runtime-runes/samples/derived-update/_config.js
new file mode 100644
index 0000000000..4628d4f5b4
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/derived-update/_config.js
@@ -0,0 +1,19 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+ html: ``,
+
+ test({ assert, target }) {
+ const [btn1, btn2] = target.querySelectorAll('button');
+
+ flushSync(() => btn1.click());
+ assert.htmlEqual(target.innerHTML, ``);
+
+ flushSync(() => btn2.click());
+ assert.htmlEqual(target.innerHTML, ``);
+
+ flushSync(() => btn1.click());
+ assert.htmlEqual(target.innerHTML, ``);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/derived-update/main.svelte b/packages/svelte/tests/runtime-runes/samples/derived-update/main.svelte
new file mode 100644
index 0000000000..894c2adce1
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/derived-update/main.svelte
@@ -0,0 +1,7 @@
+
+
+
+