Type Inference Question

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Wed May 30 07:34:29 PDT 2012


On 30/05/12 14:12, Jan Finis wrote:
> The second problem is about type inference of capture converted 
> arguments. This code is taken from the openJDK collections library:
>
> class UnmodifiableMap<K,V> {
>     public static <T> Set<T> unmodifiableSet(Set<? extends T> s) {
>         return null;
>     }
>
>     private final Map<? extends K, ? extends V> m;
>     private transient Set<K> keySet;
>     public Set<K> keySet() {
>         if (keySet==null)
>             keySet = UnmodifiableMap.unmodifiableSet(m.keySet()); 
> //This line is the problem
>         return keySet;
>     }
>
>
> }
>
> the problem is the type inference for the unmodifiableSet method call. 
> The actual type of the argument m.keySet() is Set<Capture of ? extends 
> K> and the formal type argument is Set<? extends T>. This imposes the 
> subtype constraint Set<Capture of ? extends K> <: Set<? extends T> 
> which is simplified to Capture of ? extends K <: ? extends T and 
> finally T :> Capture of ? extends K. According to the spec, this final 
> constraint yields lub(Capture of ? extends K) as inferred type for T. 
> If I read the spec correctly, lub(Capture of ? extends K) is Capture 
> of ? extends K, so this very capture is inferred for T. My compiler 
> does this and it produces a compile error, since the result of the 
> method is then Set<Capture of ? extends K>. This result cannot be 
> applied to the variable keySet which is of type Set<K>. Thus, the 
> "correct" type inference should yield K instead of Capture of ? 
> extends K. Since this is code from the standard library which should 
> compile fine, it seems that the usual javac infers that correct bound. 
> But  how? According to the spec, Capture of ? extends K should be 
> inferred. What am I misinterpreting here?
I'll leave the spec-related discussion to our spec gurus - but javac 
infers List<String> in such cases (i.e. no wildcard), as can be 
demonstrated by the following example:

class Test {
<Z> Z m(Z z) { return null; }
     void test(java.util.List<String> ls) {
         ls = m(ls); //ok!
     }
}

Javac has a special shortcut when there's only one constraint - call to 
lub is not even performed there - interestingly this helps having a 
consistent result with the case below (where lub is executed):

class Test {
<Z> Z m(Z z, Z z) { return null; }
     void test(java.util.List<String> ls) {
         ls = m(ls, ls); //ok!
     }
}



Regarding the second problem, javac (and I believe other compilers might 
do this too) takes the capture of the upper bound of an argument type as 
'actual type'. This allows you to type-check the code by avoiding the 
wildcards. There are a number of places where this technique is used - 
and it all has to do with usability of the resulting implementation. As 
the original generic javac implementation has been written when there 
was no spec available, there are few places where the compiler behaves 
differently w.r.t. JLS. During JDK 7 we addressed some of those areas 
and fixed them, partly by clarifying the spec, partly by rectifying the 
compiler implementation. Of course more is needed to bring the two in 
full sync.

Maurizio



More information about the compiler-dev mailing list