[External] : Re: Primitive type patterns
Dan Heidinga
heidinga at redhat.com
Wed Mar 2 18:43:24 UTC 2022
On Mon, Feb 28, 2022 at 1:53 PM Brian Goetz <brian.goetz at oracle.com> wrote:
>
>
> Now, what if instead of Object, we start with Long?
>
> Long l = 0L
> if (l instanceof byte b) { ... }
>
> First, applicability: does Long unbox to a primitive type that can be narrowed to byte? Yes! Long unboxes to long, and long can be narrowed to byte.
>
> Then: matching: if the RHS is not null, we unbox, and do a range check. (The rules in my previous mail probably didn't get this case perfectly right), but 0L will match, and 0xffff will not -- as we would expect.
>
>
> This is totally alien to me, when you have x instanceof Foo (note: this is not the pattern version) with X the type of x, then if x is declared with a super type of X it works the exact same way, i.e i don't have to care to much about the type on what i'm doing an instanceof / switching over it.
>
>
> Yes, I understand your discomfort. And I will admit, I don't love this particular corner-of-a-corner either. (But let's be clear: it is a corner. If you're seeking to throw out the whole scheme on the basis that corners exist, you'll find the judge to be unsympathetic.)
>
> So why have I proposed it this way? Because, unfortunately, of this existing line in JLS 5.2 (which I never liked):
>
> > an unboxing conversion followed by a widening primitive conversion
>
> This is what lets you say:
>
> long l = anInteger
>
> And, I never liked this rule, but we're stuck with it. The inverse, from which we would derive this rule, is that
>
> anInteger instanceof long l
>
> should be applicable, and in fact always match when the LHS is non-null. I would prefer to not allow this assignment conversion, and similarly not allow both unboxing and widening in one go in pattern matching, but I didn't get to write JLS 5.2.
>
> What's new here is going in the *other* direction:
>
> anInteger instanceof short s
>
> and I think what is making you uncomfortable is that you are processing two generalizations at once, and it's pushing your "OMG different! scary!" buttons:
>
> - that we're defining primitive type patterns in a way such that we can derive the existing assignment conversions;
> - that primitive type patterns can have dynamic checks that primitive assignments cannot, so we're including the value-range check.
>
> Each individually is not quite as scary, but I can understand why the two together would seem scary. (And, as I mentioned, I don't like the unbox-and-widen conversions either, but I didn't invent those.)
>
>
Making the pattern match compatible with assignment conversions makes
sense to me and follows a similar rationale to that used with
MethodHandle::asType following the JLS 5.3 invocation conversions.
Though with MHs we had the ability to add additional conversions under
MethodHandles::explicitCastArguments. With pattern matching, we don't
have the same ability to make the "extra" behaviour opt-in / opt-out.
We just get one chance to pick the right behaviour.
Intuitively, the behaviour you propose is kind of what we want - all
the possible byte cases end up in the byte case and we don't need to
adapt the long case to handle those that would have fit in a byte.
I'm slightly concerned that this changes Java's historical approach
and may lead to surprises when refactoring existing code that treats
unbox(Long) one way and unbox(Short) another. Will users be confused
when the unbox(Long) in the short right range ends up in a case that
was only intended for unbox(Short)? I'm having a hard time finding an
example that would trip on this but my lack of imagination isn't
definitive =)
Something like following shouldn't be surprising given the existing
rules around unbox + widening primitive conversion (though it may be
when first encountered as I expect most users haven't really
internalized the JLS 5.2 rules):
Number n = ....;
switch(n) {
case long l -> ...
case int i -> .... // dead code
case byte b -> .... // dead code
default -> ....
}
But this may be more surprising as I suggested above
Number n = new Long(5);
switch(n) {
case byte b -> .... // matches here
case int i -> .... //
case long l -> ...
default -> ....
}
Overall, I like the extra dynamic range check but would be fine with
leaving it out if it complicates the spec given it feels like a pretty
deep-in-the-weeds corner case.
--Dan
More information about the amber-spec-experts
mailing list