More on patterns, generics, null and primitives

Ali Ebrahimi ali.ebrahimi1781 at gmail.com
Fri Dec 8 21:29:05 UTC 2017


Hi,
Ok, thanks Brian.
This is my expected answer from you.
But compare   ' var x = null; '    vs      ' null matches var x ' .
First is not valid code since we can not infer type x:
Rational: 'null' value as itself doesn't have any type:
Therefore we can not assign null to var type (per JEP lvti)

For later we can not have same reasoning, and say null literal value can
not incorporate in type test matching and issue error.
This way we would be consistent in both cases.

What do you think?

On Fri, Dec 8, 2017 at 11:34 PM, Brian Goetz <brian.goetz at oracle.com> wrote:

> The answer is subtle.
>
> The pattern `var x` will match a null target.  However, both the `matches`
> operator and `switch` statement will "intercept" nulls on their way in.  So
> `(null matches var x)` will evaluate to false, even though the pattern
> matches the input.
>
> For the switch, if there's no explicit `case null`, it will NPE -- just
> like today.  If there is a `case null`, it will be selected if the target
> is null.  The null case must come before any non-constant pattern.
>
> On 12/8/2017 2:59 PM, Ali Ebrahimi wrote:
>
> Hi,
> All good.
> One question: Is this valid code? If so, what is inferred type for x?
> if( null matches var x) .............
>
> What about this one?
> switch(null){
> case ValueType x:
> case int i:
> ....
> }
> Is not above cases unreachable or incompatible with switch input value(
> 'null' literal)?
>
>
>
>
> On Thu, Dec 7, 2017 at 7:19 PM, Gavin Bierman <gavin.bierman at oracle.com>
> wrote:
>
>> Below are some more refined thoughts about patterns, generics, null and
>> primitives. Thank you for your feedback so far. Comments welcome!
>>
>> Gavin
>> ----
>>
>> Patterns: Types, primitives and null
>> ====================================
>>
>> In this note, we address some of the issues raised in earlier posts
>> concerning pattern matching.
>>
>> To make matters concrete, let us define a simple grammar of patterns:
>>
>> p  ::= l                    //literal (primitive type constant expression)
>>        null                 //null pattern
>>        R x                  //Reference type test pattern
>>        V x                  //Value type test pattern
>>        D(p_1, ..., p_n)     //Deconstructor pattern (D is a *data class*)
>>        var x                //Type inference
>>
>> For simplicity, we have only considered primitive type constant
>> expression literals, but we can easily extend what follows to cover string
>> constant expressions and Enum names. For generality we have included simple
>> deconstruction patterns, which will most likely appear first with data
>> classes - a feature we have proposed elsewhere.
>>
>>
>> Type checking basics
>> ====================
>>
>> To type check the expression e matches R x where R is a reference type,
>> we check that the type of the expression e is cast convertible to R. This
>> is captured by the following typing rule:
>>
>> e : T     T cast-convertible R
>> ------------------------------
>> e matches R x : bool
>>
>> For value types, we propose a simpler rule. When type checking an
>> expression e matches V x where V is a value type, we shall simply check
>> that the type of e is either the value type V or the boxed type of V. Again
>> we can write this more formally as follows.
>>
>> e : V   or   e : box(V)
>> -----------------------
>> e matches V x : bool
>>
>> (We have assumed a function, box, that returns the boxed reference type
>> of a given value type, e.g. box(int)=Integer.)
>>
>> Type checking literal patterns is similar. Thus given an expression e
>> matches l where l is a primitive type constant expression of type V, we
>> check that e has exactly the type V. (We could also permit e to have the
>> boxed type of V, but we disallow this for simplicity.)
>>
>> e : V    l : V
>> ------------------
>> e matches l : bool
>>
>> [A small corner case with this rule is where the primitive type constant
>> expression l is a numeric literal without a suffix, e.g. 1. In this case we
>> would treat l as a poly expression and use the type of the expression e as
>> a target type.]
>>
>> Given an expression e matches null we check that e has some reference
>> type R.
>>
>> e : R
>> ---------------------
>> e matches null : bool
>>
>>
>> Generics
>> ========
>>
>> Currently instanceof is restricted so that the type must be reifiable. In
>> other words the following is not allowed.
>>
>> ArrayList<Integer> al = ...
>> if (al instanceof List<Integer>) {          // Error
>>     ...
>> }
>>
>> We propose to lift this restriction for pattern matching using type test
>> patterns. In other words, a reference type test pattern match is well typed
>> if the cast conversion is not unchecked. If the cast conversion is
>> unchecked then type checking fails.
>>
>> In what follows we will assume that all pattern matching code has type
>> checked correctly.
>>
>>
>> Nulls
>> =====
>>
>> To recall, the issue with nulls and pattern matching came from Java’s
>> current slightly inconsistent treatment of null.
>>
>> Currently x instanceof Foo returns false where x is the null value.
>> Clearly, we would want x matches Foo f to do the same.
>>
>> However, when we come to support deconstruction patterns, we would expect
>> Foo(null) to match a pattern Foo(var x). Moreover, we intend the pattern
>> var x to mean the same as Bar x where the type has been inferred to be Bar.
>> So we appear to have conflicting requirements!
>>
>> A further complication is that the switch statement currently throws an
>> exception when the switch expression is the null value.
>>
>> To recap:
>>
>> Foo f = null;                //Foo has a String field
>> f matches Foo x              //would like to return false as for
>> instanceof
>> switch (f) { case Foo: ... } //throws
>> f = new Foo(null);
>> f matches Foo(String x)      //would like to return true and bind x to
>> null
>> f matches Foo(var x)         //behave identically to above
>>
>> We need to reconcile the current semantics and find a generalisation that
>> matches (sic) the above behaviour for patterns.
>>
>> First, we shall generalise the switch statement in the following way. We
>> propose that it no longer throws an exception when the switch expression is
>> the null value. However, we propose that all switches have an implicit case
>> null: throw new NullPointerException(); clause, unless there is an explicit
>> case null pattern label in the switch body. In this way, all existing
>> switch statements will continue to mean the same thing, but users of the
>> enhanced switch can now program a case to handle the null value case.
>>
>> We had previously proposed dealing with null by considering the type of
>> the pattern test to determine if it was a type-restatement pattern or not.
>> Whilst this works, it could cause some subtle action-at-a-distance effects
>> that we found discomforting. Another possibility had been to classify
>> patterns as top-level and non-top-level and assign them different
>> semantics. This was felt to be too subtle.
>>
>> We think we now have a solution that avoids this partition of patterns,
>> leading to a simpler and clearer system. The key to this solution is to
>> separate the semantics of a pattern from the semantics of the construct
>> that uses the pattern.
>>
>> tl;dr: The semantics of pattern matching is defined to match null values
>> against type test patterns. The semantics of constructs that use pattern
>> matching is layered over this definition in order to capture the existing
>> behaviours with respect to null values at the top level.
>>
>> Given a pattern p, and a value e of type T, we can define the semantics
>> of this pattern with a function, match, that returns a boolean indicating
>> whether the pattern matches the value or not. (Recall that we assume that
>> type checking has already succeeded.)
>>
>> match(l, e:V)      =def e = l
>> match(l, e:R)      =def e <> null /\ unbox(e) = l
>>
>> match(null, e:V)   =def false               // n/a by type-checking
>> match(null, e:R)   =def e = null
>>
>> match(R t, e:V)    =def false               // n/a by type-checking
>> match(R t, e:R')   =def e = null \/ e instanceof R
>>
>> match(V t, e:V')   =def true                // by type-checking, V = V'
>> match(V t, e:R)    =def e <> null           // by type-checking, R =
>> box(V)
>>
>> match(D(pa_1, ..., pa_n), e:V) =def false
>> match(D(pa_1, ..., pa_n), e:R) =def
>>                         e instanceof D /\ match(pa_1, e.x_1:T_1) /\
>>                                    ... /\ match(pa_n, e.x_n:T_n)
>>                                 where  data class D(T_1 x_1, ..., T_n
>> x_n);
>>
>> This definition should be intuitive, but let us consider the first two
>> cases that deal with matching a value e against a literal pattern l. If e
>> has a value type then the pattern match is just a simple check for equality
>> between the value and the literal. If e has a reference type then we first
>> check if it has the null value. If so we return false, otherwise we unbox
>> the value and recursively check this against the pattern.
>>
>> The important case is where we have a type test pattern for a reference
>> type R and a value of a reference type. In this case we check if the value
>> has the null value. If so we return true, otherwise we check whether the
>> value is an instance of the type R. In other words, the inherent semantics
>> of pattern matching returns true when matching a null value against a
>> reference type test R x.
>>
>> Note there is no case for the var pattern as the compiler will have
>> replaced it with an explicit type test pattern as a result of type
>> inference.
>>
>> The final step is to use this to define the semantics of the two
>> constructs that use pattern matching, i.e. matches and switch. This can be
>> summarised using the following table (where e has static type T).
>>
>>                 | e = null      |  e <> null
>> -------------------------------------------------
>>                 |               |
>> e matches P     | false         |  match(P, e:T)
>>                 |               |
>>                 |               |
>> switch (e) {    |               |
>>   case null: s  | s             |
>> }               |               |
>>                 |               |
>>                 |               |
>> switch (e) {    |               |
>>   // P not null | NPE           | s    if match(P,e:T)
>>   case P : s    |               |
>> }               |               |
>>                 |               |
>>
>> Below is a number of examples demonstrating these semantics in action.
>>
>> Object o = null;
>> o matches String s                          // false
>> o matches Box(var x)                        // false
>> new Box(o) matches Box b                    // true
>> new Box(o) matches Box(null)                // true
>> new Box(o) matches Box(Object o)            // true
>> new Box(o) matches Box(var o)               // true (o inferred to have
>>                                             //       type Object)
>>
>> switch (o) { case String s: }               // NPE
>>
>> switch (o) {
>>     case null: System.out.println("hello");
>> }                                           // Prints hello
>>
>> o=(Object)"Hello";
>>
>> switch (o) {
>>     case String s: System.out.println(s);
>> }                                           // Prints Hello
>>
>> switch (o) {
>>     case null: break;
>>     case String s: System.out.println(s);
>> }                                           // Prints Hello
>>
>> o=null;
>> switch (new Box(o)) {
>>     case Box b: System.out.println("Box");
>> }                                           // Prints Box
>>
>> switch (new Box(o)) {
>>     case Box(null):
>>         System.out.println("A");
>>         break;
>>     case Box(var x):
>>         System.out.println("B");
>> }                                           // Prints A
>>
>> switch (new Box(o)) {
>>     case Box(var x):
>>         System.out.println(x==null);
>> }                                           // Prints true
>
>
>
>
> --
>
> Best Regards,
> Ali Ebrahimi
>
>
>


-- 

Best Regards,
Ali Ebrahimi


More information about the amber-spec-observers mailing list