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