ivar dependencies during resolution
Stephan Herrmann
stephan.herrmann at berlin.de
Thu Jan 15 12:00:49 UTC 2026
After spending more time on this issue, I'm quite sure that JLS has no
sufficient means for accepting the below program (which is accepted by javac).
In case this assessment is wrong, it should be straight forward to point out
which of the steps detailed below is wrong.
For now I will assume that JLS _should_ provide means for resolving R#2 in
isolation. In ecj I'm implementing this by tentatively skipping T#3 <: R#2 as a
dependency for R#2. I should note, that other test cases however require to
consider dependencies of this form, so I implemented a fallback: if skipping a
dependency causes resolution to fail then restart *with* those dependencies.
I would really appreciate some feedback to ensure that the three entities JLS,
javac and ecj do not diverge further.
thanks,
Stephan
Am 06.01.26 um 22:53 schrieb Stephan Herrmann:
> Hello,
>
> I am currently revisiting an old test case, where I'm no longer sure, how
> compilers can find the desired inference solution:
>
> //---
> import java.util.*;
> import java.util.stream.*;
> import static java.util.Arrays.asList;
>
> public class C {
> static final List<Integer> DIGITS =
> Collections.unmodifiableList(asList(0,1,2,3,4,5,6,7,8,9));
>
> Collection<String> flatMapSolutions(final boolean b) {
> Collection<String> solutions =
> DIGITS.stream().flatMap( s -> {
> return b ? Stream.empty() : Stream.of("");
> }) .collect(Collectors.toList());
> return solutions;
> }
> }
> //---
>
> javac and released versions of ecj accept this.
>
> With some pending fixes, however, this is how ecj would handle this:
> ivars are:
> R#2 for <R> from flatMap()
> T#3 for <T> from empty()
> T#4 for <T> from of()
>
> flatMap() is inferred as a standalone expression, where resolution starts from
> these type bounds:
> T#3 <: R#2
> R#2 :> java.lang.String
> T#4 :> java.lang.String
> T#4 <: R#2
>
> From this we compute the following dependencies:
> R#2 depends on [R#2, T#3, T#4]
> T#3 depends on [T#3, R#2, T#4]
> T#4 depends on [T#4, R#2, T#3]
> i.e., every ivar depends on every ivar, and whichever ivar we want to resolve,
> we need to resolve them all in one batch.
>
> The first attempt of resolution adds these instantiations:
> T#3 = java.lang.Object
> R#2 = java.lang.String
> T#4 = java.lang.String
> which lets incorporation fail at
> java.lang.String :> java.lang.Object
> derived from
> R#2 = java.lang.String
> R#2 :> java.lang.Object (from T#3 = Object & T#3 <: R#2)
>
> Then during the second attempt we resolve R#2 to a fresh type variable Z#0, with
> lower bound String and upper bound Object.
>
> With this, inference for flatMap() succeeds with a return type of Stream<Z#0>.
>
> That return type is propagated through the collect() call to yield List<Z#0>
> which cannot be assigned to Collection<String>.
>
> Can anyone help me, at which point the above diverges from the desired path of
> inference?
>
> My best guess was, that perhaps R#2 should not be seen as depending on T#3 nor
> T#4: then resolving only R#2 would succeed in the first attempt, leading to an
> inference result of "Stream<String> flatMap(..)" as desired. But I doubt that
> this is how things are meant to work.
>
> thanks,
> Stephan
>
More information about the compiler-dev
mailing list