Repository: sveltejs/svelte (JavaScript)
Date: 2019-08-05 12:40:37+00:00
Task type: Bug Fix

Issue:
slot value not reactive when nested (regression)
**Describe the bug**
It seems a value from a " let:" on a component with slots doesn't trigger an update when used as a prop on a slot...  eg
```html
<WithColourPicker let:colour>
      <slot titlecolour={colour} />	
</WithColourPicker>
```
changes that "WithColourPicker" makes to "colour" aren't causing the slot to rerender with new titlecolour.

**To Reproduce**
Working:
https://svelte.dev/repl/991c974902464e119edc540fd18a99cd?version=3.6.1
Broken:
https://svelte.dev/repl/991c974902464e119edc540fd18a99cd?version=3.7.0


**Expected behavior**
Expected:
In the repl, expected behaviour is that the heading colour changes and the text changes its wording to include the selected colour.  (this can be seen in the 3.6.1 link)

What happens:
The text colour changes, but the wording doesn't change.

**Severity**
Svelte native uses nested slots like above in its rendering of List Views. This regression breaks svelte native :(

**Additional context**
Looking at generated code
in 3.7.0: 
```js
// ColouredTitle.svelte

const get_default_slot_changes = ({ colour }) => ({});
const get_default_slot_context = ({ colour }) => ({ titlecolour: colour });

p(changed, ctx) {
	if (default_slot && default_slot.p && changed.$$scope) {
		default_slot.p(get_slot_changes(default_slot_1, ctx, changed, get_default_slot_changes), get_slot_context(default_slot_1, ctx, get_default_slot_context));
 	}
}
```

whereas 3.6.1:
```js
//ColouredTitle.svelte

const get_default_slot_changes = ({ colour }) => ({ titlecolour: colour });
const get_default_slot_context = ({ colour }) => ({ titlecolour: colour });

if (default_slot && default_slot.p && (changed.$$scope || changed.colour)) {
    default_slot.p(get_slot_changes(default_slot_1, ctx, changed, get_default_slot_changes), get_slot_context(default_slot_1, ctx, get_default_slot_context));
}
```

Note that 3.6.1 considers changed.colour enough to update the slot where 3.7.0 doesn't consider this relevant.

**Diagnosis**

I think this happens because although the let variable is added to the dependencies for the InlineComponentWrapper https://github.com/sveltejs/svelte/commit/b2d9da3460f1794e342d7a0b0c04c32d8bbac6cd#diff-beaec73b844dfbe1d4a6ee6da477b182R165

They are ignored when processing the slot by this:
https://github.com/sveltejs/svelte/commit/b2d9da3460f1794e342d7a0b0c04c32d8bbac6cd#diff-5f78860f62364bc95ba513ae1fdfdc3dR88

But I am unsure of the correct fix






Issue discussion:

Solution patch:
diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts
--- a/src/compiler/compile/Component.ts
+++ b/src/compiler/compile/Component.ts
@@ -72,7 +72,7 @@ export default class Component {
 	stats: Stats;
 	warnings: Warning[];
 	ignores: Set<string>;
-	ignore_stack: Set<string>[] = [];
+	ignore_stack: Array<Set<string>> = [];
 
 	ast: Ast;
 	source: string;
diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts
--- a/src/compiler/compile/render_dom/wrappers/Slot.ts
+++ b/src/compiler/compile/render_dom/wrappers/Slot.ts
@@ -84,6 +84,7 @@ export default class SlotWrapper extends Wrapper {
 				});
 
 				const dynamic_dependencies = Array.from(attribute.dependencies).filter(name => {
+					if (this.node.scope.is_let(name)) return true;
 					const variable = renderer.component.var_lookup.get(name);
 					return is_dynamic(variable);
 				});
@@ -105,7 +106,7 @@ export default class SlotWrapper extends Wrapper {
 		}
 
 		const slot = block.get_unique_name(`${sanitize(slot_name)}_slot`);
-		const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot`);
+		const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`);
 
 		block.builders.init.add_block(deindent`
 			const ${slot_definition} = ctx.$$slots${quote_prop_if_necessary(slot_name)};
@@ -162,6 +163,7 @@ export default class SlotWrapper extends Wrapper {
 
 		const dynamic_dependencies = Array.from(this.dependencies).filter(name => {
 			if (name === '$$scope') return true;
+			if (this.node.scope.is_let(name)) return true;
 			const variable = renderer.component.var_lookup.get(name);
 			return is_dynamic(variable);
 		});
@@ -171,7 +173,10 @@ export default class SlotWrapper extends Wrapper {
 
 		block.builders.update.add_block(deindent`
 			if (${slot} && ${slot}.p && ${update_conditions}) {
-				${slot}.p(@get_slot_changes(${slot_definition}, ctx, changed, ${get_slot_changes}), @get_slot_context(${slot_definition}, ctx, ${get_slot_context}));
+				${slot}.p(
+					@get_slot_changes(${slot_definition}, ctx, changed, ${get_slot_changes}),
+					@get_slot_context(${slot_definition}, ctx, ${get_slot_context})
+				);
 			}
 		`);
 


Test patch:
diff --git a/test/runtime/samples/component-slot-let-f/A.svelte b/test/runtime/samples/component-slot-let-f/A.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/component-slot-let-f/A.svelte
@@ -0,0 +1,9 @@
+<script>
+	import B from './B.svelte';
+	export let x;
+</script>
+
+<B {x} let:reflected>
+	<span>{reflected}</span>
+	<slot {reflected} />
+</B>
\ No newline at end of file
diff --git a/test/runtime/samples/component-slot-let-f/B.svelte b/test/runtime/samples/component-slot-let-f/B.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/component-slot-let-f/B.svelte
@@ -0,0 +1,5 @@
+<script>
+	export let x;
+</script>
+
+<slot reflected={x}/>
\ No newline at end of file
diff --git a/test/runtime/samples/component-slot-let-f/_config.js b/test/runtime/samples/component-slot-let-f/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/component-slot-let-f/_config.js
@@ -0,0 +1,15 @@
+export default {
+	html: `
+		<span>1</span>
+		<span>1</span>
+	`,
+
+	async test({ assert, target, component }) {
+		component.x = 2;
+
+		assert.htmlEqual(target.innerHTML, `
+			<span>2</span>
+			<span>2</span>
+		`);
+	}
+};
diff --git a/test/runtime/samples/component-slot-let-f/main.svelte b/test/runtime/samples/component-slot-let-f/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/component-slot-let-f/main.svelte
@@ -0,0 +1,8 @@
+<script>
+	import A from './A.svelte';
+	export let x = 1;
+</script>
+
+<A {x} let:reflected>
+	<span>{reflected}</span>
+</A>
\ No newline at end of file