6

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?

BeeOnRope
  • 51,419
  • 13
  • 149
  • 309
  • 1
    This kind of thing (or maybe it's just one thing, I don't know) is a longstanding issue with ECJ. See e.g. https://bugs.eclipse.org/bugs/show_bug.cgi?id=397317. – Oliver Charlesworth Dec 05 '17 at 20:55
  • @OliverCharlesworth - well the comment thread on that bug indicates that it was actually a problem in `javac` and that it was fixed in Java 7, so it seems like that case wasn't an issue in ECJ? – BeeOnRope Dec 05 '17 at 20:58
  • Oh maybe it's that way round, then :) I'm not sure, to be honest, just a thing I was vaguely aware of. – Oliver Charlesworth Dec 05 '17 at 21:01
  • @OliverCharlesworth I don't see any connection to bugs.eclipse.org/bugs/show_bug.cgi?id=397317 neither by content nor by the history of which version of javac accepts/rejects. – Stephan Herrmann Dec 06 '17 at 22:37

1 Answers1

4

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.

Stephan Herrmann
  • 7,168
  • 1
  • 22
  • 33
  • 1
    Shouldn't ECJ reject the assignment then? As it also rejects the following: `List l1 = null; List> l2 = l1;`. And maybe this is just a discrepancy, but [eclipse shows `T` is inferred to `AtomicReference>`](https://i.stack.imgur.com/mkT8A.png). – Jorn Vernee Dec 07 '17 at 13:14