Currently switch statements can de facto switch over nullable types, since all types are nullable in Dart.
int x = null;
switch(x) {
case 3:
print("A number");
break;
default:
print("A null number");
break;
}
Post-nnbd, the type of x would become int?. Should we allow switching over nullable types? And if so, should we allow null as one of the explicit cases, even though it is not an instance of the same type as the rest of the case constants?
int? x = null;
switch(x) {
case 3:
print("A number");
break;
case null:
print("A null number");
break;
}
We should fix switch, not special-case nullable types.
(And generally stop treating it like a second class citizen.)
The specification of switch suffers from being written for Dart 1, and therefore not being able to depend on the static type of anything. It has the ... interesting ... requirement that all case expressions must be instances of the same class. That's just useless, both because of null and because you actually have type hierarchies sometimes. It means you cannot change one value to be an instance of a subtype, breaking the good-ole subtype substitutability. And it doesn't give us anything in practice.
The fix would be changing:
It is a compile-time error if the value of the expressions $e_j, j \in 1 .. n$ are not either: \begin{itemize} \item instances of the same class $C$, for all $j \in 1 .. n$, or \item instances of a class that implements \code{int}, for all $j \in 1 .. n$, or \item instances of a class that implements \code{String}, for all $j \in 1 .. n$. \end{itemize}
to
It is a compile-time error if the run-time types of the value of the expressions $e_j, j \in 1 .. n$ are not all subtypes of the static type of $e$.where e is the switch expression. We'll have to require that each case expression value has primitive equality, we can't just refer to the type $C$ (but they are constants, so we can talk about the value at compile-time), and we can remove the requirement that the run-time type of the value of e is an instance of the class $C$ too.
Then null would be automatically allowed if the expression is nullable, a full Java-style enum where some values are instances of subclasses can still be handled, there is no need to excuse int or double, etc.
The analyzer is then free to make it a warning if a switch over a nullable enum type doesn't handle all enum values and null.
I agree with everything @lrhn said. More specifically:
Should we allow switching over
nullabletypes?
Yes, I think so. The alternative is egregiously annoying if you do want null to flow to the default case like it does today — you would have to wrap the entire switch in an if (expr != null) { switch ... } else { null handling... }.
And if so, should we allow
nullas one of the explicit cases
Yes, doing so gives users an easy way to handle null explicitly while allowing other values to flow to the default case. The alternative is having to put an if (expr == null) { null handling... } else { other defaults... } inside the default case.
I updated the specification to cover this (along with other aspects of switches) here.
Most helpful comment
We should fix switch, not special-case nullable types.
(And generally stop treating it like a second class citizen.)
The specification of
switchsuffers from being written for Dart 1, and therefore not being able to depend on the static type of anything. It has the ... interesting ... requirement that all case expressions must be instances of the same class. That's just useless, both because ofnulland because you actually have type hierarchies sometimes. It means you cannot change one value to be an instance of a subtype, breaking the good-ole subtype substitutability. And it doesn't give us anything in practice.The fix would be changing:
to
Then
nullwould be automatically allowed if the expression is nullable, a full Java-style enum where some values are instances of subclasses can still be handled, there is no need to excuseintordouble, etc.The analyzer is then free to make it a warning if a switch over a nullable enum type doesn't handle all enum values and
null.