unchecked casts and intersection types

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Tue Dec 20 10:26:06 UTC 2016

On 19/12/16 23:07, Liam Miller-Cushon wrote:
> I was surprised by the unchecked warning in the following code. Is 
> this behaviour correct?
> It stems from Types.isCastable using the same logic for union and 
> intersection types, and in both cases requiring that each element type 
> be castable to the target. For intersection types, isn't it sufficient 
> for any element type to be castable?
It's surprising, yes, but I think it's sound; consider this case:

class A { }
class B { }
class C extends B implements I { }
interface I { }

B b = ...
A a = (I & A)b;

Now, you want to cast B to something that is a subtype of both I and A. 
Of course you can cast from B to I (there could exist a type, such as C, 
that extends B and implements I). But that doesn't mean that the cast is 
ok - there's no way a B can be casted to an A - so, whatever type is 
going to be a subtype of both A and I, that type is not going to extend 
B, so javac is correct in statically rejecting the cast, I believe.

The union is a bit of the same - albeit for different reason; if you 
have (I | A) you can have either a I or an A, so, to go to B you should 
make sure that there's a path from either of them to the type B.

So, in both cases if one of the components is not castable to the target 
type of the cast, you have a problem, I think.

In your example, however, I see that there's indeed a problem - I don't 
think the warning javac is correct, but not because of how javac 
performs the cast - but more because javac fails to see that if you have 
an intersection like:

A<String> & C

Then it's never possible for a member of the intersection to implement 
the interface A with a parameterization other than String, or a 
compile-time error would occur, as per 8.1.5:

"A class may not at the same time be a subtype of two interface types 
which are different parameterizations of the same generic interface 
or a subtype of a parameterization of a generic interface and a raw type 
naming that same generic interface, or a compile-time error occurs."

So, the situation javac is complaining about can never occur in practice 
because of restrictions in the inheritance graph. Detecting this 
condition would not be too dissimilar to what cast rules already do for 
'final' (see 5.5). I note that that, while the spec is as strict as 
javac in banning cast where the source type is an intersection type:

"If S is an intersection type A_1 |&| ... |&| A_n , then it is a 
compile-time error if there exists an A_i (1 ≤ /i/ ≤ /n/) such that A_i 
cannot be cast to T by this algorithm. That is, the success of the cast 
is determined by the most restrictive component of the intersection type. "

There doesn't seem an equivalent rule in 5.5.2 about unchecked warnings 
where S is an intersection, which I think it's an omission (going from 
List & A to List<String> should defo give a warning?)

> class Test {
>   interface A<Y> {}
>   interface B<Z> extends A<Z> {}
>   interface C {}
>   <T extends A<String> & C> B<String> f(T t) {
>     B<String> result;
>     A<String> a = t;
>     result = (B<String>) a; // ok
>     result = (B<String>) t; // unchecked
>     // required: B<String>
>     // found:    T
>     // where T is a type-variable:
>     //   T extends A<String>,C declared in method <T>f(T)
>     return result;
>   }
> }

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/compiler-dev/attachments/20161220/d872fcfb/attachment.html>

More information about the compiler-dev mailing list