Repository: sveltejs/svelte (JavaScript)

Issue:
`assignment_value_stale` warning possible with function bindings
### Describe the bug

This only happens in very specific circumstances, e.g. "binding" to a set-only property of a snippet argument.
The warning is not easy to understand in that context and will not happen with a regular property binding.

(The code behaves as expected so the warning feels like it might be a false positive.)

### Reproduction

```svelte
<script>
	let elementFunBind = $state();
	$inspect('fun', elementFunBind?.tagName);
	
	let elementPropBind = $state();
	$inspect('prop', elementPropBind?.tagName);

	let elementEvent = $state();
	$inspect('event', elementEvent?.tagName);
</script>

{#snippet funBind(context)}
	<!-- only this triggers the warning: -->
	<input value="via fun" bind:this={() => {}, e => context.element = e} />
{/snippet}
{@render funBind({ set element(e) { elementFunBind = e } })}

{#snippet propBind(context)}
	<textarea bind:this={context.element}>via prop</textarea>
{/snippet}
{@render propBind({ set element(e) { elementPropBind = e } })}

{#snippet event(context)}
	<button onclick={e => context.element = e.target}>via event</button>
{/snippet}
{@render event({ set element(e) { elementEvent = e } })}
```
[Playground](https://svelte.dev/playground/untitled?version=5.23.2#H4sIAAAAAAAACn2ST0_DMAzFv4oJk-ikbr2XdvyR4Ii4cKIcss4rEZ0bJe5givLdUbqObYVyi5zn599z4gTJDYpUvBArrnElYrFWNVqRvjrBOx3uQkHEB-Wd1nO7xZpDbSkt_lUvG2IktiIVmS2N0rwoqOAaGbDGDRI_tnSvaAU5TCxLxmh6HRQTRVZjydHVuqWreKC-mbOsnuQG9-KB5bNp9P-e2jT6aHrQn7ueez5skXjcEMP10bFTn9llyTE-uUtLSmtkWO_zRN2evnjqg212MZtBQ_UO-F1ZYKOqCo0Ffkf4lIYUVSnMZt0mM0W6ZdjKusW8EFslg2chYKlolYb-3EVTyBfgfAwYDv2sec8KOaCHZFGQS3osX5C7NUgrND-EDuxxGxFOwf1-QQQPvstwElH32x1kDEdpUJ6CDsj8IsQJ_VlykI9h_kwZ5zz5Fn-Cdm84oFy2zA1BQ2Wtyo_cjS1wztJU2BN3Rlmy7x0D3k8bpz18uB5VxCLMFCmbFv2b_wYajU2jsQMAAA==)

### Logs

```shell

```

### System Info

```shell
REPL
```

### Severity

annoyance


Discussion:

Patch:
diff --git a/.changeset/rare-falcons-admire.md b/.changeset/rare-falcons-admire.md
new file mode 100644
index 000000000000..ad13dd1cbca7
--- /dev/null
+++ b/.changeset/rare-falcons-admire.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: silence assignment warning on more function bindings
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js
index 150c56e166c1..850cd83b15b6 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AssignmentExpression.js
@@ -165,7 +165,9 @@ function build_assignment(operator, left, right, context) {
 		path.at(-1) === 'SvelteComponent' ||
 		(path.at(-1) === 'ArrowFunctionExpression' &&
 			path.at(-2) === 'SequenceExpression' &&
-			(path.at(-3) === 'Component' || path.at(-3) === 'SvelteComponent'))
+			(path.at(-3) === 'Component' ||
+				path.at(-3) === 'SvelteComponent' ||
+				path.at(-3) === 'BindDirective'))
 	) {
 		should_transform = false;
 	}


Test patch:
diff --git a/packages/svelte/tests/html_equal.js b/packages/svelte/tests/html_equal.js
index 0ebf1fa6bd53..4c9e2a725332 100644
--- a/packages/svelte/tests/html_equal.js
+++ b/packages/svelte/tests/html_equal.js
@@ -86,7 +86,7 @@ export function normalize_html(
 		clean_children(node);
 		return node.innerHTML;
 	} catch (err) {
-		throw new Error(`Failed to normalize HTML:\n${html}`);
+		throw new Error(`Failed to normalize HTML:\n${html}\nCause: ${err}`);
 	}
 }
 
diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/_config.js b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/_config.js
index f0292274725e..4462f492fac9 100644
--- a/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/_config.js
+++ b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/_config.js
@@ -6,7 +6,7 @@ export default test({
 		dev: true
 	},
 
-	html: `<button>items: null</button> <div>x</div> <input type="checkbox" value="1"><input type="checkbox" value="2">`,
+	html: `<button>items: null</button> <div>x</div> <input type="checkbox" value="1"><input type="checkbox" value="2"><input>`,
 
 	test({ assert, target, warnings }) {
 		const btn = target.querySelector('button');
@@ -15,13 +15,13 @@ export default test({
 		flushSync(() => btn.click());
 		assert.htmlEqual(
 			target.innerHTML,
-			`<button>items: []</button> <div>x</div> <input type="checkbox" value="1"><input type="checkbox" value="2">`
+			`<button>items: []</button> <div>x</div> <input type="checkbox" value="1"><input type="checkbox" value="2"><input>`
 		);
 
 		flushSync(() => btn.click());
 		assert.htmlEqual(
 			target.innerHTML,
-			`<button>items: [0]</button> <div>x</div> <input type="checkbox" value="1"><input type="checkbox" value="2">`
+			`<button>items: [0]</button> <div>x</div> <input type="checkbox" value="1"><input type="checkbox" value="2"><input>`
 		);
 
 		const input = target.querySelector('input');
@@ -30,7 +30,7 @@ export default test({
 		flushSync(() => input.dispatchEvent(new Event('change', { bubbles: true })));
 
 		assert.deepEqual(warnings, [
-			'Assignment to `items` property (main.svelte:8:24) will evaluate to the right-hand side, not the value of `items` following the assignment. This may result in unexpected behaviour.'
+			'Assignment to `items` property (main.svelte:9:24) will evaluate to the right-hand side, not the value of `items` following the assignment. This may result in unexpected behaviour.'
 		]);
 	}
 });
diff --git a/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/main.svelte b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/main.svelte
index ad94c4e56e03..a79fe873b7e9 100644
--- a/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/main.svelte
+++ b/packages/svelte/tests/runtime-runes/samples/proxy-coercive-assignment-warning/main.svelte
@@ -3,6 +3,7 @@
 
 	let entries = $state([]);
 	let object = $state({ items: null, group: [] });
+	let elementFunBind = $state();
 </script>
 
 <button onclick={() => (object.items ??= []).push(object.items.length)}>
@@ -13,6 +14,12 @@
 <div bind:this={entries[0]}>x</div>
 <input type="checkbox" value=1 bind:group={object.group}>
 <input type="checkbox" value=2 bind:group={object.group}>
+
 <Test bind:this={entries[1]}></Test>
 <Test bind:this={() => entries[2], (v) => (entries[2] = v)}></Test>
 <Test bind:x={entries[3]}></Test>
+
+{#snippet funBind(context)}
+	<input bind:this={() => {}, (e) => (context.element = e)} />
+{/snippet}
+{@render funBind({ set element(e) { elementFunBind = e } })}