> From: "Brian Goetz" <[email protected]>
> To: "amber-spec-experts" <[email protected]>
> Sent: Thursday, April 7, 2022 9:54:11 PM
> Subject: Re: Primitive type patterns
> There's another, probably stronger, reason why primitive patterns supporting
> widening, narrowing, boxing, unboxing, etc, are basically a forced move,
> besides alignment with `let` statements, discussed earlier:
>> There is another constraint on primitive type patterns: the let/bind
>> statement
>> coming down the road. Because a type pattern looks (not accidentally) like a
>> local variable declaration, a let/bind we will want to align the semantics of
>> "local variable declaration with initializer" and "let/bind with total type
>> pattern". Concretely:
>> let String s = "foo";
>> is a pattern match against the (total) pattern `String s`, which introduces
>> `s`
>> into the remainder of the block. Since let/bind is a generalization of local
>> variable declaration with initialization, let/bind should align with locals
>> where the two can express the same thing. This means that the set of
>> conversions allowed in assignment context (JLS 5.2) should also be supported
>> by
>> type patterns.
> The other reason is the duality with constructors (and eventually, methods).
> if
> we have a record:
> record R(int x, Long l) { }
> we can construct an R with
> new R(int, Long) // no adaptation
> new R(Integer, Long) // unbox x
> new R(short, Long) // widen x
> new R(int, long) // box y
> Deconstruction patterns are the dual of constructors; we should be able to
> deconstruct an R with:
> case R(int x, Long l) // no adaptation
> case R(Integer x, Long l) // box x
> case R(short s, Long l) // range check
> case R(int x, long l) // unbox y, null check
> So the set of adaptations in method invocation context should align with those
> in nested patterns, too.
We already discussed those rules when we discuss instanceof, it means that "x
instanceof primitive" has different meaning depending on the type of x
Object x = ...
if (x instanceof short) { ... } // <=> instanceof Short + unboxing
int x = ...
if (x instanceof short) { ... } // <=> range check
Byte x = ...
if (x instanceof short) { ... } // <=> nullcheck + unboxing + widening
You are overloading instanceof with different meanings, losing everybody apart
the compiler in the process.
It's also another creative way to have an action at distance,
var x = ...
f (x instanceof short) { ... } // <=> ???
We do not need all theses conversions to be part of the pattern, those
conversions are already done as part expression/instruction after the ':' or
'->' of the switch in an organic way.
// with op(int, Long)
case R(int x, Long l) -> op(x, l); // no adaptation
// with op(Integer, Long)
case R(int x, Long l) -> op(x, l); // box x
// with op(int, long)
case R(int x, Long l) -> op(x, l); // unbox l, nullcheck
And for the range check, as i said earlier, it's better to use a pattern method
with a good name so everybody will be able to read the code.
Rémi