SceDev
SceDev

Reputation: 47

Code compiling with eclipse but not with javac

The code below compiles without any error with Eclipse but generates an error with Javac. It seems to be a compiler error but I don't know which one is right.

I would like to point out that I know how to correct this error by changing the code to make it work with both, but that is not the current topic. I would just like to know whether it's a java or an eclipse problem.

I try with Intellij but I have the same javac error.

Sample code to reproduce this error:

import java.util.ArrayList;
import java.util.List;

public class A<T extends B> {
    protected List<C> list = new ArrayList<>();

    class C {}

    public void createIO() {
        A<? extends B> x = null;
        List<A<? extends B>.C> y = x.list;
    }
}

class B {
}

JVM:

openjdk version "13-BellSoft" 2019-09-17
OpenJDK Runtime Environment (build 13-BellSoft+33)
OpenJDK 64-Bit Server VM (build 13-BellSoft+33, mixed mode, sharing)

With Eclipse, I don't have any errors. With Javac, I have the error:

A.java:13: error: incompatible types: List<A<CAP#1>.C> cannot be converted to List<A<? extends B>.C>
        List<A<? extends B>.C> y = x.list;
                                    ^
  where CAP#1 is a fresh type-variable:
    CAP#1 extends B from capture of ? extends B

Upvotes: 2

Views: 409

Answers (3)

Stephan Herrmann
Stephan Herrmann

Reputation: 8178

This is indeed a bug in ecj, but the reasons for this are to be found fairly deep inside JLS.

In order to understand the issue, we first need to see that C indeed is a generic type, since it is declared within the scope of type variable <T>. Here I agree with the other answer. (Note that this would not apply if C were declared static).

As a next step let's expand a few types that are abbreviated in the source code:

The field list has the following type: List<A<T>.C>

To determine the type of the field access x.list we need to consult the type of x, which is A<#capture-of ? extends B>. We use this to instantiate <T>, which yields: List<A<#capture-of ? extends B>>.

This latter type looks very similar to the required type List<A<? extends B>>, the unknown type (capture resp. wildcard) appears in the exact same locations left and right.

The next concept needed for explanation is type argument containment as defined in JLS §4.5.1, which is a necessary condition for compatibility between two parameterized types with different type arguments.

The detail, that was not respected by ecj in this particular situation, is the fact that §4.5.1 looks only at direct type arguments and does not descend into nested type arguments. So while #capture-of ? extends B is contained in ? extends B, the parameterized type A<#capture-of ? extends B> is only a subtype of A<? extends B>, neither is contained in the other. From this we see that the full types List<..> are indeed incompatible.

ecj correctly respects these rules for immediate type arguments, but got confused when analysing the type arguments of the outer type A.

Edit:

The ecj bug has been fixed, an integration build with the fix is available at https://download.eclipse.org/eclipse/downloads/index.html

Upvotes: 1

Oleksandr Pyrohov
Oleksandr Pyrohov

Reputation: 16276

The Eclipse compiler should show a compiler error like javac does. See Eclipse bug 539105.

javac behavior is correct and is aligned with the JLS:

And if a generic class C<T> has a non-generic member class D, then the member type C<String>.D is a parameterized type, even though the class D is not generic.

Thus, even though the inner class C is not generic, the:

A<? extends B>.C

is a parameterized type, where the type parameter of A is an unknown subtype of B. So, all rules on Generics apply to C as well in this context.

Thus, to eliminate compiler error, the type of y can be declared as follows:

List<? extends A<? extends B>.C> y = x.list;

But keep in mind that this won't allow you to add items to the list y (see PECS).

If it sounds complicated, try to think about it in scope of a simplified example:

List<Integer> l1 = new ArrayList<>();
List<Number > l2 = l1; // Error: incompatible types...

List<? extends Number> l3 = l1; // OK

Additional details:

The example in the question can be minimized to:

class A<T> {
    class C {}
    List<C> l1 = null;
    List<A<?>.C> l2 = l1; // Error: incompatible types...
    List<? extends A<?>.C> l3 = l1; // OK
}

List<A<?>.C> is a list of C of A of unknown type; C of arbitrary A can be added:

class A<T> {
    class C {}
    C c = new C();
    void foo(List<A<?>.C> list) {
        list.add(new A<String>().c);
        list.add(new A<Number>().c);
    }
}

See also:

Upvotes: 0

might be Idea and Eclipse are using their own compilers.

Upvotes: 0

Related Questions