Inference variable has incompatible bounds. Java 8 Compiler Regression?

Lukas Eder picture Lukas Eder · Jun 3, 2015 · Viewed 17k times · Source

The following program compiles in Java 7 and in Eclipse Mars RC2 for Java 8:

import java.util.List;

public class Test {

    static final void a(Class<? extends List<?>> type) {
        b(newList(type));
    }

    static final <T> List<T> b(List<T> list) {
        return list;
    }

    static final <L extends List<?>> L newList(Class<L> type) {
        try {
            return type.newInstance();
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

Using the javac 1.8.0_45 compiler, the following compilation error is reported:

Test.java:6: error: method b in class Test cannot be applied to given types;
        b(newList(type));
        ^
  required: List<T>
  found: CAP#1
  reason: inference variable L has incompatible bounds
    equality constraints: CAP#2
    upper bounds: List<CAP#3>,List<?>
  where T,L are type-variables:
    T extends Object declared in method <T>b(List<T>)
    L extends List<?> declared in method <L>newList(Class<L>)
  where CAP#1,CAP#2,CAP#3 are fresh type-variables:
    CAP#1 extends List<?> from capture of ? extends List<?>
    CAP#2 extends List<?> from capture of ? extends List<?>
    CAP#3 extends Object from capture of ?

A workaround is to locally assign a variable:

import java.util.List;

public class Test {

    static final void a(Class<? extends List<?>> type) {

        // Workaround here
        List<?> variable = newList(type);
        b(variable);
    }

    static final <T> List<T> b(List<T> list) {
        return list;
    }

    static final <L extends List<?>> L newList(Class<L> type) {
        try {
            return type.newInstance();
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

I know that type inference has changed a lot in Java 8 (e.g. due to JEP 101 "generalized target-type inference"). So, is this a bug or a new language "feature"?

EDIT: I have also reported this to Oracle as JI-9021550, but just in case this is a "feature" in Java 8, I've reported the issue also to Eclipse:

Answer

ZhongYu picture ZhongYu · Jun 3, 2015

Disclaimer - I don't know enough about the subject, and the following is an informal reasoning of mine to try to justify javac's behavior.


We can reduce the problem to

<X extends List<?>> void a(Class<X> type) throws Exception
{
    X instance = type.newInstance();
    b(instance);  // error
}

<T> List<T> b(List<T> list) { ... }

To infer T, we have constraints

      X <: List<?>
      X <: List<T>

Essentially, this is unsolvable. For example, no T exists if X=List<?>.

Not sure how Java7 infers this case. But javac8 (and IntelliJ) behaves "reasonably", I'd say.


Now, how come this workaround works?

    List<?> instance = type.newInstance();
    b(instance);  // ok!

It works due to wildcard capture, which introduces more type info, "narrowing" the type of instance

    instance is List<?>  =>  exist W, where instance is List<W>  =>  T=W

Unfortunately, this is not done when instance is X, thus there is less type info to work with.

Conceivably, the language could be "improved" to do wildcard capture for X too:

    instance is X, X is List<?>  =>  exist W, where instance is List<W>