Bug 18912 – [REG 2.080 git] "switch skips declaration" of foreach variable

Status
RESOLVED
Resolution
INVALID
Severity
regression
Priority
P1
Component
dmd
Product
D
Version
D2
Platform
x86_64
OS
Linux
Creation time
2018-05-27T11:35:10Z
Last change time
2018-05-27T20:59:33Z
Assigned to
No Owner
Creator
JR

Comments

Comment #0 by zorael — 2018-05-27T11:35:10Z
I have a function with a switch, with an inner foreach to generate cases based on a template parameters .tupleof. It still works in 2.080.0 and even with "dmd-nightly" on run.dlang.io, but not with dmd from git as of 180527. --- struct Foo { string abc, def; } void applyConfiguration(Thing)(ref Thing thing) { switch ("asdf") { foreach (immutable n, ref member; thing.tupleof) { enum memberstring = __traits(identifier, Thing.tupleof[n]); case memberstring: // ... break; } default: break; } } void main() { Foo foo; applyConfiguration(foo); } --- > switch.d(8): Error: `switch` skips declaration of variable `switch.applyConfiguration!(Foo).applyConfiguration.member` at switch.d(11) > switch.d(27): Error: template instance `switch.applyConfiguration!(Foo)` error instantiating The offending commit is eabc6a62b1d2f5924637f1e61464b9a975341dd4, "fix Issue 18858 - switch 'skips declaration' test only checks last declaration". Is this a regression or was my code always broken?
Comment #1 by ag0aep6g — 2018-05-27T19:37:45Z
(In reply to JR from comment #0) > Is this a regression or was my code always broken? As far as I see, the error is good, and your code should not be allowed. So I'm closing this as invalid. But please feel free to reopen if you disagree with my reasoning. Note that the compiler complains about the `member` variable, not `memberstring`. You're not using `member` in your code, so you don't see that it's broken, but it is. For example, you can try printing `member` in the case statement: ---- struct Foo { string abc, def; } void main() { Foo foo = Foo("hello", "world"); switch ("abc") { foreach (immutable n, ref member; foo.tupleof) { enum memberstring = __traits(identifier, Foo.tupleof[n]); case memberstring: import std.stdio; writeln(member); /* prints garbage and/or crashes */ break; } default: break; } } ---- You can use `static foreach` to get a compile-time loop without declaring a broken `member` variable: ---- struct Foo { string abc, def; } void main() { Foo foo = Foo("hello", "world"); sw: switch ("abc") { static foreach (immutable n; 0 .. foo.tupleof.length) {{ enum memberstring = __traits(identifier, Foo.tupleof[n]); case memberstring: import std.stdio; writeln(foo.tupleof[n]); /* Prints "hello". */ break sw; }} default: break; } } ----
Comment #2 by zorael — 2018-05-27T20:59:33Z
I see. The code was naturally heavily reduced. I have up until now been using the foreach without any issues, though mostly indexing the .tupleof directly to access the underlying symbol rather than using the member variable. I only use member once with std.traits.isType. https://github.com/zorael/kameloso/blob/f4617a5e5c796fcc9797e1f556b0861f44930f40/source/kameloso/config.d#L519 Slightly less reduced: --- thingloop: foreach (immutable i, thing; things) { switch (hits["entry"]) { foreach (immutable n, ref member; things[i].tupleof) { static if (!isType!member && !hasUDA!(Things[i].tupleof[n], Unconfigurable)) { enum memberstring = __traits(identifier, Things[i].tupleof[n]); case memberstring: things[i].setMemberByName(hits["entry"], hits["value"]); continue thingloop; } } default: // Unknown setting in known section invalidEntries[section] ~= hits["entry"].length ? hits["entry"] : line; break; } } --- Thanks, I will try your other approaches.