Repository: sveltejs/svelte (JavaScript)
Date: 2019-10-13 06:16:05+00:00
Task type: Bug Fix

Issue:
Compile error: Nested loops with index broke after 3.9.1
**Describe the bug**
You can see the exact same weird parsing behavior in the example below

**Logs**
Generated code is wrong, for instance when using `{@debug paneIndex}` it generates this:
![image](https://user-images.githubusercontent.com/181384/65169506-f422d480-da46-11e9-9412-0f6068fbd27b.png)
(`paneIndex` is the index from `{#each ... }` loop)

**To Reproduce**
https://svelte.dev/repl/af7ece0c29f240ad97bf795bd3be1347?version=3.9.1

- Remove debug, it compiles properly
- Remove the inner loop, and it compiles properly. 
- Debug is not at fault here, because in my project, the `index` is no longer passed onto nested loops which breaks my code.

**Expected behavior**
- Proper compilation without errors

**Stacktraces**
- No errors from the compiler, only compiled code gives an error

**Information about your Svelte project:**
- Svelte version 3.9.1 compared to every single version above

**Severity**
- Medium, project is live with over 100+ daily CCU. Having smooth animations is very important in my project. I would've loved to have latest transition/animations fixes which currently look a bit janky in 3.9.1, this compiler issue prevents me from upgrading.


Issue discussion:
@Xerios, the code from the REPL builds and works under **3.12.1** when building for production via `npm run build` and `npm run start` so I think it really is debugging/dev codegen that is causing the issue. The `if ()` is still the culprit, as you said, though.

Turns out my issue was some sort of race condition.

After diving into the generated code I noticed this: 
```js
function create_each_block$5(ctx) {
  // ctx.data has 0 elements, thus if_block is not created
  var if_block = ctx.data[ctx.paneIndex] && create_if_block_1$4(ctx);
  // ...
  const block = {
    p: function update(changed, new_ctx) {
      // ctx.data has one element, but since if_block wasn't created, it gives an error
      if (ctx.data[ctx.paneIndex]) if_block.p(changed, ctx);
    }
  }
}
```
This bug is still present with the changes in the `code-red` branch, but manifests as a compile-time exception rather than outputting invalid code.

Solution patch:
diff --git a/src/compiler/compile/render_dom/wrappers/DebugTag.ts b/src/compiler/compile/render_dom/wrappers/DebugTag.ts
--- a/src/compiler/compile/render_dom/wrappers/DebugTag.ts
+++ b/src/compiler/compile/render_dom/wrappers/DebugTag.ts
@@ -59,21 +59,19 @@ export default class DebugTagWrapper extends Wrapper {
 				.join(', ');
 			const logged_identifiers = this.node.expressions.map(e => e.node.name).join(', ');
 
-			block.builders.update.add_block(deindent`
-				if (${condition}) {
-					const { ${ctx_identifiers} } = ctx;
-					@_console.${log}({ ${logged_identifiers} });
-					debugger;
-				}
-			`);
+			const debugStatements = deindent`
+			{
+				const { ${ctx_identifiers} } = ctx;
+				@_console.${log}({ ${logged_identifiers} });
+				debugger;
+			}
+			`;
 
-			block.builders.create.add_block(deindent`
-				{
-					const { ${ctx_identifiers} } = ctx;
-					@_console.${log}({ ${logged_identifiers} });
-					debugger;
-				}
-			`);
+			block.builders.update.add_block(condition ? deindent`
+				if (${condition}) ${debugStatements}
+			` : debugStatements);
+
+			block.builders.create.add_block(debugStatements);
 		}
 	}
 }


Test patch:
diff --git a/test/js/samples/debug-no-dependencies/_config.js b/test/js/samples/debug-no-dependencies/_config.js
new file mode 100644
--- /dev/null
+++ b/test/js/samples/debug-no-dependencies/_config.js
@@ -0,0 +1,5 @@
+export default {
+	options: {
+		dev: true
+	}
+};
\ No newline at end of file
diff --git a/test/js/samples/debug-no-dependencies/expected.js b/test/js/samples/debug-no-dependencies/expected.js
new file mode 100644
--- /dev/null
+++ b/test/js/samples/debug-no-dependencies/expected.js
@@ -0,0 +1,144 @@
+/* generated by Svelte vX.Y.Z */
+import {
+	SvelteComponentDev,
+	destroy_each,
+	detach_dev,
+	dispatch_dev,
+	empty,
+	init,
+	insert_dev,
+	noop,
+	safe_not_equal,
+	space,
+	text
+} from "svelte/internal";
+
+const file = undefined;
+
+function get_each_context(ctx, list, i) {
+	const child_ctx = Object.create(ctx);
+	child_ctx.thing = list[i];
+	child_ctx.index = i;
+	return child_ctx;
+}
+
+// (4:0) {#each things as thing, index}
+function create_each_block(ctx) {
+	var t0, t1_value = ctx.thing + "", t1;
+
+	const block = {
+		c: function create() {
+			{
+				const { index } = ctx;
+				console.log({ index });
+				debugger;
+			}
+
+			t0 = space();
+			t1 = text(t1_value);
+		},
+
+		m: function mount(target, anchor) {
+			insert_dev(target, t0, anchor);
+			insert_dev(target, t1, anchor);
+		},
+
+		p: function update(changed, ctx) {
+			{
+				const { index } = ctx;
+				console.log({ index });
+				debugger;
+			}
+		},
+
+		d: function destroy(detaching) {
+			if (detaching) {
+				detach_dev(t0);
+				detach_dev(t1);
+			}
+		}
+	};
+	dispatch_dev("SvelteRegisterBlock", { block, id: create_each_block.name, type: "each", source: "(4:0) {#each things as thing, index}", ctx });
+	return block;
+}
+
+function create_fragment(ctx) {
+	var each_1_anchor;
+
+	let each_value = things;
+
+	let each_blocks = [];
+
+	for (let i = 0; i < each_value.length; i += 1) {
+		each_blocks[i] = create_each_block(get_each_context(ctx, each_value, i));
+	}
+
+	const block = {
+		c: function create() {
+			for (let i = 0; i < each_blocks.length; i += 1) {
+				each_blocks[i].c();
+			}
+
+			each_1_anchor = empty();
+		},
+
+		l: function claim(nodes) {
+			throw new Error("options.hydrate only works if the component was compiled with the `hydratable: true` option");
+		},
+
+		m: function mount(target, anchor) {
+			for (let i = 0; i < each_blocks.length; i += 1) {
+				each_blocks[i].m(target, anchor);
+			}
+
+			insert_dev(target, each_1_anchor, anchor);
+		},
+
+		p: function update(changed, ctx) {
+			if (changed.things) {
+				each_value = things;
+
+				let i;
+				for (i = 0; i < each_value.length; i += 1) {
+					const child_ctx = get_each_context(ctx, each_value, i);
+
+					if (each_blocks[i]) {
+						each_blocks[i].p(changed, child_ctx);
+					} else {
+						each_blocks[i] = create_each_block(child_ctx);
+						each_blocks[i].c();
+						each_blocks[i].m(each_1_anchor.parentNode, each_1_anchor);
+					}
+				}
+
+				for (; i < each_blocks.length; i += 1) {
+					each_blocks[i].d(1);
+				}
+				each_blocks.length = each_value.length;
+			}
+		},
+
+		i: noop,
+		o: noop,
+
+		d: function destroy(detaching) {
+			destroy_each(each_blocks, detaching);
+
+			if (detaching) {
+				detach_dev(each_1_anchor);
+			}
+		}
+	};
+	dispatch_dev("SvelteRegisterBlock", { block, id: create_fragment.name, type: "component", source: "", ctx });
+	return block;
+}
+
+class Component extends SvelteComponentDev {
+	constructor(options) {
+		super(options);
+		init(this, options, null, create_fragment, safe_not_equal, []);
+		dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "Component", options, id: create_fragment.name });
+	}
+}
+
+export default Component;
\ No newline at end of file
diff --git a/test/js/samples/debug-no-dependencies/input.svelte b/test/js/samples/debug-no-dependencies/input.svelte
new file mode 100644
--- /dev/null
+++ b/test/js/samples/debug-no-dependencies/input.svelte
@@ -0,0 +1,7 @@
+<script>
+</script>
+
+{#each things as thing, index}
+	{@debug index}
+	{thing}
+{/each}
\ No newline at end of file