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
(§9.1.2
<https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.1.2>),
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?)
Maurizio
>
> 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