Incompilable instanceof with super-bound
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Thu Mar 16 14:54:08 UTC 2023
On 16/03/2023 11:13, Tagir Valeev wrote:
> Also fails (with same error). Now, there are two questions here:
>
> 1. should the cast be statically rejected - this means asking whether
> |Sub<?>| and |Super<? super Box<?>>| are provably distinct
> parameterized types;
> 2. should the cast be allowed without warnings - this means proving
> that there is only one possible way to go from a |Super<? super
> Box<?>>| down to a |Sub<?>|;
>
Before I dive into the actual rules, I must pause for a second, and
tackle the elephant in the room: there is a long-standing discrepancy
between javac and Eclipse - in that javac static checks around casts go
beyond what is specified by the JLS. This can result in some casts being
/rejected/, rather than simply flagged with an unchecked warning. We are
in one such case here.
Back to the example, we have to prove that there exists a narrowing
reference conversion (5.1.6.1) between |Super<#CAP1>|, where |#CAP1 :>
Box<?>| (note the capture conversion here) and |Sub<?>|. To do that we
have to prove that there is /no/ supertype X of |Super<#CAP1>| and Y of
|Sub<?>| such that X and Y are provably distinct (4.5). To establish
that, we need to first find a supertype of |Sub<?>| that can be
compared against |Super<#CAP1>|. That supertype is |Super<#CAP2>| where
|#CAP1 <: Object| (that's the direct supertype of |Sub<?>|). So, are
|Super<#CAP1>| and |Super<#CAP2>| provably distinct? From the definition:
> Two type arguments are provably distinct if one of the following is true:
>
> Neither argument is a type variable or wildcard, and the two
> arguments are not the same type.
>
> One type argument is a type variable or wildcard, with an upper
> bound (from capture conversion (§5.1.10), if necessary) of S; and the
> other type argument T is not a type variable or wildcard; and neither
> |S| <: |T| nor |T| <: |S| (§4.8, §4.10).
>
> Each type argument is a type variable or wildcard, with upper
> bounds (from capture conversion, if necessary) of S and T; and neither
> |S| <: |T| nor |T| <: |S|.
>
Since the upper bounds of |#CAP1| and |#CAP2| are compatible (after all,
|Box<?> <: Object| ), we can conclude that, from a JLS perspective,
|Super<#CAP1>| and |Super<#CAP2>| are /not/ provably distinct. So a
narrowing reference conversion exists, and the cast should be allowed.
Should there be a warning? This depends on whether the narrowing
reference conversion is checked - which is defined in 5.1.6.2:
> The unchecked narrowing reference conversions are as follows:
>
> A narrowing reference conversion from a type S to a parameterized
> class or interface type T is unchecked, unless at least one of the
> following is true:
>
> All of the type arguments of T are unbounded wildcards.
>
> T <: S, and S has no subtype X other than T where the type
> arguments of X are not contained in the type arguments of T.
Since here the target T is |Sub<?>|, which contains only unbounded
wildcards, the conversion should /not/ be unchecked (meaning no warning
should be generated). This also means that |Sub<?>| is a valid type test
pattern for the instanceof.
Back to javac, the reason as to why javac disallows the cast (and hence,
the instanceof) is that javac doesn’t think that you can write a
well-formed witness of |Super<? super Box<?>>| that is /also/ compatible
with |Sub<?>|. I believe javac is correct in its assertion: the only two
witnesses I can think of are |Super<Box<?>>| and |Super<Box>|, neither
of which seems compatible with |Sub<?>| (as |Sub<T> extends
Super<Box<T>>|, and there’s no value you can replace for T to make the
resulting type match with either |Super<Box>| or |Super<Box<?>>|). Which
is exactly the same reason as to why javac doesn’t think (inside the
|main| method) that there’s a way for |new Sub<>| to be inferred such
that the inferred type is compatible with |Super<? super Box<?>>|.
Now, while there are some discrepancies between what the JLS say and
what javac does with respect to narrowing reference conversions, there
should be no such discrepancies when it comes to inference. Note that
the same failure with |new Sub<>| can be reproduced even w/o method
call, as in:
|Super<? super Box<?>> s = new Sub<>(); |
In this case the compiler has to prove that:
|Sub<alpha> <: Super<? super Box<?>> --> Super<Box<alpha>> <: Super<?
super Box<?>> --> Box<alpha> <= ? super Box<?> --> Box<?> <: Box<alpha>
--> ? <= alpha --> false |
(that’s because 18.2.3 doesn’t have any notion of how to reduce the
above type containment).
In other words, there’s no instantiation for alpha which makes the above
proof correct, so infererence must fail.
Summing up, we know there are difference between how Eclipse, JLS and
javac deal with narrowing reference conversions. But, typically the
mismatches have to do with corner cases where no concrete witness for
the cast can be produced. So I think the most important question here is
whether |isSub(new Sub<>())| should succeed at all. I don’t think it
should (see above) - and if that’s the case, this seems “just” another
case where javac eagerly rejects a cast that seems unsound anyway.
Cheers
Maurizio
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/compiler-dev/attachments/20230316/842dff4c/attachment-0001.htm>
More information about the compiler-dev
mailing list