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