Type test patterns: allow expression type to match pattern type

Brian Goetz brian.goetz at oracle.com
Sun Apr 4 20:25:18 UTC 2021


On 4/4/2021 1:16 PM, Anthony Vanelverdinghe wrote:
> When using a type test pattern, the compiler gives an error if the expression type is a subtype of the pattern type.
> While I agree that it doesn't make sense for the expression type to be a strict subtype, I believe it should be allowed to be the same type.

More precisely, the compiler ensures that the pattern type is 
_applicable_ to the expression type.  For reference type patterns, 
applicability means strict subtyping.

You're asking if we could relax this.  In theory, yes, but the choice to 
not permit total patterns in instanceof (which is what would happen if 
we allowed you use the exact type) was a deliberate choice, to work 
around the legacy behavior of `instanceof`, and to prevent bugs.  And in 
fact, the bug it prevented is exactly the bug you almost made, because 
you were trying to use a total type pattern as a null check, which 
wouldn't have worked the way you wanted.

For reasons that have been litigated to death, the semantics of matching 
a type pattern `T t` to a target of type `U` are:

  - If T is total on U, then it matches all values of U, including null;
  - If T is not total on U, then it is equivalent to `instanceof U`.

This is the natural semantics for matching patterns, but it does cause a 
sharp edge relative the legacy meaning of `instanceof`.  It means that:

     (x instanceof Object)

and

     (x instanceof Object o)

agree everywhere -- except null.  Because these two are so syntactically 
similar, but would not mean the same thing, we restricted the pattern on 
the RHS of `instanceof` to patterns that _actually ask a question_.  If 
we allowed `x instanceof Object o`, this would be equivalent to `true`, 
which would be surprising to many developers, including you.  So this 
sharp edge was resolved in favor of "instanceof must actually ask a 
question".  (There are ample other places where Java doesn't let you 
write "dumb" code, such as unreachable statements, for the same reason.)

The diagnosis here, though, is that you tried to out-clever yourself by 
using a nullable pattern as a null check, instead of writing what you 
really wanted to ask -- is this thing null.)

At some point, we might consider strictly non-nullable type patterns 
(e.g., `x instanceof String! s`), but we would want to do this in the 
context of a more comprehensive treatment of nullability.

Looking ahead, what this really illustrates, though, is that methods 
like Map::get are the problem.  Map::get pretends to be total, but 
really is partial.  Really, Map::get should not be a method that returns 
a distinguished sentinel on failure, but should be a _partial pattern_ 
on maps.  Then you'd say something like:

     if (map instanceof Map.containing(k)(var v)) { ... can use v here }





>
> For example, with a `NavigableMap<String, String> map`, instead of writing:
> if(map.higherKey("foo") != null) {
>      Files.writeString(path, map.higherKey("foo"));
> }
>
> We could write:
> if(map.higherKey("foo") instanceof String kNext) {
>      Files.writeString(path, kNext);
> }
>
> Or, with a `BufferedReader in`, instead of writing:
> String line;
> while((line = in.readLine()) != null) { ... }
>
> We could write:
> while(in.readLine() instanceof String line) { ... }
>
> Has this idea been considered before? If so, why was it turned down? If not, shall I file an RFE for this?
>
> Kind regards,
> Anthony
>



More information about the amber-dev mailing list