Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Eclipse ECJ accepts this code, javac doesn't - who is right?

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?

like image 920
BeeOnRope Avatar asked Dec 05 '17 20:12

BeeOnRope


1 Answers

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:

  • initial constraint:
    • ⟨Class<AtomicReference#RAW> → Class<? extends T#0>⟩
  • reduced to:
    • ⟨Class<AtomicReference#RAW> <: Class<? extends T#0>⟩
    • ⟨AtomicReference#RAW <= ? extends T#0⟩
    • ⟨AtomicReference#RAW <: T#0⟩
    • AtomicReference#RAW <: T#0
  • resolved to:
    • 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.

like image 66
Stephan Herrmann Avatar answered Nov 03 '22 07:11

Stephan Herrmann