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=14532
This 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









ag0aep6g <anonymous example.com> 