[External] : Re: Type test patterns: allow expression type to match pattern type
Brian Goetz
brian.goetz at oracle.com
Mon Apr 5 12:42:25 UTC 2021
Actually, this is just a bug in the error message. This can be fixed --
thanks for the bug report!
On 4/5/2021 3:45 AM, Anthony Vanelverdinghe wrote:
> Thanks for the instructive response and spot-on diagnosis, Brian. What would've helped me connect the dots myself in this case, is mentioning totality in the error message, something like "pattern type {1} is total on expression type {0}" (rather than the current "expression type {0} is a subtype of pattern type {1}"). But I assume this isn't possible for now, since the concept isn't actually defined in the JLS yet.
>
> Kind regards, Anthony
>
> On Sunday, April 04, 2021 22:25 CEST, Brian Goetz <brian.goetz at oracle.com> wrote:
>
>> 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