JDK-7045341 : Left subtyping argument must be captured
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 7,8
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2011-05-16
  • 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 :  
Relates :  
Description
Per JLS 4.10.2, a subtyping test that involves indentifying the direct supertypes of a wildcard-parameterized type must capture the type before performing a substitution:

"The direct supertypes of the type C<T1,...,Tn>, where Ti (1 ��� i ��� n) is a type, are D<U1 ��,...,Uk ��>, where:
��� D<U1,...,Uk> is a direct supertype of C<F1,...,Fn>, and �� is the substitution [F1:=T1,...,Fn:=Tn].
��� C<S1,...,Sn> where Si contains Ti (��4.5.1) for 1 ��� i ��� n.

"The direct supertypes of the type C<R1,...,Rn>, where at least one of the Ri (1 ��� i ��� n) is a wildcard type argument, are the direct supertypes of C<X1,...,Xn>, where C<X1,...,Xn> is the result of applying capture conversion (��5.1.10) to C<R1,...,Rn>."

javac does not do this; as a result, unsound results are permitted.  The following program compiles without error and crashes at runtime because the subtyping implementation allows C<?> <: A<Box<?>>.

public class WildSubstitution {

static class Box<T> {
 private T val;
 public Box(T val) { this.val = val; }
 public T get() { return val; }
 public void set(T val) { this.val = val; }
}

interface A<T> { T get(); void set(T arg); }
interface B<T> extends A<T> {}
interface C<S> extends A<Box<S>> {}

static class D implements B<String> {
 String s;
 public String get() { return s; }
 public void set(String s) { this.s = s; }
}

static class E implements C<String> {
 Box<String> b;
 public Box<String> get() { return b; }
 public void set(Box<String> arg) { b = arg; }
}

public static void main(String... args) {
 // B<?> <: A<?> is true
 Box<D> b1 = new Box<D>(new D());
 Box<? extends B<?>> b2 = b1;
 Box<? extends A<?>> b3 = b2;

 // C<?> <: A<Box<?>> is false
 Box<E> b4 = new Box<E>(new E());
 Box<? extends C<?>> b5 = b4;
 Box<? extends A<Box<?>>> b6 = b5;

 b6.get().set(new Box<Integer>(10));
 String s = b4.get().get().get();
}

}

Comments
Interestingly, array types are handled correctly: void test(C<?>[] arg) { A<Box<?>>[] b = arg; // expected: error; actual: error }
11-12-2015

Simplified test case (without trying to demonstrate why this is unsound): interface Box<T> {} interface A<T> {} interface C<S> extends A<Box<S>> {} void test(Box<C<?>> arg) { Box<? extends A<Box<?>>> b = arg; // expected: error; actual: okay }
11-12-2015