Opting into totality

Brian Goetz brian.goetz at oracle.com
Mon Aug 24 17:30:31 UTC 2020


The previous mail, on optimistic totality, only applied to switches that 
were already total.  Currently that includes only expression switches; 
we want a non-too-invasive way to mark a statement switch as total as well.

First, I'd like to rule out the variants of "automatically make XYZ 
statement switches total", such as, e.g., "statement switches over enums 
or sealed types" -- because partial switches are entirely _useful and 
reasonable_.  A partial switch is like an `if` without an `else`.  
Nothing wrong with that, regardless of whether the target is an enum or 
sealed type or some other known-enumerable type.  There's no reason to 
rule out partiality here.  Users won't thank us.


There are a number of syntactic options for opting in, such as:

  - A modified keyword (total-switch)
  - A modifier before or after `switch` (e.g., `switch enum`, as per 
Guy's #4, `sealed switch`, or `switch case` as per #8)
  - Turning the statement switch into a void expression switch (Guy's #5)
  - Giving `default` new jobs to do
  - A streamlined form of throw-on-residue default (e.g., `default: 
throw`, #6)
  - Using cast expressions to highlight the target type (#7)

There is something pretty attractive about saying only "this statement 
switch is total in exactly the same ways that all expression switches 
are", because then we have factored the interaction between the switch 
being total (or not) and the set of cases being optimistically total (or 
not), rather than introduced yet another mechanism whose only purpose is 
to plug a hole.

The idea of "turn it into an expression switch by casting to void" is 
cute, but will always be perceived by users as an "idiom" rather than an 
actual feature.  I would like to encourage developers to think more in 
terms of totality, because it contains hidden benefits for them.  (I am 
also vaguely fearful of an unexpected interaction when we try to turn 
`void` into a type in Valhalla, and would rather "avoid" creating new 
constraints on this.)

The idea of `default: throw` reads nicely, but the semantics feel too 
ad-hoc, in that we're inferring the behavior of `throw` in the context 
of a `default` in the context of an otherwise-optimistically-total 
switch.  I would expect that people would then expect `throw` to mean 
"throw the obvious thing" in other contexts, leading to disappointment.


Another option is to engage some notion of unreachability.  It has been 
requested a few times before to have a feature like an "unreachable" 
statement:

     void m(int x) {
         if (x == 0)
             throw new FooException();
         else
             return;

         unreachable;
     }

Which would be interpreted as an assertion that this statement is 
unreachable, that the compiler should issue an error if it can prove the 
statement _is_ reachable, and translate to throwing some sort of 
assertion error.  If we had such a notion, we could extend it to say:

     default: optimistically-unreachable

which would have the effect of (a) engaging reachability analysis and 
generating compiler errors if the case is known to be reachable, and (b) 
throwing the expected set of errors on residue at runtime.




More information about the amber-spec-observers mailing list