RFR: 8325859: potential information loss during type inference [v3]

Vicente Romero vromero at openjdk.org
Thu May 15 15:04:55 UTC 2025


On Thu, 15 May 2025 13:12:16 GMT, Maurizio Cimadamore <mcimadamore at openjdk.org> wrote:

> Btw, I've looked at the issue in JBS again, and simplified it a bit:
> 
> ```
> class Generics {
>        public static void main(String... args) {
>             Integer i = null;
>             runT(() -> supplyNull(i));
>         }
> 
>         static <R, X> R supplyNull(X... varargs) {
>             System.out.println(varargs.getClass().getComponentType());
>             return null;
>         }
>               
>         static <T> void runT(Runnable runnable) { runnable.run(); }
> }
> ```
> 
> (I've removed a call to `Integer::valueOf` that was not necessary to reproduce the issue -- but was adding some extra noise).
> 
> Note that here the calls to `supplyNull` and `runT` are completely independent. That's because the call to `supplyNull` occurs in a void-compatible lambda (the target of the lambda is `Runnable`). As such, it is not possible for inference of the nested call to `supplyNull` to have any effect on the inference of the `runT` call.
> 
> However, when debugging, I see that when we call `checkMethod` for `supplyNull`, we attempt to propagate its inference context outwards. This is done even though the `resultInfo` says that the expected type is `void` (where `void` is the return type of the function descriptor).
> 
> In other words, it seems to me that `Infer::shouldPropagate` is buggy: it returns true if all the conditions below are satisfied:
> 
>     * the enclosing method is a generic method
> 
>     * the return type of the current method contains some inference vars
> 
>     * no eager resolution is required
> 
> 
> But we never check what the target-type of the current method actually is. Normally, the target will be some non-void type -- so when we propagate, we will need to check compatibility again, which will fix up all the types. But in this case (which is a corner case due to void-compatible lambdas), the target happens to be `void` -- meaning inference will never be "completed".
> 
> See the implementation of `Attr.ExpressionLambdaReturnContext::compatible`:
> 
> ```
>            @Override
>             public boolean compatible(Type found, Type req, Warner warn) {
>                 //a void return is compatible with an expression statement lambda
>                 if (req.hasTag(VOID)) {
>                     expStmtExpected = true;
>                     return TreeInfo.isExpressionStatement(expr);
>                 } else {
>                     return super.compatible(found, req, warn);
>                 }
>             }
> ```
> 
> In other words, if the expected type is `void` (as in this case), `super.compatible` will not be called -- which means the deferred type for the nested generic method call will remain uninferred.
> 
> Adding an extra check to `Infer::shouldPropagate` to _not_ propagate when the target happens to be `void` seems to fix the issue in JBS. (but, I have not ran any other test).

good catch, yes this is a bug in `Infer::shouldPropagate` and one we should fix for sure, but I think that the fundamental problem is still there and will show its head again as soon as, for example, the lambda is not void-compatible

-------------

PR Comment: https://git.openjdk.org/jdk/pull/25011#issuecomment-2884160827


More information about the compiler-dev mailing list