Search code examples
javaeclipsejavacecj

Eclipse Java compiler infers the wrong generic type?


I wonder if this is a bug in ECJ or a valid interpretation of the JLS.

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Supplier;

public class GenericsTest {
    public void testInference() {
        Set<Object> set1 = getCollection(HashSet::new);
        Set<Object> set2 = getCollection2(HashSet::new);
    }

    public static <E, C extends Collection<E>> C getCollection(Supplier<C> collectionSupplier) {
        return collectionSupplier.get();
    }

    public static <E, C extends Collection<E>> C getCollection2(CollectionSupplier<E, C> collectionSupplier) {
        return collectionSupplier.get();
    }

    public interface CollectionSupplier<E, C extends Collection<E>> {
        C get();
    }
}

Javac (11.0.11) compiles everything (correctly, I would say).

ECJ (4.20.0) fails to compile the getCollection2(HashSet::new) call with error "Type mismatch: cannot convert from Collection to Set".

The getCollection(HashSet::new) call is not a problem for any compiler.

If I apply the suggested quickfix and insert a cast to HashSet<Object>, I get a different error from ECJ: "Problem detected during type inference: Unknown error at invocation of getCollection2(GenericsTest.CollectionSupplier<Object,Collection>)"

There are lots of similar questions here and bugs at bugs.eclipse.org, but most examples seemed to involve ?.


Solution

  • Please report the issue you get with the cast to Eclipse JDT.

    As a workaround, you can give the Eclipse compiler a hint as follows:

    Set<Object> set2 = getCollection2((CollectionSupplier<Object, Set<Object>>) HashSet::new);
    

    The tricky part for the compiler is to check whether what getCollection2 returns is compatible to Set<Object>. But to be able to do that, the type parameter of the HashSet::new has to be known, and this is determined from Set<Object> (in this case, HashSet::new creates an instance of HashSet<Object>). So, the type parameter must be determined from the opposite direction than checking whether the return type is compatible with the declared type for which the type parameter is required.

    It seems, Eclipse fails to compute the type parameter of HashSet::new. ObjectHashSet::new with static class ObjectHashSet extends HashSet<Object> {} works. GenericsTest::newObjectHashSet with static Set<Object> newObjectHashSet() { return new HashSet<Object>(); } works also, but fails with static <T> Set<T> newObjectHashSet() { return new HashSet<T>(); }.