Lambda and wildcard

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Thu Oct 9 09:00:01 UTC 2025


Hi Tagir!

Nice cacth.

Here's a slightly different example which reveals a bit more of what's 
happening:

```
interface Main {
     interface X<T> {
         X<T> self();
     }

     static X<?> makeX() {return null;}

     static <R> X<R> create(Supplier<? extends R> supplier) {return null;}

     static X<X<?>> methodRef() {
         var s = (String)create(Main::makeX);
     }

     static X<X<?>> lambda() {
         var s = (String)create(() -> makeX());
     }
}
```

This prints:

```
error: incompatible types: X<X<?>> cannot be converted to String
         var s = (String)create(Main::makeX);
                               ^
error: incompatible types: X<X<CAP#1>> cannot be converted to String
         var s = (String)create(() -> makeX());
                               ^
   where CAP#1 is a fresh type-variable:
     CAP#1 extends Object from capture of ?
```

So, the main difference between the two examples is that in the lambda 
case, the return type of the makeX call is captured. But in the method 
refreene case no capture occurs.

I believe the lambda case works as expected, but the method reference 
case does not.

The JLS mandates a capture of the resolved method return type (JLS 
15.13.2, emphasis mine):

> A method reference expression is /congruent/ with a function type if 
> both of the following are true:
>
>  *
>
>     The function type identifies a single compile-time declaration
>     corresponding to the reference.
>
>  *
>
>      One of the following is true:
>
>      o
>
>         The result of the function type is |void|.
>
>      o
>
>          The result of the function type is R, *and the result of
>         applying capture conversion (§5.1.10
>         <https://docs.oracle.com/javase/specs/jls/se25/html/jls-5.html#jls-5.1.10>)
>         to the return type of the invocation type (§15.12.2.6
>         <https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.12.2.6>)
>         of the chosen compile-time declaration is R'* (where R is the
>         target type that may be used to infer R'), and neither R nor
>         R' is |void|, and R' is compatible with R in an assignment
>         context.
>
It seems like javac is missing this capture conversion and, because of 
that, the behavior of the two examples diverge.

Cheers
Maurizio





On 07/10/2025 17:12, Tagir Valeev wrote:
> Hello!
>
> I'm investigating a seemingly weird compilation case. Consider the 
> following Java interface:
>
> import java.util.function.Supplier;
>
> interface Main {
>     interface X<T> {
>         X<T> self();
>     }
>
>     static X<?> makeX() {return null;}
>
>     static <R> X<R> create(Supplier<? extends R> supplier) {return null;}
>
>     static X<X<?>> methodRef() {
>         return create(Main::makeX).self();
>     }
>
>     static X<X<?>> lambda() {
>         return create(() -> makeX()).self();
>     }
> }
>
> I expect that either both methods 'methodRef' and 'lambda' should be 
> compilable or both should be non-compilable. However, while 
> 'methodRef' compiles, 'lambda' is rejected by compiler (using 
> javac build 25+36-3489):
>
> Main.java:17: error: incompatible types: X<X<CAP#1>> cannot be 
> converted to X<X<?>>
>         return create(() -> makeX()).self();
>                                          ^
>   where CAP#1 is a fresh type-variable:
>     CAP#1 extends Object from capture of ?
> 1 error
> error: compilation failed
>
> Could you please help me and clarify whether this is an expected 
> behavior or not?
>
> With best regards,
> Tagir Valeev
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/compiler-dev/attachments/20251009/ebfe466b/attachment-0001.htm>


More information about the compiler-dev mailing list