Open lrhn opened 2 years ago
Agreed!
So it turns out the proposal is in an inconsistent state. It doesn't actually propose or specify switch elements. But it does refer to them in a couple of places.
(I believe what happened is that in our discussion around the syntax for switches, I deliberately wanted to consider expressions, statements, and elements holistically. And from that I jumped to assuming I'd already put switch elements in the proposal when I haven't.)
For now, I'm going to remove references to them from the proposal so that it's in a consistent state.
Came across this the other day when I wanted to use switch expression to create entries in a map literal, where both the key and value depend on the body of the switch expression - something like:
Map<String, List<String>> _parseChunks(List<List<String>> input) => {
for (final chunk in input) switch (chunk) {
['%', ...final tail] => 'begin': tail,
['&', ...final tail] => 'end': tail,
[...final xs, final last] => xs.join(): [last],
[] => throw StateError('chunks are never empty'),
}
};
Is this something that switch elements would support?
Came across this the other day when I wanted to use switch expression to create entries in a map literal, where both the key and value depend on the body of the switch expression - something like:
Map<String, List<String>> _parseChunks(List<List<String>> input) => { for (final chunk in input) switch (chunk) { ['%', ...final tail] => 'begin': tail, ['&', ...final tail] => 'end': tail, [...final xs, final last] => xs.join(): [last], [] => throw StateError('chunks are never empty'), } };
Is this something that switch elements would support?
Probably, although I think the last case of your switch expression would still fail with Expressions can't be used in a map literal.
I don't know if there's a workaround for this, as it also happens with collection-if
expressions.
Map<String, List<String>> _parseChunks2(List<List<String>> input) => {
for (final chunk in input)
if (chunk case ['%', ...final tail]) 'begin': tail
else if (chunk case ['&', ...final tail]) 'end': tail
else if (chunk case [...final xs, final last]) xs.join(): [last]
else throw StateError('chunks are never empty'), // Error: Expressions can't be used in map literals
};
Just remembered that there is already an issue to cover this last case: #2943.
The current workaround would be to throw in the key and use a "fake throw" in the value so the entry evaluates as Never: Never
, which will always be a subtype of whatever map we are constructing.
So, something like this:
Map<String, List<String>> _parseChunks(List<List<String>> input) => {
for (final chunk in input) switch (chunk) {
['%', ...final tail] => 'begin': tail,
['&', ...final tail] => 'end': tail,
[...final xs, final last] => xs.join(): [last],
[] => throw StateError('chunks are never empty'): throw '',
}
};
the last case of your switch expression would still fail with
Expressions can't be used in a map literal.
Ah right, that got lost in translation between my original code and the example. I'm mainly curious about a switch expression evaluating to a map entry.
the last case of your switch expression would still fail with
Expressions can't be used in a map literal.
Ah right, that got lost in translation between my original code and the example. I'm mainly curious about a switch expression evaluating to a map entry.
There shouldn't be a problem. As I understand this issue, it's generalized for collection literals, not specifically list literals.
The patterns proposal 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:
(just picking a random platform enum as example, bigger enums that can't be handled easily by
if
would be a better example).Grammar
The grammar is different from switch statements in that:
case
,default
,,
or}
. This is consistent with other elements. And it precludes using thecase
-free=>
syntax from expression switch.('case' <guardedPattern> ':')+
...[]
for introducing "no value". (Use?null
when we get null-aware elements.)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 ofdefault:
. It's slightly harder to parse a priorif
, though. (I'd even have allowedelse element
without a:
, but that's definitely not possible.)Not completely ambiguous, but a little confusing. Probably just keep using
default:
, orcase _:
with an element.If adding this, do add null-aware element at the same time,
? expression
can be written asswitch (expression) { case var v?: v }
, but...? optList
can be written asswitch (optList) { case var list?: ...list }
too, and we still think it's a good operator.