Language: Should we allow switch statements over nullable types?

Created on 13 Dec 2019  Â·  3Comments  Â·  Source: dart-lang/language

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;
}
nnbd

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 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.

All 3 comments

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 nullable types?

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 null as 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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

lrhn picture lrhn  Â·  4Comments

wytesk133 picture wytesk133  Â·  4Comments

har79 picture har79  Â·  5Comments

natebosch picture natebosch  Â·  4Comments

leonsenft picture leonsenft  Â·  4Comments