resolving several ivars to the same capture?
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Wed Oct 22 10:05:59 UTC 2025
Hi Stephan,
first, I'll note that this issue seems dangerously close to this open
spec issue:
https://bugs.openjdk.org/browse/JDK-8016196
Then, to the specifics of your query, the crux of the problem here is to
determine how this subexpression is type checked:
new A<>(c)
Because that determines T, and the type of T then determines whether
calling intValue in the other lambda makes sense.
Now, to check the above expression we have to prove that the type of `c`
is compatible with the formal parameter of the A's constructor, so,
something like:
C<B<?>> <: C<? extends C<U>> (where U is an inference variable here)
This seems to lead to:
B<?> <: C<U>
B<#CAP> <: C<U>, where #CAP <: Number // CAPTURE HERE!
U = #CAP
So, the type we infer for the instance creation expression would be
A<#CAP>. Note that this capture variable does have an upper bound
(Number), which you didn't report in your ecj dump, but I assume it to
be there (otherwise the second lambda would fail to type check).
Now, the second step of the subtyping proof above requires capture,
which is the spec issue I mentioned above. I think javac went back and
forth a bit on whether to apply capture or not (given the open spec
issue), and perhaps landed on a slightly different place than ecj.
We have other examples where applying capture conversion during
incorporation this way leads to issues:
https://bugs.openjdk.org/browse/JDK-8206142
(That said, I would have expected javac to perform a capture there --
@Vicente, when you have time, can you please take a look and see why
javac is not capturing?)
Thanks
Maurizio
On 21/10/2025 20:26, Stephan Herrmann wrote:
> Once more, I have a bunch of tests on my desk where javac and ecj
> disagree.
>
> The pending fix for one case, makes ecj accept the following program,
> which is rejected by javac:
>
> //---
> import java.util.function.Function;
> public class Test {
> public static void main(String[] args) {
> C<B<?>> c = null;
> m(
> _ -> new A<>(c),
> b -> b.intValue());
> }
> static <T, R> void m(Function<B<Number>, A<T>> f1, Function<T, R>
> f2) {}
> static class A<U> {
> public A(C<? extends C<U>> t) {}
> }
> private record B<V extends Number>(V t) implements C<V> {
>
> }
> private interface C<W> {
> W t();
> }
> }
> //---
>
> javac reports:
> Test.java:5: error: cannot infer type arguments for A<>
> _ -> new A<>((C<B<?>>) null),
> ^
> reason: cannot infer type-variable(s) U
> (argument mismatch; C<B<?>> cannot be converted to C<? extends C<U>>)
> where U is a type-variable:
> U extends Object declared in class A
> Test.java:6: error: cannot find symbol
> b -> b.intValue());
> ^
> symbol: method intValue()
> location: variable b of type Object
> 2 errors
>
>
> With my pending fix ecj would accept, with these details:
>
> Outer inference of m() yields:
> T#0 : capture#1-of ?
> R#1 : java.lang.Integer
> U#3 : capture#1-of ?
> so m() resolves as:
> void m(Function<B<Number>,A<capture#1-of ?>>, Function<capture#1-of
> ?,Integer>)
>
> With target types Function<B<Number>,A<capture#1-of ?>> and
> Function<capture#1-of ?,Integer> the two lambdas complete resolution
> just fine.
>
>
> Am I missing any detail, why this inference solution is not valid?
>
>
> FYI, the pending fix relates to capturing while computing super types
> of A<?>. While normally we ensure uniqueness of captures per source
> location, when type inference requests a capture for supertype
> computation we use the current invocation as the point of reference,
> i.e., repeatedly capturing the same wildcard during one invocation of
> type inference will share one unified capture. Is this OK? Or what are
> the rules for uniqueness vs. unification in this case?
>
>
>
> thanks,
> Stephan
More information about the compiler-dev
mailing list