dart-lang / dart-lang/language

Switch elements - switches in collections, like `if` and `for`

Open
#2,124 7 comments 17 reactions 0 assignees View on GitHub
feature-completeness patterns
Dominant language
TeX
Stars
2.9k
Forks
239
Avg merge
2d 18h
Merged PRs (30d)
14

Description

The [patterns proposal](https://github.com/dart-lang/language/blob/master/working/0546-patterns/patterns-feature-specification.md#switch-expression) includes an expression switch.

It would likely be very useful to allow it as an "element switch" as well, so that it can be used in collection literals with "element"s as values instead of expression.

(Most likely, it requires duplicating the grammar into the elements grammar as well).

That would allow something like:
```dart
[42, 37, switch (Endian.host) {
case Endian.little: ...[87, 0, 0, 0]
case Endian.big: ...[0, 0, 0, 87]
}, 99]
```
(just picking a random platform enum as example, bigger enums that can't be handled easily by `if` would be a better example).

## Grammar
```ebnf
::=
|
|
|
|
| (* new *)

(* All new: *)
::= 'switch' '(' ')'
'{' * ? '}'
::= ('case' ':')+
::= 'default' ':' ?
```

The grammar is different from switch statements in that:
* The switch element caluses have no syntactic terminator, they are terminated by the following `case`, `default`, `,` or `}`. This is consistent with other elements. _And it precludes using the `case`-free `=>` syntax from expression switch._
* There are no labels. (There are no continues either, so no use for labels.)
* "Empty-bodied" switchs falling through to (combining with, really) the next case is made explicit, by matching them together as `('case' ':')+`
* It *is* an option to not allow multi-case elements.
* Or to allow "no element"-bodies which introduce no element, but that's ambiguous with "fallthrough". For now, use `...[]` for introducing "no value". (Use `?null` when we get null-aware elements.)
* The `default` clause can omit an element clause, which will mean introducing no element. It's there for "must exhust" types".

## Semantics

Matching and binding semantics are the same as for switch statements, the scope is the following element.

As usual, elements guarded by multiple cases with "fallthrough" can only use bindings that have the same type in every pattern binding leading to that element.

A switch element is "must exhaust" in the same cases as a switch statement (switching on enum, bool, Null, sealed type, or combinations of those). It's otherwise not "must exhaust" since it's OK to not introduce any elementrs.
A "must exhaust" switch can use `default:` as a default no-element catch-all.

## Comments.
I wanted to allow `else:` instead of `default:`. It's slightly harder to parse a prior `if`, though. (I'd even have allowed `else element` without a `:`, but that's definitely not possible.)
```dart
var x = [switch (e) {
case p1: if (b2) e3
else: b4
}
```
Not completely ambiguous, but a little confusing. Probably just keep using `default:`, or `case _:` with an element.

If adding this, do add null-aware element at the same time, `? expression` can be written as `switch (expression) { case var v?: v }`, but `...? optList` can be written as `switch (optList) { case var list?: ...list }` too, and we still think it's a good operator.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.