D issues are now tracked on GitHub. This Bugzilla instance remains as a read-only archive.
Issue 7835 - switch case fallthrough error despite a break inside static foreach
Summary: switch case fallthrough error despite a break inside static foreach
Status: RESOLVED INVALID
Alias: None
Product: D
Classification: Unclassified
Component: dmd (show other issues)
Version: D2
Hardware: x86 Windows
: P2 normal
Assignee: No Owner
URL:
Keywords: diagnostic
Depends on:
Blocks:
 
Reported: 2012-04-05 17:40 UTC by bearophile_hugs
Modified: 2016-09-21 17:27 UTC (History)
5 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description bearophile_hugs 2012-04-05 17:40:12 UTC
This D2 program compiles with no warnings or errors with DMD 2.059beta:


import core.stdc.stdio: printf;
template TypeTuple(TList...) {
    alias TList TypeTuple;
}
void main() {
    char c = 'b';
    switch (c) {
        case 'a': printf("1 a\n"); break;
        foreach (o; TypeTuple!('b', 'c')) {
            case o: printf("2 %c\n", c); break;
        }
        default: printf("default");
    }
}



But it runs in a wrong way, as you see:

...>dmd -run test.d
2 b
default

...>dmd -w -run test.d
test.d(12): Error: switch case fallthrough - use 'goto default;' if intended


So the break inside the static foreach is ignored.

(This idiom of using a static foreach inside a switch is handy to generate switch cases.)



Note: adding a second break, like this, doesn't improve the situation:

import core.stdc.stdio: printf;
template TypeTuple(TList...) {
    alias TList TypeTuple;
}
void main() {
    char c = 'b';
    switch (c) {
        case 'a': printf("1 a\n"); break;
        foreach (o; TypeTuple!('b', 'c')) {
            case o: printf("2 %c\n", c); break; break;
        }
        default: printf("default");
    }
}


test.d(10): Warning: statement is not reachable
test.d(10): Warning: statement is not reachable
test.d(12): Error: switch case fallthrough - use 'goto default;' if intended
Comment 1 Dmitry Olshansky 2012-04-06 07:15:55 UTC
(In reply to comment #0)
> This D2 program compiles with no warnings or errors with DMD 2.059beta:
> 
> 
> import core.stdc.stdio: printf;
> template TypeTuple(TList...) {
>     alias TList TypeTuple;
> }
> void main() {
>     char c = 'b';

L_MySwitch:
>     switch (c) {
>         case 'a': printf("1 a\n"); break;
>         foreach (o; TypeTuple!('b', 'c')) {
>             case o: printf("2 %c\n", c); break L_MySwitch;
>         }
>         default: printf("default");
>     }
> }
> 
> 

For these cases I recommend to use labeled breaks 
so that it's more clear for humans and compiler alike.


> 
> But it runs in a wrong way, as you see:
> 
> ...>dmd -run test.d
> 2 b
> default
> 
> ...>dmd -w -run test.d
> test.d(12): Error: switch case fallthrough - use 'goto default;' if intended
> 
> 
> So the break inside the static foreach is ignored.
> 
> (This idiom of using a static foreach inside a switch is handy to generate
> switch cases.)
> 
> 
> 
> Note: adding a second break, like this, doesn't improve the situation:
> 
> import core.stdc.stdio: printf;
> template TypeTuple(TList...) {
>     alias TList TypeTuple;
> }
> void main() {
>     char c = 'b';
>     switch (c) {
>         case 'a': printf("1 a\n"); break;
>         foreach (o; TypeTuple!('b', 'c')) {
>             case o: printf("2 %c\n", c); break; break;
>         }
>         default: printf("default");
>     }
> }

It can't help because the second break is by definition unreachable.
Comment 2 timon.gehr 2012-04-06 07:38:55 UTC
Not a bug. break applies to the innermost statement that can be broken out from. This includes foreach.

(I use this idiom often. Use labeled break to break from the switch.)

Please reopen as enhancement if you think the code should be illegal.
Comment 3 bearophile_hugs 2012-04-06 10:06:56 UTC
(In reply to comment #2)
> Not a bug. break applies to the innermost statement that can be broken out
> from. This includes foreach.
> 
> (I use this idiom often. Use labeled break to break from the switch.)

You are right, thank you.
(My error was to think that "static foreach" doesn't support break.)
Comment 4 bearophile_hugs 2012-04-06 10:18:05 UTC
Reopened, because you have missed the error message in my bug report.

Using a labeled break:


import core.stdc.stdio: printf;
template TypeTuple(TList...) {
    alias TList TypeTuple;
}
void main() {
    char c = 'b';
    MySwitch: switch (c) {
        case 'a': printf("1 a\n"); break;
        foreach (o; TypeTuple!('b', 'c')) {
            case o: printf("2 %c\n", c); break MySwitch;
        }
        default: printf("default");
    }
}


DMD 2.059 beta gives (compiling with -w):

test.d(12): Error: switch case fallthrough - use 'goto default;' if intended

I have also changed the issue title to better reflect the problem, now the Keywords is 'diagnostic' because it's giving a warning where there is nothing to warn against.
Comment 5 bearophile_hugs 2012-04-06 11:32:39 UTC
This compiles with no warnings and it seems to work correctly, but I don't fully understand it:


import core.stdc.stdio: printf;
template TypeTuple(TList...) {
    alias TList TypeTuple;
}
void main() {
    char c = 'b';
    MySwitch: switch (c) {
        case 'a': printf("1 a\n"); break;
        foreach (o; TypeTuple!('b', 'c')) {
            case o: printf("2 %c\n", c); break;
        }
        break;
        default: printf("default");
    }
}


Is it correct? if the break inside here is meant to be the foreach break:
{ case o: printf("2 %c\n", c); break; }

Then why a single break is enough after:

        foreach (o; TypeTuple!('b', 'c')) {
            case o: printf("2 %c\n", c); break;
        }
        break;

despite the foreach synthesizes more than one switch case?
Comment 6 Dmitry Olshansky 2012-04-06 11:37:01 UTC
(In reply to comment #5)
> This compiles with no warnings and it seems to work correctly, but I don't
> fully understand it:
> 
> 
> import core.stdc.stdio: printf;
> template TypeTuple(TList...) {
>     alias TList TypeTuple;
> }
> void main() {
>     char c = 'b';
>     MySwitch: switch (c) {
>         case 'a': printf("1 a\n"); break;
>         foreach (o; TypeTuple!('b', 'c')) {
>             case o: printf("2 %c\n", c); break;
>         }
>         break;
>         default: printf("default");
>     }
> }
> 
> 
> Is it correct? if the break inside here is meant to be the foreach break:

Yes.

> { case o: printf("2 %c\n", c); break; }
No it's {case 0: printf("2 %c\n", c); }

the break did his job already, it can't work twice.


> 
> Then why a single break is enough after:
> 
>         foreach (o; TypeTuple!('b', 'c')) {
>             case o: printf("2 %c\n", c); break;
>         }

Then the code below gives you one break after that statement.
>         break;
> 
> despite the foreach synthesizes more than one switch case?

foreach synthesizes exactly one statement here.
Comment 7 timon.gehr 2012-04-06 11:41:49 UTC
(In reply to comment #5)
> This compiles with no warnings and it seems to work correctly, but I don't
> fully understand it:
> 
> 
> import core.stdc.stdio: printf;
> template TypeTuple(TList...) {
>     alias TList TypeTuple;
> }
> void main() {
>     char c = 'b';
>     MySwitch: switch (c) {
>         case 'a': printf("1 a\n"); break;
>         foreach (o; TypeTuple!('b', 'c')) {
>             case o: printf("2 %c\n", c); break;
>         }
>         break;
>         default: printf("default");
>     }
> }
> 
> 
> Is it correct? if the break inside here is meant to be the foreach break:
> { case o: printf("2 %c\n", c); break; }
> 
> Then why a single break is enough after:
> 
>         foreach (o; TypeTuple!('b', 'c')) {
>             case o: printf("2 %c\n", c); break;
>         }
>         break;
> 
> despite the foreach synthesizes more than one switch case?

Your code is expanded to:

void main() {
    char c = 'b';
    switch (c) {
        case 'a': printf("1 a\n"); break;
        {case 'b': printf("2 %c\n", c); goto break_foreach;}
        {case 'c': printf("2 %c\n", c); goto break_foreach;}
        break_foreach: break;
        default: printf("default");
    }
}
Comment 8 bearophile_hugs 2012-04-06 13:12:01 UTC
(In reply to comment #7)

> Your code is expanded to:
> 
> void main() {
>     char c = 'b';
>     switch (c) {
>         case 'a': printf("1 a\n"); break;
>         {case 'b': printf("2 %c\n", c); goto break_foreach;}
>         {case 'c': printf("2 %c\n", c); goto break_foreach;}
>         break_foreach: break;
>         default: printf("default");
>     }
> }

Thank you again Timon :-) So there is no bug here.

This was not easy to understand for me. (Maybe D newbies will enjoy to read an example of this in some D tips&trickls somewhere, or maybe it was just a conceptualization problem of mine.)

Issue closed again, as invalid.
Comment 9 Brad Roberts 2012-04-06 15:11:51 UTC
I think that the expansion of the static foreach is wrong.  It explains the behavior, but doesn't excuse it.

I think the bug report is valid.
Comment 10 timon.gehr 2012-04-06 15:21:47 UTC
(In reply to comment #9)
> I think that the expansion of the static foreach is wrong.  It explains the
> behavior, but doesn't excuse it.
> 
> I think the bug report is valid.

What would be your expected behavior?
Comment 11 Alex Parrill 2015-05-12 16:00:16 UTC
This also happens if you use return instead of break in the foreach loop, which should be valid (since return doesn't care about loops):

import std.stdio;
import std.typetuple;

alias SwitchCases = TypeTuple!("a", "b", "c");

int main() {
	string s = "a";
	
	switch(s) {
		case "special":
			writeln("Special case!");
			return 0;
		
		foreach(c; SwitchCases) {
			case c:
				writeln(c);
				return 1;
		}
		
		default:
			writeln("default case");
			return 2;
	}
}

$ rdmd -w ~/test.d
/home/col/test.d(21): Warning: switch case fallthrough - use 'goto default;' if intended

The switch works properly even if you ignore the warning, and moving the special case to after the foreach loop removes the warning.
Comment 12 Mathias Lang 2016-09-21 17:27:28 UTC
I think the bug report from c4 and c11 is actually a diagnostic issue: https://issues.dlang.org/show_bug.cgi?id=7390

The original motivation for this bug report was actually not a bug. It has been proposed as an ER by Martin here: https://issues.dlang.org/show_bug.cgi?id=14887

So, since both the ER and the bug found are reported, I'll close this again. Feel free to direct any further discussion to the bug report or the ER, or reopen if I missed something.