Lukas Eder
Lukas Eder

Reputation: 221115

Lower-bounded wild card causes trouble in javac, but not Eclipse

This piece of code compiles in Eclipse but not in javac:

import java.util.function.Consumer;

public class Test {
    public static final void m1(Consumer<?> c) {
        m2(c);
    }
    private static final <T> void m2(Consumer<? super T> c) {
    }
}

javac output:

C:\Users\lukas\workspace>javac -version
javac 1.8.0_92

C:\Users\lukas\workspace>javac Test.java
Test.java:5: error: method m2 in class Test cannot be applied to given types;
        m2(c);
        ^
  required: Consumer<? super T>
  found: Consumer<CAP#1>
  reason: cannot infer type-variable(s) T
    (argument mismatch; Consumer<CAP#1> cannot be converted to Consumer<? super T>)
  where T is a type-variable:
    T extends Object declared in method <T>m2(Consumer<? super T>)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
1 error
----------------------------------------------------------------

Which compiler is wrong and why? (Eclipse bug report and parts of discussion here)

Upvotes: 17

Views: 367

Answers (2)

Holger
Holger

Reputation: 298459

Note that the following can be compiled without problems:

public class Test {
    public static final void m1(Consumer<?> c) {
        m2(c);
    }
    private static final <T> void m2(Consumer<T> c) {
    }
}

Despite we don’t know the actual type of the consumer, we know that it will be assignable to Consumer<T>, though we don’t know what T is (not knowing what T is, is the norm in generic code anyway).

But if the assignment to Consumer<T> is valid, the assignment to Consumer<? super T> would be as well. We can even show this practically with an intermediate step:

public class Test {
    public static final void m1(Consumer<?> c) {
        m2(c);
    }
    private static final <T> void m2(Consumer<T> c) {
        m3(c);
    }
    private static final <T> void m3(Consumer<? super T> c) {
    }
}

No compiler objects that.

It will also be accepted when you replace the wild card with a named type, e.g.

public class Test {
    public static final void m1(Consumer<?> c) {
        m2(c);
    }
    private static final <E,T extends E> void m2(Consumer<E> c) {
    }
}

Here, E is a super type of T, just like ? super T is.

I tried to find the bug report for javac closest to this scenario, but when it comes to javac and wildcard types, there are so many of them, that I eventually gave up. Disclaimer: this does not imply that there are so many bugs, just that there are so many related scenarios reported, which may all be different symptoms of the same bug.

The only thing that matters, is, that it is already fixed in Java 9.

Upvotes: 6

Stephan Herrmann
Stephan Herrmann

Reputation: 8178

This code is legal wrt JLS 8. javac version 8 and earlier had several bugs in how they handle wildcards and captures. Starting with version 9 (early access, I tried version ea-113 and newer) also javac accepts this code.

To understand how a compiler analyzes this according to JLS, it is essential to tell apart what are wildcard captures, type variables, inference variables and such.

The type of c is Consumer<capture#1-of ?> (javac would write Consumer<CAP#1>). This type is unknown, but fixed.

The parameter of m2 has type Consumer<? super T>, where T is a type variable to be instantiated by type inference.

During type inference an inference variable, denoted by ecj as T#0, is used to represent T.

Type inference consists in determining, whether T#0 can be instantiated to any type without violating any given type constraints. In this particular case we start with this contraint:

⟨c → Consumer<? super T#0>⟩

Which is stepwise reduced (by applying JLS 18.2):

⟨Consumer<capture#1-of ?> → Consumer<? super T#0>⟩

⟨capture#1-of ? <= ? super T#0⟩

⟨T#0 <: capture#1-of ?⟩

T#0 <: capture#1-of ?

The last line is a "type bound" and reduction is done. Since no further constraints are involved, resolution trivially instantiates T#0 to the type capture#1-of ?.

By these steps, type inference has proven that m2 is applicable for this particular invocation. qed.

Intuitively, the shown solution tells us: whatever type the capture may represent, if T is set to represent the exact same type, no type constraints are violated. This is possible, because the capture is fixed before starting type inference.

Upvotes: 12

Related Questions