• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 315

18 Dec 2025 03:00PM UTC coverage: 78.963% (+0.2%) from 78.784%
315

push

github

adangel
[doc] Explain how to build or pull snapshot dependencies for single module builds (#6287)

18491 of 24300 branches covered (76.09%)

Branch coverage included in aggregate %.

40287 of 50137 relevant lines covered (80.35%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.19
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprCheckHelper.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.java.types.internal.infer;
6

7
import static net.sourceforge.pmd.lang.java.types.TypeConversion.capture;
8
import static net.sourceforge.pmd.lang.java.types.TypeOps.areSameTypesInInference;
9
import static net.sourceforge.pmd.lang.java.types.TypeOps.asClassType;
10
import static net.sourceforge.pmd.lang.java.types.TypeOps.findFunctionalInterfaceMethod;
11
import static net.sourceforge.pmd.lang.java.types.TypeOps.mentionsAny;
12
import static net.sourceforge.pmd.lang.java.types.TypeOps.nonWildcardParameterization;
13
import static net.sourceforge.pmd.lang.java.types.internal.InternalMethodTypeItf.cast;
14
import static net.sourceforge.pmd.lang.java.types.internal.infer.ExprOps.methodRefAsInvocation;
15
import static net.sourceforge.pmd.lang.java.types.internal.infer.InferenceVar.BoundKind.EQ;
16
import static net.sourceforge.pmd.lang.java.types.internal.infer.InferenceVar.BoundKind.LOWER;
17
import static net.sourceforge.pmd.lang.java.types.internal.infer.InferenceVar.BoundKind.UPPER;
18

19
import java.util.ArrayList;
20
import java.util.Collections;
21
import java.util.List;
22
import java.util.Set;
23

24
import org.checkerframework.checker.nullness.qual.NonNull;
25
import org.checkerframework.checker.nullness.qual.Nullable;
26

27
import net.sourceforge.pmd.lang.java.types.JClassType;
28
import net.sourceforge.pmd.lang.java.types.JMethodSig;
29
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
30
import net.sourceforge.pmd.lang.java.types.JTypeVar;
31
import net.sourceforge.pmd.lang.java.types.JWildcardType;
32
import net.sourceforge.pmd.lang.java.types.Substitution;
33
import net.sourceforge.pmd.lang.java.types.TypeOps;
34
import net.sourceforge.pmd.lang.java.types.TypeSystem;
35
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.BranchingMirror;
36
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.FunctionalExprMirror;
37
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.InvocationMirror;
38
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.InvocationMirror.MethodCtDecl;
39
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.LambdaExprMirror;
40
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.MethodRefMirror;
41
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.PolyExprMirror;
42
import net.sourceforge.pmd.util.CollectionUtil;
43
import net.sourceforge.pmd.util.OptionalBool;
44

45
@SuppressWarnings("PMD.CompareObjectsWithEquals")
1✔
46
final class ExprCheckHelper {
47

48
    private final InferenceContext infCtx;
49
    private final MethodResolutionPhase phase;
50
    private final ExprChecker checker;
51
    private final @Nullable MethodCallSite site;
52
    private final Infer infer;
53
    private final TypeSystem ts;
54

55
    ExprCheckHelper(InferenceContext infCtx,
56
                    MethodResolutionPhase phase,
57
                    ExprChecker checker,
58
                    @Nullable MethodCallSite site,
59
                    Infer infer) {
1✔
60

61
        this.infCtx = infCtx;
1✔
62
        this.phase = phase;
1✔
63
        this.checker = checker;
1✔
64
        this.site = site;
1✔
65
        this.infer = infer;
1✔
66
        this.ts = infer.getTypeSystem();
1✔
67
    }
1✔
68

69
    /**
70
     * Recurse on all relevant subexpressions to add constraints.
71
     * E.g when a parameter must be a {@code Supplier<T>},
72
     * then for the lambda {@code () -> "string"} we add a constraint
73
     * that {@code T} must be a subtype of string.
74
     *
75
     * <p>Instead of gathering expressions explicitly into a bound set,
76
     * we explore the structure of the expression and:
77
     * 1. if it's standalone, we immediately add a constraint {@code type(<arg>) <: formalType}
78
     * 2. otherwise, it depends on the form of the expression
79
     *  i) if it's a method or constructor invocation:
80
     *     i.i) we instantiate it. If that method is generic, we add its
81
     *     inference variables in this context and solve them globally.
82
     *     i.ii) we add a constraint linking the return type of the invocation and the formal type
83
     *  ii) if it's a lambda:
84
     *     ii.i) we ensure that the formalType is a functional interface
85
     *     ii.ii) we find the lambda's return expressions and add constraints
86
     *     on each one recursively with this algorithm.
87
     *
88
     *
89
     * @param targetType Target, not necessarily ground
90
     * @param expr       Expression
91
     *
92
     * @return true if it's compatible (or we don't have enough info, and the check is deferred)
93
     *
94
     * @throws ResolutionFailedException If the expr is not compatible, and we want to add a message for the reason
95
     */
96
    boolean isCompatible(JTypeMirror targetType, ExprMirror expr) {
97
        final boolean isStandalone;
98
        {
99
            JTypeMirror standalone = expr.getStandaloneType();
1✔
100
            if (standalone != null) {
1✔
101
                if (mayMutateExpr()) {
1!
102
                    expr.setInferredType(standalone);
1✔
103
                    expr.finishStandaloneInference(standalone);
1✔
104
                }
105
                isStandalone = true;
1✔
106

107
                // defer check if fi is not ground
108
                checker.checkExprConstraint(infCtx, standalone, targetType);
1✔
109
                if (!(expr instanceof PolyExprMirror)) {
1✔
110
                    return true;
1✔
111
                }
112
                // otherwise fallthrough, we potentially need to finish
113
                // inferring some things on the polys
114
            } else {
115
                isStandalone = false;
1✔
116
            }
117
        }
118

119
        if (expr instanceof FunctionalExprMirror) { // those are never standalone
1✔
120

121
            JClassType funType = getProbablyFunctItfType(targetType, (FunctionalExprMirror) expr);
1✔
122
            if (funType == null) {
1✔
123
                return true; // deferred to invocation
1✔
124
            }
125

126
            if (expr instanceof LambdaExprMirror) {
1✔
127
                LambdaExprMirror lambda = (LambdaExprMirror) expr;
1✔
128
                try {
129
                    return isLambdaCompatible(funType, lambda);
1✔
130
                } catch (ResolutionFailedException e) {
1✔
131
                    // need to cleanup the partial data
132
                    if (mayMutateExpr()) {
1!
133
                        lambda.setInferredType(null);
1✔
134
                        lambda.setFunctionalMethod(null);
1✔
135
                    }
136

137
                    if (site != null) {
1✔
138
                        site.maySkipInvocation(false);
1✔
139
                    }
140
                    throw e;
1✔
141
                }
142
            } else {
143
                return isMethodRefCompatible(funType, (MethodRefMirror) expr);
1✔
144
            }
145

146
        } else if (expr instanceof InvocationMirror) {
1✔
147
            // then the argument is a poly invoc expression itself
148
            // in that case we need to infer that as well
149
            return isInvocationCompatible(targetType, (InvocationMirror) expr, isStandalone);
1✔
150
        } else if (expr instanceof BranchingMirror) {
1!
151
            return ((BranchingMirror) expr).branchesMatch(it -> isCompatible(targetType, it));
1✔
152
        }
153

154
        return false;
×
155
    }
156

157

158
    private boolean isInvocationCompatible(JTypeMirror targetType, InvocationMirror invoc, boolean isStandalone) {
159
        MethodCallSite nestedSite = infer.newCallSite(invoc, targetType, this.site, this.infCtx, isSpecificityCheck());
1✔
160

161
        MethodCtDecl argCtDecl = infer.determineInvocationTypeOrFail(nestedSite);
1✔
162
        JMethodSig mostSpecific = argCtDecl.getMethodType();
1✔
163

164
        JTypeMirror actualType = mostSpecific.getReturnType();
1✔
165

166
        if (argCtDecl == infer.FAILED_INVOCATION) {
1✔
167
            throw ResolutionFailedException.incompatibleFormal(infer.LOG, invoc, ts.ERROR, targetType);
1✔
168
        } else if (argCtDecl == infer.getMissingCtDecl()) {
1✔
169
            JTypeMirror fallback = invoc.unresolvedType();
1✔
170
            if (fallback != null) {
1✔
171
                actualType = fallback;
1✔
172
            }
173
            // else it's ts.UNRESOLVED
174
            if (mayMutateExpr()) {
1!
175
                invoc.setInferredType(fallback);
1✔
176
                invoc.setCompileTimeDecl(infer.getMissingCtDecl());
1✔
177
            }
178
        }
179

180
        if (site != null) {
1✔
181
            site.maySkipInvocation(nestedSite.canSkipInvocation());
1✔
182
        }
183

184
        // now if the return type of the arg is polymorphic and unsolved,
185
        // there are some additional bounds on our own infCtx
186

187
        if (!isStandalone) {
1✔
188
            // If the expr was standalone, the constraint was already added.
189
            // We must take care not to duplicate the constraint, because if
190
            // it's eg checking a wildcard parameterized type, we could have
191
            // two equality constraints on separate captures of the same wild -> incompatible
192
            checker.checkExprConstraint(infCtx, actualType, targetType);
1✔
193
        }
194

195
        if (!argCtDecl.isFailed() && mayMutateExpr()) {
1!
196
            infCtx.addInstantiationListener(
1✔
197
                infCtx.freeVarsIn(mostSpecific),
1✔
198
                solved -> {
199
                    JMethodSig ground = solved.ground(mostSpecific);
1✔
200
                    invoc.setInferredType(ground.getReturnType());
1✔
201
                    invoc.setCompileTimeDecl(argCtDecl.withMethod(ground));
1✔
202
                }
1✔
203
            );
204
        }
205
        return true;
1✔
206
    }
207

208
    private @Nullable JClassType getProbablyFunctItfType(final JTypeMirror targetType, FunctionalExprMirror expr) {
209
        JClassType asClass;
210
        if (targetType instanceof InferenceVar && site != null) {
1!
211
            if (site.isInFinalInvocation()) {
1✔
212
                asClass = asClassType(softSolve(targetType)); // null if not funct itf
1✔
213
            } else {
214
                /*
215
                 * The functional expression has an inference variable as a target type,
216
                 * and that ivar does not have enough bounds to be resolved to a functional interface type yet.
217
                 *
218
                 * <p>This should not prevent ctdecl resolution to proceed. The additional
219
                 * bounds may be contributed by the invocation constraints of an enclosing
220
                 * inference process.
221
                 */
222
                infer.LOG.functionalExprNeedsInvocationCtx(targetType, expr);
1✔
223
                return null; // defer
1✔
224
            }
225
        } else {
226
            asClass = asClassType(targetType);
1✔
227
        }
228

229
        if (asClass == null && TypeOps.isUnresolved(targetType)
1✔
230
            || asClass != null && TypeOps.isUnresolved(asClass)) {
1✔
231
            // The type is unresolved, meaning classpath is incomplete.
232
            // We will treat the lambda/mref as if it is compatible with this
233
            // unresolved type. This is usually the right thing to do but
234
            // the types of lambda parameters may be unresolved.
235
            JTypeMirror target = asClass != null ? asClass : targetType;
1✔
236
            handleFunctionalExprWithoutTargetType(expr, target);
1✔
237
            infer.LOG.functionalExprHasUnresolvedTargetType(targetType, expr);
1✔
238
            return null;
1✔
239
        }
240
        if (asClass == null) {
1✔
241
            throw ResolutionFailedException.notAFunctionalInterface(infer.LOG, targetType, expr);
1✔
242
        }
243
        return asClass;
1✔
244
    }
245

246
    private void handleFunctionalExprWithoutTargetType(FunctionalExprMirror expr, JTypeMirror targetType) {
247
        if (expr instanceof LambdaExprMirror) {
1✔
248
            LambdaExprMirror lambda = (LambdaExprMirror) expr;
1✔
249
            List<JTypeMirror> paramTypes;
250
            List<JTypeMirror> explicit = lambda.getExplicitParameterTypes();
1✔
251
            paramTypes = explicit != null
1✔
252
                         ? explicit : Collections.nCopies(lambda.getParamCount(), ts.UNKNOWN);
1✔
253
            // we need to set the parameter types
254
            lambda.updateTypingContext(paramTypes);
1✔
255
            // And add a constraint on the free variables in the target type.
256
            // These free variables may be inferable when the classpath is complete
257
            // through the lambda adding constraints on those variables. Since
258
            // we do not know the signature of the function, we should allow for
259
            // the variables mentioned in this type to resolve to (*unknown*) and not
260
            // Object.
261
            checker.checkExprConstraint(infCtx, ts.UNKNOWN, targetType);
1✔
262
        }
263
        if (mayMutateExpr()) {
1!
264
            infCtx.addInstantiationListener(
1✔
265
                infCtx.freeVarsIn(targetType),
1✔
266
                ctx -> expr.finishFailedInference(ctx.ground(targetType)));
1✔
267
        }
268
    }
1✔
269

270
    // we can't ask the infctx to solve the ivar, as that would require all bounds to be ground
271
    // We want however to be able to add constraints on the functional interface type's inference variables
272
    // This is useful to infer lambdas generic in their return type
273
    // Eg Function<String, R>, where R is not yet instantiated at the time
274
    // we check the argument, should not be ground: we want the lambda to
275
    // add a constraint on R according to its return expressions.
276
    @SuppressWarnings("PMD.CompareObjectsWithEquals")
277
    private @Nullable JTypeMirror softSolve(JTypeMirror t) {
278
        if (!(t instanceof InferenceVar)) {
1✔
279
            return t;
1✔
280
        }
281
        InferenceVar ivar = (InferenceVar) t;
1✔
282
        Set<JTypeMirror> bounds = ivar.getBounds(EQ);
1✔
283
        if (bounds.size() == 1) {
1✔
284
            return bounds.iterator().next();
1✔
285
        }
286
        bounds = ivar.getBounds(LOWER);
1✔
287
        if (!bounds.isEmpty()) {
1!
288
            JTypeMirror lub = ts.lub(bounds);
×
289
            return lub != ivar ? softSolve(lub) : null;
×
290
        }
291
        bounds = ivar.getBounds(UPPER);
1✔
292
        if (!bounds.isEmpty()) {
1!
293
            JTypeMirror glb = ts.glb(bounds);
1✔
294
            return glb != ivar ? softSolve(glb) : null;
1!
295
        }
296
        return null;
×
297
    }
298

299
    private boolean isMethodRefCompatible(@NonNull JClassType functionalItf, MethodRefMirror mref) {
300
        // See JLS§18.2.1. Expression Compatibility Constraints
301

302
        // A constraint formula of the form ‹MethodReference → T›,
303
        // where T mentions at least one inference variable, is reduced as follows:
304

305
        // If T is not a functional interface type, or if T is a functional interface
306
        // type that does not have a function type (§9.9), the constraint reduces to false.
307

308
        JClassType nonWildcard = nonWildcardParameterization(functionalItf);
1✔
309
        if (nonWildcard == null) {
1!
310
            throw ResolutionFailedException.notAFunctionalInterface(infer.LOG, functionalItf, mref);
×
311
        }
312

313
        JMethodSig fun = findFunctionalInterfaceMethod(nonWildcard);
1✔
314
        if (fun == null) {
1✔
315
            throw ResolutionFailedException.notAFunctionalInterface(infer.LOG, functionalItf, mref);
1✔
316
        }
317

318
        JMethodSig exactMethod = ExprOps.getExactMethod(mref);
1✔
319
        if (exactMethod != null) {
1✔
320
            //  if the method reference is exact (§15.13.1), then let P1, ..., Pn be the parameter
321
            //  types of the function type of T, and let F1, ..., Fk be
322
            //  the parameter types of the potentially applicable method
323
            List<JTypeMirror> ps = fun.getFormalParameters();
1✔
324
            List<JTypeMirror> fs = exactMethod.getFormalParameters();
1✔
325

326
            int n = ps.size();
1✔
327
            int k = fs.size();
1✔
328

329
            if (n == k + 1) {
1✔
330
                // The parameter of type P1 is to act as the target reference of the invocation.
331
                // The method reference expression necessarily has the form ReferenceType :: [TypeArguments] Identifier.
332
                // The constraint reduces to ‹P1 <: ReferenceType› and, for all i (2 ≤ i ≤ n), ‹Pi → Fi-1›.
333
                JTypeMirror lhs = mref.getLhsIfType();
1✔
334
                if (lhs == null) {
1!
335
                    // then the constraint reduces to false (it may be,
336
                    // that the candidate is wrong, so that n is wrong ^^)
337
                    return false;
×
338
                }
339

340
                // The receiver may not be boxed
341
                JTypeMirror receiver = ps.get(0);
1✔
342
                if (receiver.isPrimitive()) {
1✔
343
                    throw ResolutionFailedException.cannotInvokeInstanceMethodOnPrimitive(infer.LOG, receiver, mref);
1✔
344
                }
345
                checker.checkExprConstraint(infCtx, receiver, lhs);
1✔
346

347
                for (int i = 1; i < n; i++) {
1✔
348
                    checker.checkExprConstraint(infCtx, ps.get(i), fs.get(i - 1));
1✔
349
                }
350
            } else if (n != k) {
1!
351
                throw ResolutionFailedException.incompatibleArity(infer.LOG, k, n, mref);
×
352
            } else {
353
                // n == k
354
                for (int i = 0; i < n; i++) {
1✔
355
                    checker.checkExprConstraint(infCtx, ps.get(i), fs.get(i));
1✔
356
                }
357
            }
358

359
            //  If the function type's result is not void, let R be its
360
            //  return type.
361
            //
362
            JTypeMirror r = fun.getReturnType();
1✔
363
            if (r != ts.NO_TYPE) {
1✔
364
                //  Then, if the result of the potentially applicable
365
                //  compile-time declaration is void, the constraint reduces to false.
366
                JTypeMirror r2 = exactMethod.getReturnType();
1✔
367
                if (r2 == ts.NO_TYPE) {
1✔
368
                    return false;
1✔
369
                }
370

371
                //  Otherwise, the constraint reduces to ‹R' → R›, where R' is the
372
                //  result of applying capture conversion (§5.1.10) to the return
373
                //  type of the potentially applicable compile-time declaration.
374
                checker.checkExprConstraint(infCtx, capture(r2), r);
1✔
375
            }
376
            completeMethodRefInference(mref, nonWildcard, fun, mrefSigAsCtDecl(exactMethod), true);
1✔
377
        } else if (TypeOps.isUnresolved(mref.getTypeToSearch())) {
1✔
378
            // Then this is neither an exact nor inexact method ref,
379
            // we just don't know what it is.
380

381
            // The return values of the mref are assimilated to an (*unknown*) type.
382
            checker.checkExprConstraint(infCtx, ts.UNKNOWN, fun.getReturnType());
1✔
383
            completeMethodRefInference(mref, nonWildcard, fun, infer.getMissingCtDecl(), false);
1✔
384
        } else {
385
            // Otherwise, the method reference is inexact, and:
386

387
            // (If one or more of the function's formal parameter types
388
            // is not a proper type, the constraint reduces to false.)
389

390
            // This is related to the input variable trickery used
391
            // to resolve input vars before resolving the constraint:
392
            // https://docs.oracle.com/javase/specs/jls/se12/html/jls-18.html#jls-18.5.2.2
393

394
            Set<InferenceVar> inputIvars = infCtx.freeVarsIn(fun.getFormalParameters());
1✔
395
            // The free vars of the return type depend on the free vars of the parameters.
396
            // This explicit dependency is there to prevent solving the variables in the
397
            // return type before solving those of the parameters. That is because the variables
398
            // mentioned in the return type may be further constrained by adding the return constraints
399
            // below (in the listener), which is only triggered when the input ivars have been instantiated.
400
            infCtx.addInstantiationDependencies(infCtx.freeVarsIn(fun.getReturnType()), inputIvars);
1✔
401
            infCtx.addInstantiationListener(
1✔
402
                inputIvars,
403
                solvedCtx -> solveInexactMethodRefCompatibility(mref, solvedCtx.ground(nonWildcard), solvedCtx.ground(fun))
1✔
404
            );
405
        }
406
        return true;
1✔
407
    }
408

409
    private void solveInexactMethodRefCompatibility(MethodRefMirror mref, JClassType nonWildcard, JMethodSig fun) {
410
        // Otherwise, a search for a compile-time declaration is performed, as specified in §15.13.1.
411
        @Nullable MethodCtDecl ctdecl0 = infer.exprOps.findInexactMethodRefCompileTimeDecl(mref, fun);
1✔
412

413
        // If there is no compile-time declaration for the method reference, the constraint reduces to false.
414
        if (ctdecl0 == null) {
1✔
415
            throw ResolutionFailedException.noCtDeclaration(infer.LOG, fun, mref);
1✔
416
        }
417

418
        JMethodSig ctdecl = ctdecl0.getMethodType();
1✔
419

420
        // Otherwise, there is a compile-time declaration, and: (let R be the result of the function type)
421
        JTypeMirror r = fun.getReturnType();
1✔
422
        if (r == ts.NO_TYPE) {
1✔
423
            // If R is void, the constraint reduces to true.
424
            completeMethodRefInference(mref, nonWildcard, fun, mrefSigAsCtDecl(ctdecl), false);
1✔
425
            return;
1✔
426
        }
427

428
        boolean fixInstantiation = false;
1✔
429

430
        // Otherwise, if the method reference expression elides TypeArguments, and the compile-time
431
        // declaration is a generic method, and the return type of the compile-time declaration mentions
432
        // at least one of the method's type parameters, then:
433
        if (mref.getExplicitTypeArguments().isEmpty() && ExprOps.isContextDependent(ctdecl)) {
1!
434

435
            // If R mentions one of the type parameters of the function type, the constraint reduces to false.
436
            if (mentionsAny(r, fun.getTypeParameters())) {
1✔
437
                // Rationale from JLS
438
                // In this case, a constraint in terms of R might lead an inference variable to
439
                // be bound by an out-of-scope type variable. Since instantiating an inference
440
                // variable with an out-of-scope type variable is nonsensical, we prefer to
441
                // avoid the situation by giving up immediately whenever the possibility arises.
442

443
                // Apparently javac allows compiling stuff like that. There's a test case in
444
                // MethodRefInferenceTest, which was found in our codebase.
445
                // We try one last thing to avoid the possibility of referencing out-of-scope stuff
446

447
                if (!TypeOps.haveSameTypeParams(ctdecl, fun)) {
1!
448
                    // then we really can't do anything
449
                    throw ResolutionFailedException.unsolvableDependency(infer.LOG);
×
450
                } else {
451
                    fixInstantiation = true;
1✔
452
                }
453
            }
454

455
            // JLS:
456
            // If R does not mention one of the type parameters of the function type, then the
457
            // constraint reduces to the bound set B3 which would be used to determine the
458
            // method reference's compatibility when targeting the return type of the function
459
            // type, as defined in §18.5.2.1. B3 may contain new inference variables, as well
460
            // as dependencies between these new variables and the inference variables in T.
461

462
            if (phase.isInvocation()) {
1!
463
                MethodCtDecl sig = inferMethodRefInvocation(mref, fun, ctdecl0);
1✔
464
                if (fixInstantiation) {
1✔
465
                    // We know that fun & sig have the same type params
466
                    // We need to fix those that are out-of-scope
467
                    JMethodSig inferred = sig.getMethodType();
1✔
468
                    inferred = inferred.subst(Substitution.mapping(fun.getTypeParameters(), inferred.getTypeParameters()));
1✔
469
                    sig = sig.withMethod(inferred);
1✔
470
                }
471
                completeMethodRefInference(mref, nonWildcard, fun, sig, false);
1✔
472
            }
1✔
473
        } else {
474
            // Otherwise, let R' be the result of applying capture conversion (§5.1.10) to the return
475
            // type of the invocation type (§15.12.2.6) of the compile-time declaration. If R' is void,
476
            // the constraint reduces to false; otherwise, the constraint reduces to ‹R' → R›.
477
            if (ctdecl.getReturnType() == ts.NO_TYPE) {
1!
478
                throw ResolutionFailedException.incompatibleReturn(infer.LOG, mref, ctdecl.getReturnType(), r);
×
479
            } else {
480
                checker.checkExprConstraint(infCtx, capture(ctdecl.getReturnType()), r);
1✔
481
                completeMethodRefInference(mref, nonWildcard, fun, mrefSigAsCtDecl(ctdecl), false);
1✔
482
            }
483
        }
484
    }
1✔
485

486
    private void completeMethodRefInference(MethodRefMirror mref, JClassType groundTargetType, JMethodSig functionalMethod, MethodCtDecl ctDecl, boolean isExactMethod) {
487
        if ((phase.isInvocation() || isExactMethod) && mayMutateExpr()) {
1!
488
            // if exact, then the arg is relevant to applicability and there
489
            // may not be an invocation round
490
            infCtx.addInstantiationListener(
1✔
491
                infCtx.freeVarsIn(groundTargetType),
1✔
492
                solved -> {
493
                    mref.setInferredType(solved.ground(groundTargetType));
1✔
494
                    mref.setFunctionalMethod(cast(solved.ground(functionalMethod)).withOwner(solved.ground(functionalMethod.getDeclaringType())));
1✔
495
                    mref.setCompileTimeDecl(ctDecl.withMethod(solved.ground(ctDecl.getMethodType())));
1✔
496
                }
1✔
497
            );
498
        }
499
    }
1✔
500

501
    MethodCtDecl mrefSigAsCtDecl(JMethodSig sig) {
502
        return new MethodCtDecl(sig, MethodResolutionPhase.INVOC_LOOSE, false, OptionalBool.UNKNOWN, false, null);
1✔
503
    }
504

505
    MethodCtDecl inferMethodRefInvocation(MethodRefMirror mref, JMethodSig targetType, MethodCtDecl ctdecl) {
506
        InvocationMirror wrapper = methodRefAsInvocation(mref, targetType, false);
1✔
507
        wrapper.setCompileTimeDecl(ctdecl);
1✔
508
        MethodCallSite mockSite = infer.newCallSite(wrapper, /* expected */ targetType.getReturnType(), site, infCtx, isSpecificityCheck());
1✔
509
        return infer.determineInvocationTypeOrFail(mockSite);
1✔
510
    }
511

512
    /**
513
     * Only executed if {@link MethodResolutionPhase#isInvocation()},
514
     * as per {@link ExprOps#isPertinentToApplicability(ExprMirror, JMethodSig, JTypeMirror, InvocationMirror)}.
515
     */
516
    private boolean isLambdaCompatible(@NonNull JClassType functionalItf, LambdaExprMirror lambda) {
517

518
        JClassType groundTargetType = groundTargetType(functionalItf, lambda);
1✔
519
        if (groundTargetType == null) {
1!
520
            throw ResolutionFailedException.notAFunctionalInterface(infer.LOG, functionalItf, lambda);
×
521
        }
522

523
        JMethodSig groundFun = findFunctionalInterfaceMethod(groundTargetType);
1✔
524
        if (groundFun == null) {
1✔
525
            throw ResolutionFailedException.notAFunctionalInterface(infer.LOG, functionalItf, lambda);
1✔
526
        }
527

528

529
        // might be partial, whatever
530
        // We use that, so that the parameters may at least have a type
531
        // in the body of the lambda, to infer its return type
532

533
        // this is because the lazy type resolver uses the functional
534
        // method to resolve the type of a LambdaParameter
535
        if (mayMutateExpr()) {
1!
536
            lambda.setInferredType(groundTargetType);
1✔
537
            lambda.setFunctionalMethod(groundFun);
1✔
538

539
            // set the final type when done
540
            if (phase.isInvocation()) {
1✔
541
                infCtx.addInstantiationListener(
1✔
542
                    infCtx.freeVarsIn(groundTargetType),
1✔
543
                    solved -> {
544
                        JClassType solvedGround = solved.ground(groundTargetType);
1✔
545
                        lambda.setInferredType(solvedGround);
1✔
546
                        lambda.setFunctionalMethod(cast(solved.ground(groundFun)).withOwner(solved.ground(groundFun.getDeclaringType())));
1✔
547
                    }
1✔
548
                );
549
            }
550
        }
551

552
        return isLambdaCongruent(functionalItf, groundTargetType, groundFun, lambda);
1✔
553
    }
554

555
    private boolean mayMutateExpr() {
556
        return !isSpecificityCheck();
1!
557
    }
558

559
    private boolean isSpecificityCheck() {
560
        return site != null && site.isSpecificityCheck();
1!
561
    }
562

563
    // functionalItf    = T
564
    // groundTargetType = T'
565
    @SuppressWarnings("PMD.UnusedFormalParameter")
566
    private boolean isLambdaCongruent(@NonNull JClassType functionalItf,
567
                                      @NonNull JClassType groundTargetType,
568
                                      @NonNull JMethodSig groundFun,
569
                                      LambdaExprMirror lambda) {
570

571
        if (groundFun.isGeneric()) {
1!
572
            throw ResolutionFailedException.lambdaCannotTargetGenericFunction(infer.LOG, groundFun, lambda);
×
573
        }
574

575
        //  If the number of lambda parameters differs from the number
576
        //  of parameter types of the function type, the constraint
577
        //  reduces to false.
578
        if (groundFun.getArity() != lambda.getParamCount()) {
1✔
579
            throw ResolutionFailedException.incompatibleArity(infer.LOG, lambda.getParamCount(), groundFun.getArity(), lambda);
1✔
580
        }
581

582
        // If the function type's result is void and the lambda body is
583
        // neither a statement expression nor a void-compatible block,
584
        // the constraint reduces to false.
585
        JTypeMirror result = groundFun.getReturnType();
1✔
586
        if (result == ts.NO_TYPE && !lambda.isVoidCompatible()) {
1✔
587
            throw ResolutionFailedException.lambdaCannotTargetVoidMethod(infer.LOG, lambda);
1✔
588
        }
589

590
        // If the function type's result is not void and the lambda
591
        // body is a block that is not value-compatible, the constraint
592
        // reduces to false.
593
        if (result != ts.NO_TYPE && !lambda.isValueCompatible()) {
1✔
594
            throw ResolutionFailedException.lambdaCannotTargetValueMethod(infer.LOG, lambda);
1✔
595
        }
596

597
        // If the lambda parameters have explicitly declared types F1, ..., Fn
598
        // and the function type has parameter types G1, ..., Gn, then
599
        // i) for all i (1 ≤ i ≤ n), ‹Fi = Gi›
600
        if (lambda.isExplicitlyTyped()
1✔
601
            && !areSameTypesInInference(groundFun.getFormalParameters(), lambda.getExplicitParameterTypes())) {
1!
602
            throw ResolutionFailedException.mismatchedLambdaParameters(infer.LOG, groundFun, lambda.getExplicitParameterTypes(), lambda);
×
603
        }
604

605
        // and ii) ‹T' <: T›.
606
        // if (!groundTargetType.isSubtypeOf(functionalItf)) {
607
        //     return false;
608
        // }
609

610
        // finally, add bounds
611
        if (result != ts.NO_TYPE) {
1✔
612
            Set<InferenceVar> inputIvars = infCtx.freeVarsIn(groundFun.getFormalParameters());
1✔
613
            // The free vars of the return type depend on the free vars of the parameters.
614
            // This explicit dependency is there to prevent solving the variables in the
615
            // return type before solving those of the parameters. That is because the variables
616
            // mentioned in the return type may be further constrained by adding the return constraints
617
            // below (in the listener), which is only triggered when the input ivars have been instantiated.
618
            infCtx.addInstantiationDependencies(infCtx.freeVarsIn(groundFun.getReturnType()), inputIvars);
1✔
619
            infCtx.addInstantiationListener(
1✔
620
                inputIvars,
621
                solvedCtx -> {
622
                    if (mayMutateExpr()) {
1!
623
                        lambda.setInferredType(solvedCtx.ground(groundTargetType));
1✔
624
                        JMethodSig solvedGroundFun = solvedCtx.ground(groundFun);
1✔
625
                        lambda.setFunctionalMethod(solvedGroundFun);
1✔
626
                        lambda.updateTypingContext(solvedGroundFun.getFormalParameters());
1✔
627
                    }
628
                    JTypeMirror groundResult = solvedCtx.ground(result);
1✔
629
                    // We need to build another checker that uses the solved context.
630
                    // This is because the free vars may have been adopted by a parent
631
                    // context, so the solvedCtx may be that parent context. The checks
632
                    // must use that context so that constraints and listeners are added
633
                    // to the parent context, since that one is responsible for solving
634
                    // the variables.
635
                    ExprCheckHelper newChecker = new ExprCheckHelper(solvedCtx, phase, this.checker, site, infer);
1✔
636
                    for (ExprMirror expr : lambda.getResultExpressions()) {
1✔
637
                        if (!newChecker.isCompatible(groundResult, expr)) {
1!
638
                            return;
×
639
                        }
640
                    }
1✔
641
                });
1✔
642
        }
643

644
        if (mayMutateExpr()) { // we know that the lambda matches now
1!
645
            lambda.updateTypingContext(groundFun.getFormalParameters());
1✔
646
        }
647
        return true;
1✔
648
    }
649

650
    private @Nullable JClassType groundTargetType(JClassType type, LambdaExprMirror lambda) {
651

652

653
        List<JTypeMirror> targs = type.getTypeArgs();
1✔
654
        if (CollectionUtil.none(targs, it -> it instanceof JWildcardType)) {
1✔
655
            return type;
1✔
656
        }
657

658
        if (lambda.isExplicitlyTyped() && lambda.getParamCount() > 0) {
1✔
659
            return inferGroundTargetTypeForExplicitlyTypedLambda(type, lambda);
1✔
660
        } else {
661
            return nonWildcardParameterization(type);
1✔
662
        }
663
    }
664

665
    private @Nullable JClassType inferGroundTargetTypeForExplicitlyTypedLambda(JClassType targetType, LambdaExprMirror lambda) {
666
        List<JTypeMirror> explicitParamTypes = lambda.getExplicitParameterTypes();
1✔
667
        assert explicitParamTypes != null : "Expecting explicitly typed lambda";
1!
668
        // https://docs.oracle.com/javase/specs/jls/se22/html/jls-18.html#jls-18.5.3
669
        // > For example:
670
        // >     Predicate<? super Integer> p = (Number n) -> n.equals(23);
671
        // > The lambda expression is a Predicate<Number>, which is a subtype of Predicate<? super Integer> but not
672
        // > Predicate<Integer>. The analysis in this section is used to infer that Number is an appropriate choice
673
        // > for the type argument to Predicate.
674

675
        // Let `targetType = F<A1, ..., Am>`
676
        // Let `'a1, ..., 'am` be fresh inference variables.
677
        JClassType targetGTD = targetType.getGenericTypeDeclaration();
1✔
678
        List<JTypeVar> formalTypeParams = targetGTD.getFormalTypeParams();
1✔
679
        InferenceContext ctx = infer.newContextFor(formalTypeParams, false);
1✔
680

681
        // let `inferenceTarget = F<'a1, ..., 'am>`
682
        JClassType inferenceTarget = (JClassType) ctx.mapToIVars(targetGTD);
1✔
683

684
        JMethodSig msig = findFunctionalInterfaceMethod(inferenceTarget);
1✔
685
        if (msig == null) {
1!
686
            return null;
×
687
        }
688

689
        List<JTypeMirror> formals = msig.getFormalParameters();
1✔
690

691
        // Now match formal params of the lambda with those of the signature (which contains ivars)
692
        // this adds constraints
693
        if (!TypeOps.areSameTypesInInference(formals, explicitParamTypes)) {
1!
694
            return null;
×
695
        }
696

697
        ctx.solve(true); // may throw ResolutionFailedException
1✔
698

699
        // If we are here then solving succeeded.
700
        // Build type arguments with instantiated vars. Vars that were not bound (meaning,
701
        // they don't depend on the parameter types of the lambda) are just taken from the
702
        // provided type args.
703

704
        int numTyArgs = formalTypeParams.size();
1✔
705
        List<JTypeMirror> typeArgs = targetType.getTypeArgs();
1✔
706
        List<JTypeMirror> newTyArgs = new ArrayList<>(numTyArgs);
1✔
707
        for (int i = 0; i < numTyArgs; i++) {
1✔
708
            InferenceVar ivarI = (InferenceVar) ctx.mapToIVars(formalTypeParams.get(i));
1✔
709
            if (ivarI.getInst() != null) {
1✔
710
                newTyArgs.add(ivarI.getInst());
1✔
711
            } else {
712
                newTyArgs.add(typeArgs.get(i));
1✔
713
            }
714
        }
715

716
        // Now check that the primary bounds are valid.
717
        ctx.addPrimaryBounds();
1✔
718
        ctx.solve(); // may throw ResolutionFailedException
1✔
719

720
        // This is our type
721
        JClassType inferredTy = targetGTD.withTypeArguments(newTyArgs);
1✔
722
        return nonWildcardParameterization(inferredTy);
1✔
723
    }
724

725

726
    @FunctionalInterface
727
    interface ExprChecker {
728

729
        /**
730
         * In JLS terms, adds a constraint formula {@code < exprType -> formalType >}.
731
         *
732
         * <p>This method throws ResolutionFailedException if the constraint
733
         * can be asserted as false immediately. Otherwise the check is
734
         * deferred until both types have been inferred (but bounds on the
735
         * type vars are added).
736
         */
737
        void checkExprConstraint(InferenceContext infCtx, JTypeMirror exprType, JTypeMirror formalType) throws ResolutionFailedException;
738
    }
739

740

741
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc