digitalmars.D.learn - Generating switch at Compile Time
- Jesse Phillips (69/69) Apr 13 2017 I realize that this is likely really pushing the compile time
- ag0aep6g (14/31) Apr 13 2017 [...]
- Jesse Phillips (6/18) Apr 17 2017 Yes it did.
- ag0aep6g (67/76) Apr 17 2017 It's really weird. I thought the loop would just not be unrolled at all....
- Stefan Koch (33/102) Apr 17 2017 This is what is generated by your example:
I realize that this is likely really pushing the compile time generation but a recent change to the switch statement[1] is surfacing because of this usage. uninitswitch2.d(13): Deprecation: 'switch' skips declaration of variable uninits witch2.main.li at uninitswitch2.d(14) --------------------- import std.traits; import std.typecons; import std.meta; private static immutable list = AliasSeq!( tuple("a", "q"), tuple("b", "r"), ); void main() { import std.stdio; string search; switch(search) { ---> foreach(li; list) { // li initialization is skipped mixin("case li[0]:"); mixin("writeln(li[1]);"); return; } default: break; } // Works mixin(genSwitch("search")); } --------------------- I realize I can build out the entire switch and mix it in: --------------------- string genSwitch(string search) { auto ans = "switch(" ~ search ~ ") {\n"; foreach(li; list) { ans ~= "case \"" ~ li[0] ~ "\":\n"; ans ~= "writeln(\"" ~ li[1] ~ "\");\n"; ans ~= "return;\n"; } ans ~= "default:\n"; ans ~= "break;\n"; ans ~= "}"; return ans; } --------------------- But I'm just wondering if the new initialization check should not be triggered from this utilization. --------------------- // Unrolled based on // https://wiki.dlang.org/User:Quickfur/Compile-time_vs._compile-time description version(none) void func2243(Tuple param0, Tuple param1) { { { case param0[0]: writeln(param0[1]); return; } { case param1[0]: writeln(param1[1]); return; } } } --------------------- Thoughts? 1. https://issues.dlang.org/show_bug.cgi?id=14532
Apr 13 2017
On 04/13/2017 11:06 PM, Jesse Phillips wrote:---------------------[...]private static immutable list = AliasSeq!( tuple("a", "q"), tuple("b", "r"), );[...]switch(search) { ---> foreach(li; list) { // li initialization is skipped mixin("case li[0]:"); mixin("writeln(li[1]);"); return; } default: break; }[...]} --------------------- Thoughts?That's not a static foreach. It's a normal run-time foreach. The switch jumps into the loop body without the loop head ever executing. The compiler is correct when it says that initialization of li is being skipped. Make `list` an enum or alias instead. Then the foreach is unrolled at compile time, you don't get a deprecation message, and it works correctly. By the way, in my opinion, `case li[0]:` shouldn't compile with the static immutable `list`. `list` and `li[0]` are dynamic values. The compiler only attempts (and succeeds) to evaluate them at compile time because they're typed as immutable. The way I see it, that only makes things more confusing.
Apr 13 2017
On Thursday, 13 April 2017 at 21:33:28 UTC, ag0aep6g wrote:That's not a static foreach. It's a normal run-time foreach. The switch jumps into the loop body without the loop head ever executing. The compiler is correct when it says that initialization of li is being skipped.Good good. I did miss that as I was trying different things.Make `list` an enum or alias instead. Then the foreach is unrolled at compile time, you don't get a deprecation message, and it works correctly.Yes it did.By the way, in my opinion, `case li[0]:` shouldn't compile with the static immutable `list`. `list` and `li[0]` are dynamic values. The compiler only attempts (and succeeds) to evaluate them at compile time because they're typed as immutable. The way I see it, that only makes things more confusing.This is very interesting. I wonder if the compiler is still unrolling the loop at compile time since it functions as expected; It certainly needs that deprecation though.
Apr 17 2017
On 04/17/2017 09:29 PM, Jesse Phillips wrote:On Thursday, 13 April 2017 at 21:33:28 UTC, ag0aep6g wrote:[...]It's really weird. I thought the loop would just not be unrolled at all. However, both `case`s seem to be generated as expected. So it behaves like a static foreach in that regard. But when you use `li` as a dynamic value (e.g. `writeln(li[1])`), it's suddenly empty. Seems that dmd can't decide what to do, so it does a little bit of both. Maybe I was wrong, and the loop in your code is a static foreach, but at some point there's a bug that makes dmd think it's dealing with run-time values. The behavior is also completely inconsistent. With ints, the program compiles and the assert passes: ---- alias AliasSeq(stuff ...) = stuff; immutable list = AliasSeq!(1, 2); void main() { switch(1) { foreach(li; list) { case li: enum e = li; assert(e == li); return; } default: } } ---- With strings, the program doesn't compile: ---- alias AliasSeq(stuff ...) = stuff; immutable list = AliasSeq!("foo", "bar"); void main() { switch("foo") { foreach(li; list) { case li: enum e = li; assert(e == li); return; /* "Error: case must be a string or an integral constant, not li" */ } default: } } ---- And with structs (your case), it compiles with a deprecation warning and behaves schizophrenically: ---- alias AliasSeq(stuff ...) = stuff; struct S { int x; } immutable list = AliasSeq!(S(1), S(2)); void main() { switch(1) /* Deprecation: 'switch' skips declaration of variable */ { foreach(li; list) { case li.x: enum e = li; assert(e == li); return; /* The assert fails. */ } default: } } ---- In my opinion, they should all simply be rejected. But I have no idea what's intended by the compiler writers. It's a mess. With enum/alias, they all compile and work as expected, of course.By the way, in my opinion, `case li[0]:` shouldn't compile with the static immutable `list`. `list` and `li[0]` are dynamic values. The compiler only attempts (and succeeds) to evaluate them at compile time because they're typed as immutable. The way I see it, that only makes things more confusing.This is very interesting. I wonder if the compiler is still unrolling the loop at compile time since it functions as expected; It certainly needs that deprecation though.
Apr 17 2017
On Thursday, 13 April 2017 at 21:06:52 UTC, Jesse Phillips wrote:I realize that this is likely really pushing the compile time generation but a recent change to the switch statement[1] is surfacing because of this usage. uninitswitch2.d(13): Deprecation: 'switch' skips declaration of variable uninits witch2.main.li at uninitswitch2.d(14) --------------------- import std.traits; import std.typecons; import std.meta; private static immutable list = AliasSeq!( tuple("a", "q"), tuple("b", "r"), ); void main() { import std.stdio; string search; switch(search) { ---> foreach(li; list) { // li initialization is skipped mixin("case li[0]:"); mixin("writeln(li[1]);"); return; } default: break; } // Works mixin(genSwitch("search")); } --------------------- I realize I can build out the entire switch and mix it in: --------------------- string genSwitch(string search) { auto ans = "switch(" ~ search ~ ") {\n"; foreach(li; list) { ans ~= "case \"" ~ li[0] ~ "\":\n"; ans ~= "writeln(\"" ~ li[1] ~ "\");\n"; ans ~= "return;\n"; } ans ~= "default:\n"; ans ~= "break;\n"; ans ~= "}"; return ans; } --------------------- But I'm just wondering if the new initialization check should not be triggered from this utilization. --------------------- // Unrolled based on // https://wiki.dlang.org/User:Quickfur/Compile-time_vs._compile-time description version(none) void func2243(Tuple param0, Tuple param1) { { { case param0[0]: writeln(param0[1]); return; } { case param1[0]: writeln(param1[1]); return; } } } --------------------- Thoughts? 1. https://issues.dlang.org/show_bug.cgi?id=14532This is what is generated by your example: switch (search) { unrolled { { // does not actually open a new scope immutable immutable(Tuple!(string, string)) li = __list_field_0; case "a": { } writeln(li.__expand_field_1); return 0; } { // same here we do not actually open a new scope. immutable immutable(Tuple!(string, string)) li = __list_field_1; case "b": { } writeln(li.__expand_field_1); return 0; } } default: { break; } } return 0; it should be clear to sww why li's initialisation is referred to as skipped.
Apr 17 2017