Patterns design question: Nulls

Gavin Bierman gavin.bierman at oracle.com
Fri Nov 3 10:46:10 UTC 2017


Nulls and pattern matching

The null value is treated somewhat inconsistently in Java; unfortunately pattern matching places a fresh focus on these inconsistences. Consider a local variable, String s=null;. Currently s instanceof String returns false; whereas (String)ssucceeds; and further switch (s){ case "Hello": ... } throws a NPE. Unfortunately, we need to maintain these behaviours whilst providing a consistent story for patterns.

So far, we have essentially two choices on the table. One based on what might be called a pragmatic navigation of existing choices; and another more sophisticated one based on static type information. (In what follows, we assume a declaration T t = null; is in scope.)

Option 1.

Matches t matches Object o. To keep it consistent with instanceof this must return false.

Switch switch is retconned to not throw a NPE when given a null. However, all switches with reference-typed selector expressions are considered to have an implicit case null: throw new NullPointerException(); clause as the first clause in the switch. If the user supplies a null clause (which means it must be a pattern-matching switch) then this replaces the implicit clause. [An alternative to this is to introduce non-null type tests, which we fear would quickly become unwieldy.]

Note that this addresses a problem that has been brought up on the external mailing list. Currently:

static void testSwitchInteger(Integer i) {
    switch(i) {
        case 1:  System.out.println("One");   break;
        default: System.out.println("Other"); break;
    }
}
static void testSwitchNumber(Number i) {
    switch(i) {
        case 1:  System.out.println("One");   break;
        default: System.out.println("Other"); break;
    }
}
testSwitchNumber(null);  // prints "Other"
testSwitchInteger(null); // NPE
The Integer case is an old-style switch, so throws an NPE. The Number case is a pattern matching case, so without the insertion of an implicit null clause, it would actually match the default clause (this is the behaviour of the current prototype).

ASIDE Adding a null clause has an impact on the dominance analysis in pattern matching. A null pattern must appear before a type test pattern.

Nested/Destructuring patterns As discussed earlier, t matches Object o returns false. But unfortunately new Box(t) matches Box(Object o) really ought to return true. (Both because this is what we feel would be expected, but also to be consistent with expected semantics of extractors.) In other words, the semantics of matching against null is not compositional. Note also that the null value never matches against a nested pattern.

We might expect a translation to proceed something like the following.

e matches Box(Object o)
-> e matches Box && 
       (e.contents matches null as o || e.contents matches Object o) 
-> e instance Box && 
       (e.contents == null || e.contents instanceof Object)
(Note the rarely seen as pattern in the intermediate pattern.)

Option 2.

We can use the static type information to classify pattern matches, which ultimately determines how the matching is translated.

For example:

if (t matches U u) { // where T <: U
    ...
}
Notice here that the pattern match is guaranteed to succeed as T is a subtype of U. We can classify this as a type restatement pattern, and compile it essentially to the following

if (true) {
    U u = t;
    ...
}
In other words, the expression (o matches U u) succeeds depending on the static type of o: if the static type of o is a subtype of U then it evaluates to true, even for the value of null. If it is not statically a subtype of U then its runtime type is tested as normally, and null would fail.

ASIDE The choice of null matching also impacts on our reachability analysis. For example:

Integer i = ...;
switch (i) {
    case Integer j: {
        System.out.println(j); 
        break;
    }
    default: System.out.println("Something else");
}
Is the default case reachable? If the type test matches null then it is unreachable, otherwise it is reachable.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20171103/9884b078/attachment-0001.html>


More information about the amber-spec-experts mailing list