JDK-8051807 : JLS 15.12.2.7: Generic type inference fails with once-removed interface impl
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 7u65,9
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: windows_8
  • CPU: x86
  • Submitted: 2014-07-20
  • Updated: 2015-12-11
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbd_majorUnresolved
Related Reports
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.7.0_65"
Java(TM) SE Runtime Environment (build 1.7.0_65-b19)
Java HotSpot(TM) 64-Bit Server VM (build 24.65-b04, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Windows 8 64-bit: Microsoft Windows [Version 6.2.9200]

Originally discovered on Ubuntu, JDK 7u45 (box not accessible at the time)

EXTRA RELEVANT SYSTEM CONFIGURATION :
Nothing relevant (pure compiler bug)

A DESCRIPTION OF THE PROBLEM :
The generic type inference seems to break down in the example code below.  When a class is declared as generic with a bound on the type parameter and also implements an interface with the same parameter, the bound should also apply to the interface.  Succinctly, if class C<T extend B> implements I2<T>, then C<?> should be assignable to I2<? extends B>, since the parameter is known by definition to extend B.  Naturally, if I2<T> extends I1<T> as well, then C<?> should be assignable to I1<? extends B>. 

The compiler inference does work for this case (as evidenced by the test3() call), but seems to break down when this exact case occurs in a generic type parameter instead.  In particular, the compiler fails to verify that List<C<?>> is a subtype of List<? extends I1<? extends B>> during method invocation conversion.  Oddly enough, the compiler is okay with the conversion through an intermediate type List<? extends I2<? extends B>>, which is correct, but seems to limit an arbitrary bound on the supertype type-search.

Linked original discussion: http://stackoverflow.com/questions/24817306/java-bounded-generics-type-inference-bug-method-invocation-jls-15-12-2-7

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile the following code with

javac Main.java

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The code compiles correctly.
ACTUAL -
The compilation fails with the error below.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
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
1 error

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
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);
        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) {}
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
The compiler is okay if the type BoundedI2<?> is declared with the redundant bound specification BoundedI2<? extends Bound>, though the latter adds quite a bit of unnecessary verbosity if the type is widely used and if the bound in question is nontrivial to specify.


Comments
It's probable that the different behavior when there is no nesting (test3) is because the type of the expression is captured before the subtyping test (see JLS 15.16).
30-09-2014

Note: in the first and third call, the compiler succeeds because some information about Bound is carried out in the wildcard type itself (see Types.interfaces and Types.subst); in particular, when we go from BoundedI2 to Interface2, the type substitution machinery makes sure that wildcards will get the bound of the formal type-variable it replaces (the ones from Boundedl2). This trick only works once though, as if we recurse again and try to go from Interface2 to Interface1, this time type-substitution uses Interface2's declared bound to set the wildcards bound, and this is where it all fail - as Interface2 has no bound, type-variable substitution will override the previously correct bound with a bad one (Object). In general, this whole business of keeping track of type-variables from within wildcards is dangerous and should be avoided.
24-07-2014

This seems to be related with missing captures during subtyping: List<B2<?>> <: List<? extends I1<? extends T>> B2<?> <= ? extends I1<? extends T> B2<?> <= I1<? extends T> I1<?> <= I1<? extends T> ? <= ? extends T Object <: T -> false Assigning to Dan who is currently investigating in this area.
24-07-2014