More on patterns, generics, null and primitives
Brian Goetz
brian.goetz at oracle.com
Fri Dec 8 20:04:39 UTC 2017
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 <mailto: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
More information about the amber-spec-observers
mailing list