Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inferred type does not conform to equality constraint error for unrelated variables

I have the following piece of code

public class TeeingCollector {

    public static void main(String[] args) {
        //        var strs = List.of("abc");
        var dividedStrings = Stream.of("foo", "hello", "bar", "world")
            .collect(Collectors.teeing(
                        Collectors.filtering((String s) -> s.length() <= 3, Collectors.toList()),
                        Collectors.filtering((String s) -> s.length() > 3, Collectors.toList()),
                        List::of
                        ));
        System.out.println(dividedStrings);
    }
    private static class Employee {
        boolean isActive;

        public Employee(boolean isActive) {
            this.isActive = isActive;
        }

        public boolean isActive() {
            return isActive;
        }

        @Override
            public String toString() {
                return "Employee{" +
                    "isActive=" + isActive +
                    '}';
            }
    }
    private static class MaxMin {
        int max;
        int min;

        MaxMin(int max, int min) {
            this.max = max;
            this.min = min;
        }

        @Override
            public String toString() {
                return "MaxMin{" +
                    "max=" + max +
                    ", min=" + min +
                    '}';
            }
    }
}

If I execute that class from terminal with java src/TeeingCollector.java I get the following error:

src/TeeingCollector.java:14: error: incompatible types: inferred type does not conform to equality constraint(s)
            .collect(Collectors.teeing(
                    ^
inferred: List<String>
equality constraints(s): List<Object>,R
where R,T,A are type-variables:
R extends Object declared in method <T,A,R>filtering(Predicate<? super T>,Collector<? super T,A,R>)
T extends Object declared in method <T,A,R>filtering(Predicate<? super T>,Collector<? super T,A,R>)
A extends Object declared in method <T,A,R>filtering(Predicate<? super T>,Collector<? super T,A,R>)
1 error
error: compilation failed

If I uncomment the line var strs = List.of("abc"); then the code is executed without any problem.

Java version (for macOS):

OpenJDK Runtime Environment (build 12+33)
OpenJDK 64-Bit Server VM (build 12+33, mixed mode, sharing)

running the same code with the following version (older) yields no errors

OpenJDK Runtime Environment (build 12-ea+23)
OpenJDK 64-Bit Server VM (build 12-ea+23, mixed mode, sharing)

Note: if I compile it then run, I don't have any errors with both builds for macOS, so it seems that only java TeeingCollector.java isn't working properly

like image 597
Adrian Avatar asked Apr 03 '19 12:04

Adrian


1 Answers

TL;DR this is obviously a bug, as the compiler’s behavior depends on entirely unrelated things, including environmental aspects outside the Java language.

I simplified your example and integrated implementations of Collectors.teeing and Predicate.not into the example, to be able to test your code with Java versions from JDK 9 to JDK 12.

Note that while I first thought that this was some kind of interaction of var’s type inference with the generic construct on the right-hand-side, more tests revealed that the problem even exists when using explicit types for the variables, which allowed to include JDK 9 in the tests.

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

import java.util.stream.Collector;

public class Temp5 {

  public static void main(String[] args) {
    // List<Character> strs = List.of("abc");
    List<List<Character>> lettersAndNumbers = Stream.of('5', 't', 'o', '9', 'p', '1', 'h')
        .collect(teeing(
                Collectors.filtering(Character::isLetter, Collectors.toList()),
                Collectors.filtering(not(Character::isLetter), Collectors.toList()),
                List::of    
        ));
    }

    public static <T, R1, R2, R>
    Collector<T, ?, R> teeing(Collector<? super T, ?, R1> downstream1,
                              Collector<? super T, ?, R2> downstream2,
                              BiFunction<? super R1, ? super R2, R> merger) {
        return teeing0(downstream1, downstream2, merger);
    }

    private static <T, A1, A2, R1, R2, R>
    Collector<T, ?, R> teeing0(Collector<? super T, A1, R1> downstream1,
                               Collector<? super T, A2, R2> downstream2,
                               BiFunction<? super R1, ? super R2, R> merger) {
        Objects.requireNonNull(downstream1, "downstream1");
        Objects.requireNonNull(downstream2, "downstream2");
        Objects.requireNonNull(merger, "merger");

        Supplier<A1> c1Supplier = Objects.requireNonNull(downstream1.supplier(), "downstream1 supplier");
        Supplier<A2> c2Supplier = Objects.requireNonNull(downstream2.supplier(), "downstream2 supplier");
        BiConsumer<A1, ? super T> c1Accumulator =
                Objects.requireNonNull(downstream1.accumulator(), "downstream1 accumulator");
        BiConsumer<A2, ? super T> c2Accumulator =
                Objects.requireNonNull(downstream2.accumulator(), "downstream2 accumulator");
        BinaryOperator<A1> c1Combiner = Objects.requireNonNull(downstream1.combiner(), "downstream1 combiner");
        BinaryOperator<A2> c2Combiner = Objects.requireNonNull(downstream2.combiner(), "downstream2 combiner");
        Function<A1, R1> c1Finisher = Objects.requireNonNull(downstream1.finisher(), "downstream1 finisher");
        Function<A2, R2> c2Finisher = Objects.requireNonNull(downstream2.finisher(), "downstream2 finisher");

        Collector.Characteristics[] characteristics;
        Set<Collector.Characteristics> c1Characteristics = downstream1.characteristics();
        Set<Collector.Characteristics> c2Characteristics = downstream2.characteristics();
        EnumSet<Collector.Characteristics> c = EnumSet.noneOf(Collector.Characteristics.class);
        c.addAll(c1Characteristics);
        c.retainAll(c2Characteristics);
        c.remove(Collector.Characteristics.IDENTITY_FINISH);
        characteristics = c.toArray(new Collector.Characteristics[0]);

        class PairBox {
            A1 left = c1Supplier.get();
            A2 right = c2Supplier.get();
            void add(T t) {
                c1Accumulator.accept(left, t);
                c2Accumulator.accept(right, t);
            }
            PairBox combine(PairBox other) {
                left = c1Combiner.apply(left, other.left);
                right = c2Combiner.apply(right, other.right);
                return this;
            }
            R get() {
                R1 r1 = c1Finisher.apply(left);
                R2 r2 = c2Finisher.apply(right);
                return merger.apply(r1, r2);
            }
        }
        return Collector.of(PairBox::new, PairBox::add, PairBox::combine, PairBox::get, characteristics);
    }
    @SuppressWarnings("unchecked")
    static <T> Predicate<T> not(Predicate<? super T> target) {
        Objects.requireNonNull(target);
        return (Predicate<T>)target.negate();
    }
}

The results are disastrous. Every version can have a different opinion about the correctness of the code, even when just changing the minor version. Well, even using OpenJDK instead of Oracle’s distribution can have a different outcome. Additionally, even the smallest changes to the code can affect it. As you’ve noted, changing the comment into an actual declaration may change the outcome, but even removing the comment changes the outcome for some JDK versions. Just changing the values, like using Stream.of('5', 't', 'o', '9', 'p', '1') instead of Stream.of('5', 't', 'o', '9', 'p', '1', 'h') changes the outcome for some versions.

My conclusion is that something in the compiler implementation depends on something stable for a particular setup, but actually unpredictable, like the iteration order of a HashMap. And the JDK version itself seems to be part of it. This would also explain why the outcome may change when using MacOS instead of Linux or Windows. Even compiling this source file together with another, unrelated source file can change the result.

like image 139
Holger Avatar answered Oct 18 '22 00:10

Holger