Consider the following returnsNull
function and a call to it with a generic type:
public static <T> List<T> returnNull(Class<? extends T> clazz) {
return null;
}
public static void main( String[] args )
{
List<AtomicReference<?>> l = returnNull(AtomicReference.class);
}
The Eclipse compiler, when set to Java 8, accepts it, but javac
in Java 8 rejects it with:
incompatible types: cannot infer type-variable(s) T
(argument mismatch; java.lang.Class<java.util.concurrent.atomic.AtomicReference> cannot be converted to java.lang.Class<? extends java.util.concurrent.atomic.AtomicReference<?>>)
The underlying difference seems to be that given a two parameterized types P1<T>
and P2<T>
, Eclipse allows conversion from the outer type parameterized with the raw inner type: P1<P2>
to the outer type parameterized with a lower bound of the of the inner-type with an unbounded wildcard like P1<? extends P2<?>>
. javac
doesn't.
This isn't just a theoretical musing: if this code was accepted it would solve my generics filtering problem.
Who is right?
During applicability inference ECJ infers <T>
to AtomicReference#RAW
, which let's the signature of returnNull
appear as
List<AtomicReference#RAW> returnNull(Class<? extends AtomicReference#RAW>)
The exact steps being:
⟨Class<AtomicReference#RAW> → Class<? extends T#0>⟩
⟨Class<AtomicReference#RAW> <: Class<? extends T#0>⟩
⟨AtomicReference#RAW <= ? extends T#0⟩
⟨AtomicReference#RAW <: T#0⟩
AtomicReference#RAW <: T#0
T#0 = AtomicReference#RAW
Now, there's no problem passing a value of type Class<AtomicReference#RAW>
into that method.
(the suffix #RAW is an implementation specific denotation for raw types, reproduced here for clarity).
EDIT: Things look different during invocation type inference: By adding the target type into the mix we end up (during incorporation) with the following constraint:
⟨AtomicReference#RAW <: AtomicReference<?>⟩
This constraint should reduce to FALSE, but ecj reduces to TRUE. From this, rejecting the program seems to be the correct answer.
I filed bug 528970 for further investigation in ECJ.
There is some irony in this, because javac has a long standing bug whereby it wrongly assumes T#RAW <: T<X>
. For compatibility reasons, ECJ explicitly copied this bug into many locations, but apparently in one particular code location it's the opposite: javac applies subtyping where ECJ checks for compatibility.
EDIT 2: One year later it seems that the bug in ECJ can only be fixed after JLS has been improved in and around JDK-8054721.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With