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