Inference variable has incompatible bounds. Java 8 Compiler Regression?

后端 未结 3 489
一个人的身影
一个人的身影 2020-12-31 00:56

The following program compiles in Java 7 and in Eclipse Mars RC2 for Java 8:

import java.util.List;

public class Test {

    static final void a(Class

        
相关标签:
3条回答
  • 2020-12-31 01:34

    Thanks for the bug report, and thanks, Holger, for the example in your answer. These and several others finally made me question one small change made in the Eclipse compiler 11 years ago. The point was: Eclipse had illegally extended the capture algorithm to apply recursively to wildcard bounds.

    There was one example where this illegal change perfectly aligned Eclipse behavior with javac. Generations of Eclipse developers have trusted this old decision more than what we could clearly see in JLS. Today I believe that previous deviation must have had a different reason.

    Today I took the courage to align ecj with JLS in this regard and voila 5 bugs that appeared to be extremely hard to crack, have essentially been solved just like that (plus a little tweak here and there as compensation).

    Ergo: Yes, Eclipse had a bug, but that bug has been fixed as of 4.7 milestone 2 :)

    Here's what ecj will report henceforth:

    The method b(List<T>) in the type Test is not applicable for the arguments (capture#1-of ? extends List<?>)
    

    It's the wildcard inside a capture bound that doesn't find a rule to detect compatibility. More precisely, some time during inference (incorporation to be precise) we encounter the following constraint (T#0 representing an inference variable):

    ⟨T#0 = ?⟩
    

    Naively, we could just resolve the type variable to the wildcard, but -- presumably because wildcards are not considered types -- the reduction rules define the above as reducing to FALSE, thus letting inference fail.

    0 讨论(0)
  • 2020-12-31 01:34

    Disclaimer - I don't know enough about the subject, and the following is an informal reasoning of mine to try to justify javac's behavior.


    We can reduce the problem to

    <X extends List<?>> void a(Class<X> type) throws Exception
    {
        X instance = type.newInstance();
        b(instance);  // error
    }
    
    <T> List<T> b(List<T> list) { ... }
    

    To infer T, we have constraints

          X <: List<?>
          X <: List<T>
    

    Essentially, this is unsolvable. For example, no T exists if X=List<?>.

    Not sure how Java7 infers this case. But javac8 (and IntelliJ) behaves "reasonably", I'd say.


    Now, how come this workaround works?

        List<?> instance = type.newInstance();
        b(instance);  // ok!
    

    It works due to wildcard capture, which introduces more type info, "narrowing" the type of instance

        instance is List<?>  =>  exist W, where instance is List<W>  =>  T=W
    

    Unfortunately, this is not done when instance is X, thus there is less type info to work with.

    Conceivably, the language could be "improved" to do wildcard capture for X too:

        instance is X, X is List<?>  =>  exist W, where instance is List<W>
    
    0 讨论(0)
  • 2020-12-31 01:47

    Thanks to bayou.io’s answer we can narrow the problem to the fact that

    <X extends List<?>> void a(X instance) {
        b(instance);  // error
    }
    static final <T> List<T> b(List<T> list) {
        return list;
    }
    

    produces an error while

    <X extends List<?>> void a(X instance) {
        List<?> instance2=instance;
        b(instance2);
    }
    static final <T> List<T> b(List<T> list) {
        return list;
    }
    

    can be compiled without problems. The assignment of instance2=instance is a widening conversion which should also happen for method invocation arguments. So the difference to the pattern of this answer is the additional subtype relationship.


    Note that while I’m not sure whether this specific case is in line with the Java Language Specification, some tests revealed that Eclipse accepting the code is likely due to the fact that it is more sloppy regarding Generic types in general, as the following, definitely incorrect, code could be compiled without any error nor warning:

    public static void main(String... arg) {
        List<Integer> l1=Arrays.asList(0, 1, 2);
        List<String>  l2=Arrays.asList("0", "1", "2");
        a(Arrays.asList(l1, l2));
    }
    static final void a(List<? extends List<?>> type) {
        test(type);
    }
    static final <Y,L extends List<Y>> void test(List<L> type) {
        L l1=type.get(0), l2=type.get(1);
        l2.set(0, l1.get(0));
    }
    
    0 讨论(0)
提交回复
热议问题