On Mon, Feb 28, 2022 at 1:53 PM Brian Goetz <brian.go...@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