Repository: sveltejs/svelte (JavaScript)

Issue:
Error when binding to a prop that is also an exported identifier
### Describe the bug

If you have a component like this you cannot bind the prop open

```svelte
<script lang="ts">
    let {open: is_open = $bindable(false)}: Props = $props();
    export const open = ()=>{
        console.log("open export called");
    }
</script>

<div>AAA</div>
```

We should be able to bind to the open `prop` even if we cannot bind to the `open` export

### Reproduction

Remove the comment to see the error

https://svelte.dev/playground/untitled?version=5.15.0#H4sIAAAAAAAACo1R0WrDMAz8FU0MmkLSvKdJINsP7H0ew2nUzODKJnZLR8i_D6XNWMsGw2AknXTWnUdkfSAs8MlwZ7gHzR3Q2bshQnvsMcW9sRSweB0xfnrplAKmy1zj_SacyEaptTrQb_Wd40gcAxZYht1gfASrua8UxqCwVgxgDvObzx_GdrAf3AFWm3zOrjSrrWIVLUVwnhgqeAxRR0r22gZabxWX-YW6Vqy4vBBJazXKPeW14vIhy-AKtYa74gaHLKsxxUjniEUcjjSlf4j-udet7DvkH8IBRNO8QwEmvC_qZD_d2kXgVMDL4HwQyEuQiGYZv_7WznH49iZZV_UofskRxFnaWNcnCueOZUZbS53ChWq6d7Ezp7ppmjKX4Nabt-kLXnLCnjwCAAA=

### Logs

```shell
bind_invalid_export Component Child.svelte has an export named `open` that a consumer component is trying to access using `bind:open`, which is disallowed. Instead, use `bind:this` (e.g. `<Child bind:this={component} />`) and then access the property on the bound component instance (e.g. `component.open`) https://svelte.dev/e/bind_invalid_export
```


### System Info

```shell
Does not matter
```


### Severity

annoyance


Discussion:
Is there a strong reason to *not* choose a different name for the property or the export?  Why must they be named the same?
No, in this case it was a modal component that allows setting and optionally binding an `open` prop and also exports an `open` method.

I don't think there is any reason to disallow this as you cannot use the prop in place of the method or the method in place of the prop.

It is also unexpected behavior as it works when passing the prop without binding it but the moment you bind it it throws.



Patch:
diff --git a/.changeset/tidy-dragons-thank.md b/.changeset/tidy-dragons-thank.md
new file mode 100644
index 000000000000..1257ec02101e
--- /dev/null
+++ b/.changeset/tidy-dragons-thank.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: don't throw `bind_invalid_export` if there's also a bindable prop with the same name
diff --git a/packages/svelte/src/internal/client/validate.js b/packages/svelte/src/internal/client/validate.js
index 951feee33bdf..417c145eb2ec 100644
--- a/packages/svelte/src/internal/client/validate.js
+++ b/packages/svelte/src/internal/client/validate.js
@@ -50,7 +50,7 @@ export function validate_prop_bindings($$props, bindable, exports, component) {
 		var name = component.name;
 
 		if (setter) {
-			if (exports.includes(key)) {
+			if (exports.includes(key) && !bindable.includes(key)) {
 				e.bind_invalid_export(component[FILENAME], key, name);
 			}
 


Test patch:
diff --git a/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/Component.svelte b/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/Component.svelte
new file mode 100644
index 000000000000..17e895fd8cba
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/Component.svelte
@@ -0,0 +1,9 @@
+<script>
+	let { open: is_open = $bindable() } = $props();
+
+	export function open(){
+		is_open = !is_open;
+	}
+</script>
+
+<button onclick={open}>{is_open}</button>
\ No newline at end of file
diff --git a/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/_config.js b/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/_config.js
new file mode 100644
index 000000000000..45cd58f55c53
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/_config.js
@@ -0,0 +1,35 @@
+import { ok, test } from '../../test';
+import { flushSync } from 'svelte';
+
+export default test({
+	compileOptions: {
+		dev: true
+	},
+	html: `<button>true</button><button>true</button><input type="checkbox" />`,
+	ssrHtml: `<button>true</button><button>true</button><input type="checkbox" checked=""/>`,
+
+	async test({ assert, target, instance }) {
+		const [btn1, btn2] = target.querySelectorAll('button');
+		const input = target.querySelector('input');
+		flushSync(() => {
+			btn1.click();
+		});
+		assert.equal(btn1.innerHTML, 'false');
+		assert.equal(btn2.innerHTML, 'false');
+		assert.equal(input?.checked, false);
+
+		flushSync(() => {
+			btn2.click();
+		});
+		assert.equal(btn1.innerHTML, 'true');
+		assert.equal(btn2.innerHTML, 'true');
+		assert.equal(input?.checked, true);
+
+		flushSync(() => {
+			input?.click();
+		});
+		assert.equal(btn1.innerHTML, 'false');
+		assert.equal(btn2.innerHTML, 'false');
+		assert.equal(input?.checked, false);
+	}
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/main.svelte b/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/main.svelte
new file mode 100644
index 000000000000..3b47eef156ad
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/bindable-prop-and-export/main.svelte
@@ -0,0 +1,14 @@
+<script>
+	import Component from "./Component.svelte";
+
+	let open = $state(true);
+	let comp;
+</script>
+
+<Component bind:this={comp} bind:open />
+
+<button onclick={()=>{
+	comp.open();
+}}>{open}</button>
+
+<input type="checkbox" bind:checked={open} />
\ No newline at end of file