Search code examples
javajavacecj

Why can't the eclipse java compiler (ecj) compile this?


I have the following code:

package test;

import java.util.stream.IntStream;

public class A {
    public static void main(String[] args) {
        IntStream.range(0, 10).mapToObj(n -> new Object() {
            int i = n;
        }).mapToInt(o -> o.i).forEachOrdered(System.out::println);
    }

}

This code works fine when compiled with javac 1.8.0_101, and produces the number 0 to 9 as expected.

But when I use this code in eclipse, it tells me that at o.i:

i cannot be resolved or is not a field

And producing an error when executing this:

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    i cannot be resolved or is not a field

    at test.A.main(A.java:9)

Why do I need to use javac to compile this code?
And how do I get eclipse to behave?

Edit:

I did some tests and it works in ecj as long as I don't create the instance in a lambda:

package test;

import java.util.Optional;
import java.util.function.Supplier;

public class B {
    public static void main(String[] args) {

        // This works fine:
        System.out.println(new Object() {
            int j = 5;
        }.j);

        // This also
        System.out.println(trace(new Object() {
            int j = 5;
        }).j);

        // Also no problem
        System.out.println(unwrapAndTrace(Optional.of(new Object() {
            int j = 5;
        })).j);

        // Lambdas work:
        System.out.println(((Supplier & Serializable) () -> new Object()).get()); 

        // This doesn't work.
        System.out.println(invokeAndTrace(() -> new Object() {
            int j = 5;
        }).j);
    }

    public static <T> T trace(T obj) {
        System.out.println(obj);
        return obj;
    }

    public static <T> T invokeAndTrace(Supplier<T> supplier) {
        T result = supplier.get();
        System.out.println(result);
        return result;
    }

    public static <T> T unwrapAndTrace(Optional<T> optional) {
        T result = optional.get();
        System.out.println(result);
        return result;
    }


}

Solution

  • This is a bug in ecj, recently reported also as Bug 535969.

    In a nutshell: to avoid a hard technical problem, the compiler drops the anonymous class during type inference, replacing it with its super class (in specific situations, not always). With this, the result of mapToObj() is seen as Stream<Object> where indeed the anonymous class should be used. Original assessment, that this information loss would be OK (because nobody can mention the anonymous class) is proven wrong by the example in this question.

    EDIT: The bug has been fixed via the pre-existing report Bug 477894