[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 

But, there is a subtle difference between

     switch (x) {
         case FOO: ...


     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