This code compiles using ecj but not javac. Is this a bug in ecj, javac or neither?
Asked Answered
B

2

12

The following code creates a Collector that produces an UnmodifiableSortedSet:

package com.stackoverflow;

import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class SOExample {

    public static <T extends Comparable<T>> Collector<T, ?, SortedSet<T>> toSortedSet() {
        return Collectors.toCollection(TreeSet::new);
    }

    public static <T extends Comparable<T>> Collector<T, ?, SortedSet<T>> toUnmodifiableSortedSet() {
        return Collectors.collectingAndThen(toSortedSet(), Collections::<T> unmodifiableSortedSet);
    }
}

The codes compiles under the ecj compiler:

$ java -jar ~/Downloads/ecj-3.13.101.jar -source 1.8 -target 1.8 SOExample.java
Picked up _JAVA_OPTIONS: -Duser.language=en -Duser.country=GB

Under javac however:

$ javac -version
Picked up _JAVA_OPTIONS: -Duser.language=en -Duser.country=GB
javac 1.8.0_73

$ javac SOExample.java
Picked up _JAVA_OPTIONS: -Duser.language=en -Duser.country=GB
SOExample.java:16: error: method collectingAndThen in class Collectors cannot be applied to given types;
        return Collectors.collectingAndThen(toSortedSet(), Collections::<T> unmodifiableSortedSet);
                         ^
  required: Collector<T#1,A,R>,Function<R,RR>
  found: Collector<T#2,CAP#1,SortedSet<T#2>>,Collection[...]edSet
  reason: cannot infer type-variable(s) T#3
    (actual and formal argument lists differ in length)
  where T#1,A,R,RR,T#2,T#3 are type-variables:
    T#1 extends Object declared in method <T#1,A,R,RR>collectingAndThen(Collector<T#1,A,R>,Function<R,RR>)
    A extends Object declared in method <T#1,A,R,RR>collectingAndThen(Collector<T#1,A,R>,Function<R,RR>)
    R extends Object declared in method <T#1,A,R,RR>collectingAndThen(Collector<T#1,A,R>,Function<R,RR>)
    RR extends Object declared in method <T#1,A,R,RR>collectingAndThen(Collector<T#1,A,R>,Function<R,RR>)
    T#2 extends Comparable<T#2>
    T#3 extends Object declared in method <T#3>unmodifiableSortedSet(SortedSet<T#3>)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
1 error

If I change the offending line to the following, the code compiles under both compilers:

return Collectors.collectingAndThen(toSortedSet(), (SortedSet<T> p) -> Collections.unmodifiableSortedSet(p));

Is this a bug in ecj, javac or an underspecification that allows for both behaviours?

Javac behaves the same in java 9 and 10.

Brecher answered 10/4, 2018 at 21:1 Comment(4)
It compiles without the use of toSortedSet as well: return Collectors.collectingAndThen(Collectors.toCollection(TreeSet::new), Collections::<T>unmodifiableSortedSet);Calton
"actual and formal argument lists differ in length" I can't think what lists those would be.Hotien
Jacob, great observations, very strange behaviour! Surely a javac bug?Brecher
It certainly looks like a javac bug to me - the error message makes no sense.Assumptive
B
3

Oracle has accepted this as a compiler bug.

Brecher answered 17/4, 2018 at 8:51 Comment(0)
C
2

Interestingly, it compiles without the need for toSortedSet:

public static <T extends Comparable<T>> Collector<T, ?, SortedSet<T>> toUnmodifiableSortedSet() {
    return Collectors.collectingAndThen(Collectors.toCollection(TreeSet::new), Collections::<T>unmodifiableSortedSet);
}

It also compiles if you explicitly pass T to toSortedSet (removing static and using this.<T>toSortedSet() works just as well):

public static <T extends Comparable<T>> Collector<T, ?, SortedSet<T>> toUnmodifiableSortedSet() {
    return Collectors.collectingAndThen(Test.<T>toSortedSet(), Collections::<T>unmodifiableSortedSet);
}

Regarding your question as to why it doesn't compile as is, I suspect it has to do with the capture types not being the same between both methods, and toSortedSet needs the same exact type of T used in toUnmodifiableSortedSet (as you define a generic type T for both methods).

I further believe that's the reason, as you can define a generic type T for your class and use it in both methods (if you remove static):

public class Test<T extends Comparable<? super T>> {
    public static void main(String[] args) throws Exception {
        System.out.println(Stream.of(5, 3, 4, 2, 1, 5, 4, 3, 2, 1)
            .collect(new Test<Integer>().toUnmodifiableSortedSet()));
    }

    public Collector<T, ?, SortedSet<T>> toSortedSet() {
        return Collectors.toCollection(TreeSet::new);
    }

    public Collector<T, ?, SortedSet<T>> toUnmodifiableSortedSet() {
        return Collectors.collectingAndThen(toSortedSet(), Collections::unmodifiableSortedSet);
    }
}

And the above compiles and runs just fine.

Calton answered 10/4, 2018 at 21:19 Comment(10)
Thanks Jacob, do you think it's a bug in javac?Brecher
I don't think so; I think it's just because you're defining two different generic types, T, for each method.Calton
So ecj shouldn't compile it?Brecher
I would think not, unless ecj implicitly infers that the T defined in toUnmodifiableSortedSet should be the one used in toSortedSet.Calton
This explanation seems to have two problems to me: 1. Why does return Collectors.collectingAndThen(toSortedSet(), (SortedSet<T> p) -> Collections.unmodifiableSortedSet(p)); not run into the same problem?Hotien
2. Why is javac complaining about failing to infer the T#3 defined in unmodifiableSortedSet, not in toSortedSet? Why is it inferring it at all when it's given explicitly?Hotien
@AlexeyRomanov To go along with my answer, the only explanation that I would have for (1.) is that explicitly defining p as a SortedSet<T> would allow the compiler to infer toSortedSet, but that still doesn't make much sense. I feel like this could potentially be a javac bug, but I don't want to assume anything too quickly.Calton
@JacobG. I agree about not assuming, but can't currently think of anything even approaching an explanation for the error message.Hotien
@JacobG. (And of course would be very happy to see such an explanation)Hotien
@AlexeyRomanov I agree. I'll try looking into it further, but I don't promise I'll find anything. The compiler's error messages regarding generics are notoriously bewildering, even if the architects don't agree :PCalton

© 2022 - 2024 — McMap. All rights reserved.