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.

Reply via email to