Ramses Ramses - 1 month ago 10
Java Question

Java type error when compiling code with javac 8 which worked fine with javac 6

I have this class, which is a simplification of some code I found in a project that is being ported from Java 6 to Java 8:

public class Unification {

final class Box<A> {}

final class MyMap<A, B extends Box<? extends A>> {}

MyMap<?, ?> getMap() {
return new MyMap<Object, Box<Object>>();
}

<A, B extends Box<? extends A>> void setMap(final MyMap<A, B> m) {}

void compileError() {
setMap(getMap());
}

}


It's a very small example to merely showcase the problem, the actual code makes a bit more sense. The issue seems to be quite general though, hence the abstract example. The core issue is the following: for some reason, javac does not want to accept an expression with type
MyMap<?, ?>
as the argument to the
setMap()
method, even though this should, according to my understanding, be well-typed.

The code compiles without errors with javac 6, but I get this obscure error message when I use javac 8:

C:\System9\KWS_sparse\sourcesNG\Domain\src\uz\Unification.java (21:9) error: method setMap in class Unification cannot be applied to given types;
required: Unification.MyMap<A,B>
found: Unification.MyMap<CAP#1,CAP#2>
reason: inference variable A has incompatible bounds
equality constraints: CAP#1
lower bounds: Object
where A,B are type-variables:
A extends Object declared in method <A,B>setMap(Unification.MyMap<A,B>)
B extends Unification.Box<? extends A> declared in method <A,B>setMap(Unification.MyMap<A,B>)
where CAP#1,CAP#2 are fresh type-variables:
CAP#1 extends Object from capture of ?
CAP#2 extends Unification.Box<? extends CAP#1> from capture of ?


The error message seems to indicate that no unification can be found for the first type parameter of
MyMap
, javac cannot find a type that unifies with both
CAP#1
, which represents the first wildcard argument in the actual parameter to
setMap()
, and
A
, which is the corresponding type parameter of the formal parameter of
setMap()
. Even though it seems to me that
A
and
CAP#1
should be perfectly unifiable, they would then both represent the existential type introduced by erasing the actual types in the signature of
getMap()
.

Can anyone spot what is going wrong here? Was javac 6 erroneously accepting this code? Also, is there a not-too-intrusive (and javac 6 compatible) way to guide javac 8 towards the right unification?

EDIT: I tried the suggestion to introduce a variable from stackoverflow.com/questions/23063474/ but that does not seem to help, I get the same compile error.

EDIT2: Clarified the "intent" of the example code.

EDIT3: renamed
Map
to
MyMap
, apparently it is too confusing to define a custom
Map
type.

Answer

The problem can be circumvented by introducing an intermediate method and thereby forcing javac to do the type inference in two stages. The first method only takes one type parameter and leaves the second one as a wildcard. Javac is able to correctly infer the single type parameter to this method. The second, and original, method is then able to correctly infer the second type parameter from the supplied argument.

public class Unification {

    final class Box<A> {}

    final class MyMap<A, B extends Box<? extends A>> {}

    MyMap<?, ?> getMap() {
        return new MyMap<Object, Box<Object>>();
    }

    <A> void setMap(final MyMap<A, ?> m) {
        doSetMap(m);
    }

    <A, B extends Box<? extends A>> void doSetMap(final MyMap<A, B> m) {}

    void worksFineNow() {
        setMap(getMap());
    }

}