[pattern-switch] Opting into totality
Brian Goetz
brian.goetz at oracle.com
Tue Sep 1 14:22:26 UTC 2020
On 8/31/2020 9:56 PM, Dan Smith wrote:
>
> A competent programmer will definitely need to be able to answer the question "should I add 'sealed' to this switch?" I'll take a stab at enumerating all the cases:
>
> - A switch statement with a 'default' case: doesn't matter. The language already supports 'default' in both forms (existing switch statements are non-sealed, existing switch expressions are sealed). Pick a favorite style. (Alternatively: a switch statement with a 'default' clause is implicitly sealed. Then it's a style question of whether or not to be explicit about it.)
>
> - A switch statement that initializes a local variable or returns at the end of a method: doesn't matter. If you don't say 'sealed', flow analysis will catch any mistakes. (But there are some gotchas, so maybe 'sealed' (or switch expression) is the way to go if you don't want to think about it.)
Yeah, while in many cases flow analysis will save us, I'm not sure this
is the message we want to send; it is not uncommon when presented with a
flow error ("variable x might not be initialized) to "fix" it with "int
x = 0". (Gotcha, stupid compiler.) I think these cases are prime
examples of "falling out of this switch silently is a mistake", so I
would advocate for sealing in all these cases, rather than hoping they
used flow analysis correctly.
> - A switch statement that side-effects on just a few of the possible inputs ("possible" per static types): must use a non-sealed switch.
Not "must", since you can have a default that does nothing in a sealed
switch.
But, there is a subtle difference between
switch (x) {
case FOO: ...
}
and
sealed switch (x) {
case FOO: ....
default: // nothing
}
which is, what happens on remainder. In the former, it is just another
ignored non-matching input; in the latter, we throw.
I believe this is the subtle difference that will get people.
> - A switch statement that is optimistically total over an enum/sealed class: use a sealed switch to ensure totality checking in the future. Or, if the totality is accidental (I cover the cases right now, but don't expect to in the future), use a non-sealed switch.
What we were calling optimistic totality (now, totality with remainder)
is not just about enums and sealed classes, though. Consider:
switch (foo) {
case Foo(var x): ...
}
There is a remainder, null, and a sealed switch will NPE on it. (As
will a pattern assignment.)
> - A switch statement with a last 'case' intended to be total: use a sealed switch to avoid mistakes and (if it's a risk) defend against input type changes
>
> I think that covers it? There will be some coding style preferences to work out, but I think this story will be intuitive to most programmers.
>
Yep. I think the key difficult think here, which took us a while to
see, is that the remainder shape can get complicated, especially when
you get to nested deconstruction patterns over sealed types. We agreed
that this is unavoidable, and hopefully will pop into the user's
consciousness rarely.
The key difference between a sealed switch and a non-sealed one is that
in a sealed switch, there is _no_ input value which is silently ignored.
More information about the amber-spec-experts
mailing list