Thread overview
D's SwitchStatement accepts statements with ridiculous semantics
Sep 29, 2017
Don Clugston
Sep 29, 2017
Dukc
Sep 30, 2017
sarn
Sep 30, 2017
drug
Sep 29, 2017
Timon Gehr
Sep 29, 2017
Don Clugston
Sep 29, 2017
Timon Gehr
Sep 30, 2017
Jacob Carlborg
Sep 29, 2017
H. S. Teoh
Sep 30, 2017
user1234
September 29, 2017
Guess what this prints

----
import std.stdio;

void main()
{
  int i = 0;

  switch (i) for (i = 8; i < 10; ++i)
  {
    case 7:
        writeln(i);
        return;

    default: ;
  }
}
----


Why does this even compile? It's because the grammar is:

SwitchStatement:
    switch ( Expression ) ScopeStatement


and ScopeStatement allows almost anything.
I think the only sane grammar is

SwitchStatement:
    switch ( Expression ) BlockStatement

Initially I thought ScopeStatement was accepted in order to enable Duff's device, but it isn't necessary for that. It might have originally accepted ScopeStatement to support

E e; switch( e ) with (E) { .... }

Or it may have just been an accident.
But regardless of the original motivation, it allows some truly dreadful semantics.
Can we disallow this silliness please?

September 29, 2017
On Friday, 29 September 2017 at 09:12:54 UTC, Don Clugston wrote:
> Guess what this prints

My guess is it prints "1".

By "guess" I mean it, I did not test! Anyway reminds me a lot of very badly used gotos.

September 29, 2017
On 29.09.2017 11:12, Don Clugston wrote:
> Guess what this prints
> 
> ----
> import std.stdio;
> 
> void main()
> {
>    int i = 0;
> 
>    switch (i) for (i = 8; i < 10; ++i)
>    {
>      case 7:
>          writeln(i);
>          return;
> 
>      default: ;
>    }
> }
> ----
> 
> 
> Why does this even compile? It's because the grammar is:
> 
> SwitchStatement:
>      switch ( Expression ) ScopeStatement
> 
> 
> and ScopeStatement allows almost anything.
> I think the only sane grammar is
> 
> SwitchStatement:
>      switch ( Expression ) BlockStatement
> 
> Initially I thought ScopeStatement was accepted in order to enable Duff's device, but it isn't necessary for that. It might have originally accepted ScopeStatement to support
> 
> E e; switch( e ) with (E) { .... }
> 
> Or it may have just been an accident.

It is very likely that this part of the grammar was deliberately copied from C. It's also consistent with how all other control flow constructs are parsed.

> But regardless of the original motivation, it allows some truly dreadful semantics.

I don't see what your proposed grammar change accomplishes:

switch(i){
    for(i=8;i<10;++i){
        case 7:
            writeln(i);
            return;
        default:{}
    }
}

I.e., you seem to have misidentified the culprit. Whether or not to the curly braces are required by the parser has nothing to do with switch semantics.

> Can we disallow this silliness please?
> 

Maybe this specific case can be disallowed during semantic (but I don't really see why it helps, it mostly just makes the language definition more complex).
September 29, 2017
On Friday, 29 September 2017 at 10:32:02 UTC, Timon Gehr wrote:
> On 29.09.2017 11:12, Don Clugston wrote:
>> Guess what this prints
>> 
>> ----
>> import std.stdio;
>> 
>> void main()
>> {
>>    int i = 0;
>> 
>>    switch (i) for (i = 8; i < 10; ++i)
>>    {
>>      case 7:
>>          writeln(i);
>>          return;
>> 
>>      default: ;
>>    }
>> }
>> ----
>> 
>> 
>> Why does this even compile? It's because the grammar is:
>> 
>> SwitchStatement:
>>      switch ( Expression ) ScopeStatement
>> 
>> 
>> and ScopeStatement allows almost anything.
>> I think the only sane grammar is
>> 
>> SwitchStatement:
>>      switch ( Expression ) BlockStatement
>> 
>> Initially I thought ScopeStatement was accepted in order to enable Duff's device, but it isn't necessary for that. It might have originally accepted ScopeStatement to support
>> 
>> E e; switch( e ) with (E) { .... }
>> 
>> Or it may have just been an accident.
>
> It is very likely that this part of the grammar was deliberately copied from C. It's also consistent with how all other control flow constructs are parsed.
>
>> But regardless of the original motivation, it allows some truly dreadful semantics.
>
> I don't see what your proposed grammar change accomplishes:
>
> switch(i){
>     for(i=8;i<10;++i){
>         case 7:
>             writeln(i);
>             return;
>         default:{}
>     }
> }
>
> I.e., you seem to have misidentified the culprit. Whether or not to the curly braces are required by the parser has nothing to do with switch semantics.

That case looks quite different to me.
It's rather more obvious that the `for` statement has been skipped.

The problem I have with the original example is that it looks as though the body of the `for` loop is the body of the switch statement.

>
>> Can we disallow this silliness please?
>> 
>
> Maybe this specific case can be disallowed during semantic (but I don't really see why it helps, it mostly just makes the language definition more complex).

I believe it makes it simpler. You cannot avoid the reference to BlockStatement.
Note that:  "A switch statement must have a default statement."

This is only possible only in two situations.

1. Silly degenerate case.
     switch (i) default: ;

2. A statement which contains a BlockStatement.

It accepts unreachable code.

  switch (i) if ( foo() ) {} else { default: ; }

A switch statement followed by anything other than a BlockStatement is *always* wrong. Always.

We improved the switch statement a lot by disallowing implicit fallthrough. But it still allows other nonsense that was presumably inherited from the very early days of K&R C.

This example just struck me as exceedingly silly, and quite misleading.


September 29, 2017
On Fri, Sep 29, 2017 at 12:32:02PM +0200, Timon Gehr via Digitalmars-d wrote: [...]
> It is very likely that this part of the grammar was deliberately copied from C. It's also consistent with how all other control flow constructs are parsed.

I believe one of the reasons the grammar was written this way was to allow Duff's device in D.  Personally, I don't care much for it -- an optimizing compiler ought to be able to generate the best code without requiring the programmer to explicitly write Duff's device.  Though Walter did indicate that dmd's optimizer is lacking in the loop unrolling department.


T

-- 
It is not the employer who pays the wages. Employers only handle the money. It is the customer who pays the wages. -- Henry Ford
September 29, 2017
On 29.09.2017 17:05, Don Clugston wrote:
>>
>> I don't see what your proposed grammar change accomplishes:
>>
>> switch(i){
>>     for(i=8;i<10;++i){
>>         case 7:
>>             writeln(i);
>>             return;
>>         default:{}
>>     }
>> }
>>
>> I.e., you seem to have misidentified the culprit. Whether or not to the curly braces are required by the parser has nothing to do with switch semantics.
> 
> That case looks quite different to me.
> It's rather more obvious that the `for` statement has been skipped.
> ...

Oh, I see. (They look the same to me.)

> The problem I have with the original example is that it looks as though the body of the `for` loop is the body of the switch statement.
> ...

This can be done with basically any control flow construct we have:

if(i) for(i=8;i<10;++i)
{
    // ...
}

(This relates to my point about consistency.)

>>
>>> Can we disallow this silliness please?
>>>
>>
>> Maybe this specific case can be disallowed during semantic (but I don't really see why it helps, it mostly just makes the language definition more complex).
> 
> I believe it makes it simpler. You cannot avoid the reference to BlockStatement.

I don't see why. For example, my compiler implementation of SwitchStm does not mention BlockStm.

> Note that:  "A switch statement must have a default statement."
> 
> This is only possible only in two situations.
> 
> 1. Silly degenerate case.
>       switch (i) default: ;
> 
> 2. A statement which contains a BlockStatement.
> 
> It accepts unreachable code.
> 
>    switch (i) if ( foo() ) {} else { default: ; }
> 
> A switch statement followed by anything other than a BlockStatement is *always* wrong. Always.
> ...

Well, I have used the switch(...) with(...) idiom you mentioned in the original post a few times, and I'm quite confident you'd meet some opposition if you were to break it.

There's also this case:

switch(x)
    static foreach(i;0..10)
        static if(i==0) default: return f!0();
        else case i: return f!i();

and, of course, this one:

final switch(x)
    static foreach(i;0..10)
        case i: return f!i();

> We improved the switch statement a lot by disallowing implicit fallthrough.

Explicit fallthrough is still allowed though.

> But it still allows other nonsense that was presumably inherited from the very early days of K&R C.
> 
> This example just struck me as exceedingly silly, and quite misleading.

Well, 'switch' does not uphold the principles of structured programming. It is natural that one can extract some silliness from that, and I don't think changing grammar rules in inconsistent ways in order to disallow particular silly examples is a very good way to design a language.

I think removing () and requiring {} everywhere as Go and Rust have done is quite good language design which eliminates many of C's grammar issues, but it is not the path that D has taken and it does not improve switch semantics.
September 30, 2017
On Friday, 29 September 2017 at 09:56:17 UTC, Dukc wrote:
> On Friday, 29 September 2017 at 09:12:54 UTC, Don Clugston wrote:
>> Guess what this prints
>
> My guess is it prints "1".
>
> By "guess" I mean it, I did not test! Anyway reminds me a lot of very badly used gotos.

Yeah, it's a lot like Duff's Device:
https://en.wikipedia.org/wiki/Duff's_device

For anyone who's wondering, it works because switch is just a computed goto.  The code's equivalent to this:

import std.stdio;

void main()
{
	int i = 0;

	// switch(i)
	if (i == 7)
	{
		goto case_7;
	}
	else
	{
		goto case_default;
	}

	// for loop initialiser
	i = 8;
	// for loop test
	while (i < 10)
	{
case_7:
		writeln(i);
		return;
case_default:
		// for loop update
		++i;
	}
}
September 30, 2017
On 2017-09-29 21:56, Timon Gehr wrote:

> Well, I have used the switch(...) with(...) idiom you mentioned in the
> original post a few times, and I'm quite confident you'd meet some
> opposition if you were to break it.

I've used that as well, but the other way around. I put the switch statement inside the with statement.

-- 
/Jacob Carlborg
September 30, 2017
30.09.2017 05:35, sarn пишет:
> 
> For anyone who's wondering, it works because switch is just a computed goto.  The code's equivalent to this:
> 
> import std.stdio;
> 
> void main()
> {
>      int i = 0;
> 
>      // switch(i)
>      if (i == 7)
>      {
>          goto case_7;
>      }
>      else
>      {
>          goto case_default;
>      }
> 
>      // for loop initialiser
>      i = 8;
>      // for loop test
>      while (i < 10)
>      {
> case_7:
>          writeln(i);
>          return;
> case_default:
>          // for loop update
>          ++i;
>      }
> }

Thanks for clarification!
September 30, 2017
On Friday, 29 September 2017 at 09:12:54 UTC, Don Clugston wrote:
> Or it may have just been an accident.
> But regardless of the original motivation, it allows some truly dreadful semantics.
> Can we disallow this silliness please?

There are two big family of switches. C-like and Pascal like. In Pascal you can only have cases in the switch.

See https://en.wikipedia.org/wiki/Switch_statement#Semantics

The C form doesn't prevent you to be stricter but it's all about self-discipline since the compiler will allow everything in there.