[pattern-switch] Exhaustiveness
Brian Goetz
brian.goetz at oracle.com
Fri Aug 21 15:14:57 UTC 2020
Yes, this is the sort of ordering I was aiming at.
> If the user does not want such implicit handling of an optimistically total situation in a statement switch, then it is always possible to provide explicit clauses “case null: break;” and “default: break;”.
Indeed, and this is why I was trying to break it down into a set of
cases, to ensure that there always is a pattern the user can denote if
they want to catch some part of the residue. Where we are now is:
- In a total switch (currently just switch expressions), any residue
involving novel values gets ICCE, a null gets NPE, and any residue not
in the above categories gets (something, maybe NPE, maybe something else.)
- If the user explicitly wants Box(null), they have two choices:
explicitly match Box(null), or, more likely, use some total pattern on
Box (`Box(var x)`, `Box b`, etc.) Similarly, if they want (for whatever
reason) Box(novel), they can similarly use totality. (I hope people are
beginning to see why totality in nesting is so critical.)
So, next sub-subject (sub-ject?): when, and under what conditions, do we
get NPE from non-total switches? I said this yesterday:
> Separately (but not really separately), I'd like to refine my claim
> that `switch` is null-hostile. In reality, `switch` NPEs on null in
> three cases: a null enum, String, or primitive box. And, in each of
> these cases, it NPEs because (the implementation) really does
> dereference the target! For a `String`, it calls `hashCode()`. For
> an `enum`, it calls `ordinal()`. And for a box, it calls
> `xxxValue()`. It is _those_ methods that NPE, not the switch. (Yes,
> we could have designed it so that the implementation did a null check
> before calling those things.)
I bring this up because these situations cause current switch to NPE
even when the switch is not total, and this muddies the story a lot. We
can refine this behavior by saying: "If a switch *on enums, strings, or
boxes* has no nullable cases, then there is an implicit `case null: NPE`
at the beginning".
In other words, I am proposing to treat this "preemptive throwing" as an
artifact of switching over these special types (which is fair because
the language already gives these types special treatment.) Then, we are
free to treat residue-handling as a consequence of totality, not a
general null-hostility of switch.
Let me repeat that, because it's a big deal.
Switch is *not* null-hostile. We were just extrapolating from too
few data points to
see it.
Switches on _enums, strings, and boxes_, that do not explicitly
have null-handling cases,
are null-hostile, because switching on these involves calling
methods on Enum, String,
or {Integer,Long,...}.
If you put a `case null` in a switch on strings/etc, it doesn't
throw, it's just matching
a value.
In all other cases, null is just a value that can be matched, or
not, and if the
switch ignores its residue, the nulls leak out just like the rest
of it.
In the general case, switches throw only when they are total; for
partial switches
(e.g. statement switches), null is just another value that didn't
get matched.
I believe this restores us to sanity.
Next up (separate topic): letting statement switches opt into totality.
Assuming that we're on the right track, and drilling into the next
level, we now have to bring this back to totality.
On 8/20/2020 9:02 PM, Guy Steele wrote:
>
>> On Aug 20, 2020, at 6:14 PM, Brian Goetz <brian.goetz at oracle.com> wrote:
>>
>> I suspect there are other orderings too, such as "any nulls beat any novels" or vice versa, which would also be deterministic and potentially more natural to the user. But before we go there, I want to make sure we have something where users can understand the exceptions that are thrown without too much head-scratching.
>>
>> If a user had:
>>
>> case Box(Head)
>> case Box(Tail)
>>
>> and a Box(null) arrived unexpectedly at the switch, would NPE really be what they expect? An NPE happens when you _dereference_ a null. But no one is deferencing anything here; it's just that Box(null) fell into that middle space of "well, you didn't really cover it, but it's such a silly case that I didn't want to make you cover it either, but here we are and we have to do something." So maybe want some sort of SillyCaseException (perhaps with a less silly name) for at least the null residue.
> I believe that if Head and Tail exhaustively cover an enum or sealed type (as was the intended implication of my example)—more generally, in a situation that is optimistically total---then the user would be very happy to have some sort of error signaled if some other value shows up unexpectedly in a statement switch, whether that value is “Ankle" or “null”. Maybe a new error name would be appropriate, such as UnexpectedNull.
>
> If the user does not want such implicit handling of an optimistically total situation in a statement switch, then it is always possible to provide explicit clauses “case null: break;” and “default: break;”.
>
>> On the other hand, ICCE for Box(novel) does seem reasonable because the world really has changed in an incompatible way since the user wrote the code, and they probably do want to be alerted to the fact that their code is out of sync with the world.
> Yep.
>
>> Separately (but not really separately), I'd like to refine my claim that `switch` is null-hostile. In reality, `switch` NPEs on null in three cases: a null enum, String, or primitive box. And, in each of these cases, it NPEs because (the implementation) really does dereference the target! For a `String`, it calls `hashCode()`. For an `enum`, it calls `ordinal()`. And for a box, it calls `xxxValue()`. It is _those_ methods that NPE, not the switch. (Yes, we could have designed it so that the implementation did a null check before calling those things.)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20200821/8892caad/attachment.htm>
More information about the amber-spec-experts
mailing list