Exhaustiveness in switch

Remi Forax forax at univ-mlv.fr
Thu May 10 23:06:45 UTC 2018

> De: "Kevin Bourrillion" <kevinb at google.com>
> À: "Brian Goetz" <brian.goetz at oracle.com>
> Cc: "amber-spec-experts" <amber-spec-experts at openjdk.java.net>
> Envoyé: Jeudi 10 Mai 2018 22:44:33
> Objet: Re: Exhaustiveness in switch

> * I think that for most occurrences of `default: throw...`, by far, the user
> really doesn't benefit from being able to choose the exception type or the
> message at all. A standardized choice of exception, and an autogenerated
> message (that automatically includes the switch target, which users usually
> don't bother to do themselves!), may be strictly better.

as one of my student put it, it's important to always go banana predictably. 


> On Thu, May 10, 2018 at 1:12 PM, Brian Goetz < [ mailto:brian.goetz at oracle.com |
> brian.goetz at oracle.com ] > wrote:

>> In the long (and indirect) thread on "Expression Switch Exception Naming", we
>> eventually meandered around to the question of when should the compiler deem an
>> expression switch to be exhaustive, and therefore emit a catch-all throwing
>> default. Let's step back from this for a bit and remind ourselves why we care
>> about this.

>> Superficially, having to write a throwing default for a condition we believe to
>> be impossible is annoying:

>> switch (trafficLight) {
>> case RED -> stop();
>> case GREEN -> driveFast();
>> case YELLOW -> driveFaster();
>> default -> throw new ExasperationException("No, we haven't added any new traffic
>> light colors since the invention of the automobile, so I have no idea what " +
>> trafficLight + " is");
>> }

>> The annoyance here, though, is twofold:
>> - I have to write code for something which I think can't happen;
>> - That code is annoying to write.

>> In the above, we "knew" another traffic light color was impossible, and we
>> listed them all -- and the compiler knew it. This is particularly irritating.
>> However, we often also see cases like this:

>> void processVowel(letter) {
>> switch (letter) {
>> case A: ...
>> case E: ...
>> case I: ...
>> case O: ...
>> case U: ...
>> default: throw new IllegalStateException("Not a vowel: " + letter);
>> }

>> Here, the annoyance is slightly different, in that I could not reasonably expect
>> the compiler to know I'd covered all the vowels. In fact, I think the explicit
>> exception in this case is useful, in that it documents an invariant known to
>> the programmer but not captured in the type system. But it is still annoying
>> that I have to construct a format string, construct an exception, and throw it;
>> if there were easier ways to do that, I might be less annoyed. Without diving
>> into the bikeshed, maybe this looks something like:

>> default: throw IllegalStateException.format("Not a vowel: %s", vowel);

>> The details aren't relevant, but the point is: maybe a small-ish library tweak
>> would reduce the annoyance of writing such clauses. (This one isn't so bad, but
>> Dan excavated a bunch that were way worse.) But, let's set this aside for a
>> moment, and return back to the point of why we want the compiler to provide a
>> throwing default.

>> I think most of the discussion has centered on the problem of a novel value
>> showing up at runtime. This is surely an issue, and must be dealt with, but the
>> central issue is: a default is never able to distinguish between a
>> runtime-novel value and a value we just forgot to include at compile time. It
>> doesn't matter whether this default throws (as the implicit default in an
>> expression switch) or does nothing (as the implicit default in statement
>> switches today does).

>> We agreed that we should not require the user to provide a default when they
>> provide case clauses that cover the target type as of compile time (true+false
>> for boolean, all the members of a sealed type, etc.) This is because the
>> default you'd be forced to put in otherwise (for expression switches) is
>> actually harmful; if the type were later modified to have more values, an
>> explicit default would swallow them, rather than yielding an error at
>> recompilation time. So it is not only annoying, but actually could cover up
>> errors.

>> We then went off on the wrong tangent, though, where we wondered whether it was
>> OK to implicitly assume enums were sealed, since some enums are clearly
>> intended to acquire new values. But the mistake was focusing on the wrong
>> aspect of sealed-ness (the statement of intent to not add more values), rather
>> than the compiler's ability to reason credibly about known possible values.

>> So, backing up, I think we should always treat a "complete" enum expression
>> switch specially -- don't require a default, and implicitly add a throwing one,
>> if all the cases are specified. This way, if the assumption that you've covered
>> all the cases is later broken via separate compilation, on recompilation,
>> you'll discover this early, rather than at runtime. (You'll still get runtime
>> protection either way.) Regardless of whether we think the enum will be
>> extended in the future or not. There's no need for enums to declare themselves
>> "sealed" or "non-sealed" (and such a declaration would likely be incorrect
>> anyway, as it asks users to predict the future, which is error-prone.)

>> Given this, I'm willing to use ICCE as a base type for the implicit exception
>> (though there should be more specific subtypes.)

>> Now, statement switches. It seems sad that we can't get the same kind of
>> compile-time assistance over statement switches than we do over expression
>> switches. We're somewhat locked in by compatibility here; statement switches
>> today get an implicit "default: nothing" clause if they have no default, and we
>> cannot (and don't want to) break this. So the next best thing is if the user
>> could say "I want to get the same sort of compile-time verification of putative
>> exhaustiveness for this statement switch as I would for expression switches."
>> This would require some additional syntax (please, let's not bikeshed this
>> until everything else on this topic is nailed down; this is a target of
>> opportunity, not a problem to be solved Right Now.)

>> Someone is likely to suggest that we should do the exhaustiveness thing for all
>> three of the four new forms (statement arrow, and expression colon/arrow). Feel
>> free to make this suggestion, but you're going to get the "snitch" lecture :)

>> Another thing that we can do to make it easier to write throwing defaults: lean
>> on intrinsics. Recall that separately, we've got a story to expose some
>> compiler intrinsics for ldc() and invokedynamic(). There's room to add other
>> things to this, such as the equivalent of __LINE__ and __FILE__ macros in C, or
>> (relevant to this) information about the the current point in the compilation
>> (such as the cases enumerated in the innermost switch.) So for example:

>> default: throw SwitchException.format("Found %s, but expected one of %s",
>> target, Intrinsics.switchCases());

>> or even

>> default: throw SwitchException.of(target, Intrinsics.switchCases());

>> where `Intrinsics.switchCases()` would evaluate to a string that includes all
>> the cases handled by the current switch (in our vowels case, this would be "A,
>> E, I, O, U"). Again, not something for Right Now, but something that machinery
>> that's in the pipeline can contribute to making it simpler and more uniform to
>> express catch-all defaults, and thereby reduced the perceived annoyance.

>> Summary:
>> - For switches over any type where the compiler can enumerate the possibilities
>> (includes enums, some primitives, and sealed types), always allow the user to
>> leave off a default if they've specified all the known cases.
>> - Use subtypes of ICCE in implicit throwing defaults.
>> - Consider library enhancements to common exceptions (and maybe additional
>> intrinsics) to simplify code that throws formatted exceptions.

> --
> Kevin Bourrillion | Java Librarian | Google, Inc. | [ mailto:kevinb at google.com |
> kevinb at google.com ]
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20180511/2a536f7f/attachment-0001.html>

More information about the amber-spec-experts mailing list