Author Topic: Switch proposal  (Read 9162 times)

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Switch proposal
« on: November 28, 2018, 12:27:25 PM »
Putting it here instead as a comment in a longer discussion

Here is the propsal:

Code: [Select]
switch (a) {
  case 1 |  // Using : instead of | is syntax sugar for case 1: goto next;
  case 2 |
  case 3:
    foo();  // With : we get a break;
  case 4 |
  case 5:
    bar();
    goto next; // If we want to execute a statement then pass to next.
  case 6:
    baz();
}

// The above in C:
switch (a) {
  case 1:
  case 2:
  case 3:
    foo();
    break;
  case 4:
  case 5:
    bar();
  case 6:
    baz();
    break;
}

Another possibility is |: or :|

Code: [Select]
switch (foo) {
  case 1|:
  case 2|:
  case 3:
     do_something();

Using | means a that some care must be taken if there is an expression of type:

case 1|2: (This is is compiled to case 3: or course, but I think it's reasonable to require ( ) on any constant expression that isn't a literal.

So case 1+2: would not be allowed, but case (1+2): is ok.

Alternatives to goto next would be:

Code: [Select]
case 4:
   foo();
   fallthrough;
 case 5:
   ...

 case 4:
   foo();
   goto case 5;
 case 5:
   ...

 case 4:
   foo();
   goto case;
 case 5:
   ...

 case 4:
   foo();
   continue case;
 case 5:
   ...

 case 4:
   foo();
   next;
 case 5:
   ...

 case 4:
   foo();
   next case;
 case 5:
   ...

 case 4:
   foo();
   nextcase;
 case 5:
   ...

An even more lightweight syntax uses | for fallthrough, leading to this uniform syntax look:

Code: [Select]
  case 4:
   foo();
  | case 5:
   ...

Code: [Select]
switch (a) {
  case 1:
  | case 2:
  | case 3:
    foo();  // implicit break here
  case 4 :
  | case 5:
    bar();  // Fallthrough here because the next starts with |
  | case 6:
    baz();
}

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Switch proposal
« Reply #1 on: November 28, 2018, 12:38:02 PM »
Real life example of this kind of switch:

Code: [Select]
switch (D->getKind()) {
    case DECL_FUNC:
        expr->setConstant();
    case DECL_VAR:
        VarDecl* VD = cast<VarDecl>(D);
        QualType T = VD->getType();
        if (T.isConstQualified()) {
            Expr* Init = VD->getInitValue();
            if (Init) {
                // Copy CTC status of Init Expr
                expr->setCTC(Init->getCTC());
            }
            expr->setConstant();
            return;
        }
    case DECL_ENUMVALUE:
        expr->setCTC(CTC_FULL);
        expr->setConstant();
        return;
    case DECL_ALIASTYPE |
    case DECL_STRUCTTYPE |
    case DECL_ENUMTYPE |
    case DECL_FUNCTIONTYPE |
        expr->setConstant();
    case DECL_ARRAYVALUE |
    case DECL_IMPORT |
    case DECL_LABEL:
}

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Switch proposal
« Reply #2 on: November 28, 2018, 12:39:22 PM »
And the uniform | syntax instead:

Real life example of this kind of switch:

Code: [Select]
switch (D->getKind()) {
    case DECL_FUNC:
        expr->setConstant();
    case DECL_VAR:
        VarDecl* VD = cast<VarDecl>(D);
        QualType T = VD->getType();
        if (T.isConstQualified()) {
            Expr* Init = VD->getInitValue();
            if (Init) {
                // Copy CTC status of Init Expr
                expr->setCTC(Init->getCTC());
            }
            expr->setConstant();
            return;
        }
    case DECL_ENUMVALUE:
        expr->setCTC(CTC_FULL);
        expr->setConstant();
        return;
    case DECL_ALIASTYPE:
    | case DECL_STRUCTTYPE:
    | case DECL_ENUMTYPE:
    | case DECL_FUNCTIONTYPE:
        expr->setConstant();
    case DECL_ARRAYVALUE:
    | case DECL_IMPORT:
    | case DECL_LABEL:
        // do nothing.
}

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Switch proposal
« Reply #3 on: November 28, 2018, 12:45:15 PM »
Also to discuss, should we automatically open a scope or not. If we automatically open a scope in cases, we prevent this nice code:

Code: [Select]
switch (state) {
  case READ_WITH_LOCK:;
    LOCK *lock = acquire_lock();
    defer lock.release();
  case READ_WITHOUT_LOCK:
    read_from_resource();
    break;
  case ...
}

This since the implicit scope would trigger the defer when passing into the READ_WITHOUT_LOCK (I assume current syntax, so this is fallthrough automatically)

Of course, we could make | / fallthrough mean that scope is shared. That would make this execute correctly (new syntax):

Code: [Select]
switch (state) {
  case READ_WITH_LOCK:;
    LOCK *lock = acquire_lock();
    defer lock.release();
  | case READ_WITHOUT_LOCK:
    read_from_resource();
  case ...
}
« Last Edit: November 28, 2018, 12:47:04 PM by lerno »

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Switch proposal
« Reply #4 on: November 28, 2018, 10:36:50 PM »
Yet one more syntax:

Code: [Select]
switch (a) {
  case 1, // Comma to delimit multiple case. Can only be followed by another case
  case 2,
  case 3: // : means we have an explicit break.
    foo(); 
  case 4,
  case 5:
    bar();
    goto next; // Explicit fallthrough (could also be goto case, goto case 6 etc)
  case 6:
    baz();
}

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Switch proposal
« Reply #5 on: November 28, 2018, 10:43:58 PM »
So, current options:

(A) case 3 | for empty fallthrough, explicit fallthrough when it is non empty.
(B) | case 3 to indicate the previous case was fallthrough, regardless whether it was empty or not.
(C) case 3, for empty fallthrough, explicit fallthrough when it is non empty.

Types of "fallthrough" keyword:

(a) fallthrough
(b) goto next
(c) goto case
(d) goto case 4
(e) continue case
(f) next
(g) nextcase

bas

  • Full Member
  • ***
  • Posts: 220
    • View Profile
Re: Switch proposal
« Reply #6 on: November 29, 2018, 08:08:56 AM »
Wow, that's a lot of posts in one day..

The case of
Code: [Select]
case 3| looks quite nice, but it differs from the case where you have some code, but also a fall-through.
I want to simplify the language as much as possible, so I don't really want to change the current behavior too much. If you think
about the possible bugs that often occur is only that a develop forgets to add the break keyword. So the only needed option I
think is to add the fallthrough keyword and require that for all fallthroughs. So for example:

Code: [Select]
case 1:
   break;
case 2:
   fallthrough;
case 3:
   fallthrough:
default:
   // ...

or maybe even allow empty cases to fallthrough without keyword to make code with lots of fallthrough's better readable

Code: [Select]
case 1:
   break;
case 2:  // allowed because empty case
case 3:
   fallthrough;
default:
   // ..

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Switch proposal
« Reply #7 on: November 29, 2018, 02:11:28 PM »
I think fallthrough automatically on empty is very inconsistent. Consider this, with impllcit break:

Code: [Select]
func void printTest(i32 foo) {
 switch (foo) {
   case 1:
     printf("A\n");
   case 2:
     printf("B\n");
   default:
     // Do nothing
  }
}
printTest(1);

Prints "A"

Now we decide to comment out printf("A\n");

Code: [Select]
func void printTest(i32 foo) {
 switch (foo) {
   case 1:
     // printf("A\n");
   case 2:
     printf("B\n");
   default:
     // Do nothing
  }
}
printTest(1);

Now this prints "B". Perhaps not what we expected... So I think case 1, is a better suggestion for empty fallthrough (version C)

bas

  • Full Member
  • ***
  • Posts: 220
    • View Profile
Re: Switch proposal
« Reply #8 on: November 30, 2018, 05:03:55 PM »
Fallthrough is always automatic, except when you use break. So there is never an automatic break.
The compiler gives gives a warning/error if you don't use break/fallthrough in cases there is a code..

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Switch proposal
« Reply #9 on: November 30, 2018, 06:39:23 PM »
Err... My proposal was to add implicit break.
« Last Edit: December 03, 2018, 02:09:34 AM by lerno »

bas

  • Full Member
  • ***
  • Posts: 220
    • View Profile
Re: Switch proposal
« Reply #10 on: December 02, 2018, 12:20:26 PM »
Ahh missed that sorry

I think the common case for a switch is that every case breaks. Fallthrough is a much rarer case. So I think making
all cases break by default is an improvement indeed. Syntax wise, this means the following:
- keyword break is allowed in a case.
- keyword fallthrough is used to indicate that the case will fallthrough (there must be a next case).

I'll put in on the roadmap

bas

  • Full Member
  • ***
  • Posts: 220
    • View Profile
Re: Switch proposal
« Reply #11 on: March 12, 2019, 10:34:32 AM »
I was thinking about the fallthrough keyword and came up with a situation that requires some thinking.
How would we handle:

Code: [Select]
...
case A:
   if (b == 10) fallthrough;
   do_something();
case B:
    ..

So the fallthrough can be used to 'jump' to the next case. Or only allow fallthrough at the end?

To avoid this complexity, I see that Go (golang.org) only allows fallthrough at the end. So probably that is best..
« Last Edit: March 12, 2019, 10:36:33 AM by bas »

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Switch proposal
« Reply #12 on: March 12, 2019, 04:40:22 PM »
Well, if you look at the alternatives for fallthrough:

(a) fallthrough
(b) goto next
(c) goto case
(d) goto case 4
(e) continue case
(f) next
(g) nextcase

Here obviously b, c, d, e, g all clearly indicate that there's a jump. (d) is even more explicit of course, not to mention flexible.