[patterns] primitive widening

Brian Goetz brian.goetz at oracle.com
Tue Oct 17 17:30:57 UTC 2017

[ moving to amber-spec-experts ]

There are a few ways we can interpret a primitive type test pattern 
(case int).  We could interpret them strictly, as Remi says, permitting 
only boxing adaptation when the target is a reference type; or we could 
interpret them more broadly, treating them as an assignability check 
(could I assign the target type to a variable of this type.)

Assignment supports a combination of unboxing and widening; if we say

     Short s = ...
     int i = s

this succeeds without a cast.  This seems like the correct spirit of "x 
matches int i"; could I assign, without explicit adaptation, the target 
to an int.  (On the other hand, assignment won't let us assign an 
Integer to a Short.)

This has consequences for two forms of dead-code testing the compiler 
wants to do -- static applicability tests (where we reject nonsense 
combinations of target type and pattern) and dominance testing.

Every pattern has a "bounding type":

  - For a constant pattern "case c", it is the type of c;
  - For a type test pattern "case T t", it is T;
  - For a destructuring pattern "case Foo(..)", it is Foo.

Let's say: For a match "x matches P" or "switch (x) { case P: }", where 
X is the static type of x, P is considered inapplicable if a cast from X 
to B=bounding(P) would fail according to existing cast conversion rules, 
and should result in a compilation error.

The above works when B and X are reference types; it needs to be further 
adjusted for the case when B is a primitive type; I think the refinement 
needed is we want to check for cast conversion from X to 
maybeBox(bounding(P)), where maybeBox is the identity on reference types 
and maps to the box type for primitives?

Dominance testing is more complicated when it comes to primitives, but 
it means we have to treat int as dominating short (and Short).  Again, 
we can appeal to assignability; primitive type T dominates primitive Q 
(or Q's box) if Q can be assigned to T without a cast.  So int dominates 
short and Short.

Bottom line: use cast conversion for "could this pattern match", use 
assignment conversion for dynamic type tests involving boxing, widening, 
and subtyping.

On 10/15/2017 5:33 AM, Remi Forax wrote:
> I do not think that a Short should matches a long if the pattern matching is defined as a suite of matches, the behavior will be weird.
> By example with,
>    Object x = new Short();
>    switch(x) {
>      case long l: ...
>      case short s: ...
>    }
> it will be surprising that the first case matches.
> In my opinion, we should stick to
>    matches = instanceof + cast
> the instanceof part is a plain old instanceof and the cast can be an unboxing but not a widening conversion
> so with x an object
>    x matches long l <=> x instanceof Long + unbox Long
> With your example,
>    void test(Short x) {
>      assert(x matches long l);  // compile error, because x instanceof Long is a compile error
>    }
> regards,
> Rémi
> ----- Mail original -----
>> De: "John Rose" <john.r.rose at oracle.com>
>> À: "amber-dev" <amber-dev at openjdk.java.net>
>> Envoyé: Dimanche 15 Octobre 2017 01:02:14
>> Objet: [patterns] primitive widening
>> Interesting question:  Should a "Short" box value
>> match a "long" primitive?  More generally, should
>> pattern matching emulate assignment conversions?
>> Including primitive widening conversions?
>> Probably the answer is yes, under the theory that
>> a pattern match digs through dynamic information to
>> search for a *possible* assignment, and then (along
>> that "match succeeded" path, is ready to make the
>> assignment to a binding variable.
>>     void test(Short x) {
>>         long y = x; //OK
>>         longConsumer(x); //OK
>>         assert(x matches long);  //OK??
>>     }
>>     void longConsumer(long z) { }
>> Does the prototype work that way?  No, there is just an
>> open-coded instanceof test of the wrapper type
>> (makeTypeTest … boxedTypeOrType).
>> This is tricky to implement dynamically, but MH.invoke
>> gets these details correct already, since it makes a
>> dynamic emulation of (many of) Java's static rules for
>> method argument conversion.
>> ("Many of" means that the dynamic emulation is aware
>> only of reified types, as if only raw types were present.
>> Happily, the Core Reflection API also does the same
>> emulation, although with different exceptions thrown.)
>> These are the relevant routines from the MH runtime:
>>   sun.invoke.util.Wrapper::isConvertibleFrom
>>   sun.invoke.util.Wrapper::convert
>> One good thing about this messy stuff:  The widening
>> primitive conversions (and narrowing ones, in the case
>> of a cast) only come into play when the target type is
>> really a primitive, not just a reference wrapping one.
>> So we don't have to match a Short to a Long, just
>> a Byte, Short, Character, Integer, Float, and Long to
>> a primitive long (if the source type is a reference
>> type that can contain any or all of the above).
>> A factored runtime for pattern matching will be able
>> to use the MH routines, if we decide that's the correct
>> behavior.  By "factored runtime" I hope for, of course,
>> something with a metafactory for each switch and
>> pattern.
>> — John

More information about the amber-spec-observers mailing list