Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Bounded Generics: Type inference bug? (Method invocation, JLS 15.12.2.7)

For the following snippet of code:

import java.util.List;

public class Main {
    interface Interface1<T> {}
    interface Interface2<T> extends Interface1<T> {}
    static class Bound {}
    interface BoundedI1<T extends Bound> extends Interface1<T> {}
    interface BoundedI2<T extends Bound> extends Interface2<T> {}

    public static void main(String[] args) {
        test((List<BoundedI2<?>>) null);
        //test2((List<BoundedI2<?>>) null);
    }

    public static void test(List<? extends Interface2<? extends Bound>> list) { test2(list); }

    public static void test2(List<? extends Interface1<? extends Bound>> list) {}
}

The compiler is OK with the first call, but complains if I uncomment the second. Is this a bug in the type inference system, or can someone explain why the inference rules in the JLS fail here?

Tested on both 6u43 and 7u45 Oracle JDKs.

UPDATE: Seems like eclipsec accepts it just fine. Unfortunately I can't really change up our toolchain :P, but it is interesting to find differences in the compilers.

The error message, as printed by ideone (cool tool btw):

Main.java:12: error: method test2 in class Main cannot be applied to given types;
        test2((List<BoundedI2<?>>) null);
        ^
  required: List<? extends Interface1<? extends Bound>>
  found: List<BoundedI2<?>>
  reason: actual argument List<BoundedI2<?>> cannot be converted to List<? extends Interface1<? extends Bound>> by method invocation conversion

UPDATE 2: This compiles fine, which indicates that the compiler does think BoundedI2<?> is assignable to Interface1<? extends Bound>, which seems to more directly contradict the JLS:

public class Main {
    interface Interface1<T> {}
    interface Interface2<T> extends Interface1<T> {}
    static class Bound {}
    interface BoundedI1<T extends Bound> extends Interface1<T> {}
    interface BoundedI2<T extends Bound> extends Interface2<T> {}

    public static void main(String[] args) {
        test((List<BoundedI2<?>>) null);
        //test2((List<BoundedI2<?>>) null);
        test3((BoundedI2<?>) null);
    }

    public static void test(List<? extends Interface2<? extends Bound>> list) { test2(list); }

    public static void test2(List<? extends Interface1<? extends Bound>> list) {}
    public static void test3(Interface1<? extends Bound> instance) {}
}
like image 273
user508633 Avatar asked Jul 18 '14 04:07

user508633


1 Answers

It looks like the command line compiler have some difficulties handling at the same time

  • the fact that BoundedI2 is generic on T which must be a "Bound"
  • the fact that Interface2 is extending Interface1

At least without instanciating BoundedI2 correctly. What is strange indeed is that Eclipse configured on the same JDK compiles it just fine... Note that Eclipse uses it's internal compiler in order to handle incremental recompulation while you type, so it does not invoke the JDK's compiler at all (see org/eclipse/jdt/internal/compiler package).

This modification makes it compile ok both in Eclipse and in the command line, by forcing BoundedI2 to be on a concrete type rather than on a type inference:

import java.util.List;

public class PerfTest {
    interface Interface1<T> {}
    interface Interface2<T> extends Interface1<T> {}
    static class Bound {}
    interface BoundedI1<T extends Bound> extends Interface1<T> {}
    interface BoundedI2<T extends Bound> extends Interface2<T> {}
    static class Actual extends Bound {}

    public static void main(String[] args) {
        test((List<BoundedI2<Actual>>) null);
        test2((List<BoundedI2<Actual>>) null);
    }

    public static void test(List<? extends Interface2<? extends Bound>> list) { test2(list); }

    public static void test2(List<? extends Interface1<? extends Bound>> list) {}
}
like image 167
Eric Nicolas Avatar answered Nov 15 '22 14:11

Eric Nicolas