ivar dependencies during resolution

Stephan Herrmann stephan.herrmann at berlin.de
Tue Jan 6 21:53:47 UTC 2026


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