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

1

9

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

Wart answered 3/4, 2019 at 12:52 Comment(11)
Are the Employee and MaxMin classes relevant?Jeep
@AndyTurner Employee and MaxMin are unused, but if I remove them then I can't reproduce the errorWart
I can not reproduce, the program works in both cases for me. I'm also using build 33 of JDK 12.Skiba
@JornVernee, I just checked on Ubuntu and it's ok, but not on macOS. What OS are you using?Wart
I'm on Windows.Skiba
I can reproduce on mac for build 12+33, but I don't understand how uncommenting var strs = List.of("abc"); make it compile in your case; or that you can compile it all : using javac fails too. Are you sure about the example you provided here?Droll
@Droll Yes, it's exactly the same codeWart
@Wart my bad, you are just using java, not compiling first explicitly via javac ... I can confirm to reproduce this on mac. so darn weird... sounds like a bug that should be opened. it's even weirder that javac --release 12 will compile that, but dropping --release will not...Droll
@Droll I reported yesterday this issue via bugreport.java.com/bugreportWart
@Wart what I can say is that the version of java installed via brew cask != from the most recent one... I downloaded the latest one available and it works just fine, but at the same time it's not available in the homebrew repos yet - I tried to upgrade and it's not there yet.Droll
Filed as bugs.openjdk.java.net/browse/JDK-8222035Gregg
S
12

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.

Sidwel answered 5/4, 2019 at 14:29 Comment(4)
Thanks for working the details. Can you provide a little more detail on the versions you tried, and the results you got, so we can reproduce more effectively?Gregg
@BrianGoetz I tried "9.0.4", "10.0.2" 2018-07-17, "11-ea" 2018-09-25, "11" 2018-09-25, "11.0.1" 2018-10-16, "12-ea" 2019-03-19, "12" 2019-03-19, most of them in both, OpenJDK and Oracle version. As said, the result is rather a random pattern, even OpenJDK and OracleJDK of the same version sometimes disagree, then, subtle changes, like inserting a comment or specifying another source file on the command line, can change the outcome completely, to another random pattern. I think, the exact patterns are not really relevant.Sidwel
Thanks. Do you get any sense that, even holding the compiler version and program constant, the result is still nondeterministic?Gregg
@BrianGoetz not in my tests. Given a particular setup, the results seemed to be reproducible. The only exception was inside Netbeans, but I suppose, that’s because it runs the compiler in two different setups, one for syntax highlighting in the editor and one for the actual build in the background.Sidwel

© 2022 - 2024 — McMap. All rights reserved.