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

pmd / pmd / #3722

pending completion
#3722

push

github actions

adangel
Suppress MissingOverride for Chars::isEmpty (#4291)

67270 of 127658 relevant lines covered (52.7%)

0.53 hits per line

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

93.52
/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.infer.ExprOps.methodRefAsInvocation;
14
import static net.sourceforge.pmd.lang.java.types.internal.infer.InferenceVar.BoundKind.EQ;
15
import static net.sourceforge.pmd.lang.java.types.internal.infer.InferenceVar.BoundKind.LOWER;
16
import static net.sourceforge.pmd.lang.java.types.internal.infer.InferenceVar.BoundKind.UPPER;
17

18
import java.util.List;
19
import java.util.Set;
20

21
import org.checkerframework.checker.nullness.qual.NonNull;
22
import org.checkerframework.checker.nullness.qual.Nullable;
23

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

40
@SuppressWarnings("PMD.CompareObjectsWithEquals")
41
final class ExprCheckHelper {
42

43
    private final InferenceContext infCtx;
44
    private final MethodResolutionPhase phase;
45
    private final ExprChecker checker;
46
    private final @Nullable MethodCallSite site;
47
    private final Infer infer;
48
    private final TypeSystem ts;
49

50
    ExprCheckHelper(InferenceContext infCtx,
51
                    MethodResolutionPhase phase,
52
                    ExprChecker checker,
53
                    @Nullable MethodCallSite site,
54
                    Infer infer) {
1✔
55

56
        this.infCtx = infCtx;
1✔
57
        this.phase = phase;
1✔
58
        this.checker = checker;
1✔
59
        this.site = site;
1✔
60
        this.infer = infer;
1✔
61
        this.ts = infer.getTypeSystem();
1✔
62
    }
1✔
63

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

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

114
        if (expr instanceof FunctionalExprMirror) { // those are never standalone
1✔
115
            JClassType funType = getProbablyFunctItfType(targetType, expr);
1✔
116
            if (funType == null) {
1✔
117
                /*
118
                 * The functional expression has an inference variable as a target type,
119
                 * and that ivar does not have enough bounds to be resolved to a functional interface type yet.
120
                 *
121
                 * <p>This should not prevent ctdecl resolution to proceed. The additional
122
                 * bounds may be contributed by the invocation constraints of an enclosing
123
                 * inference process.
124
                 */
125
                infer.LOG.functionalExprNeedsInvocationCtx(targetType, expr);
1✔
126
                return true; // deferred to invocation
1✔
127
            }
128

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

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

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

157
        return false;
×
158
    }
159

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

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

166
        JTypeMirror actualType = mostSpecific.getReturnType();
1✔
167

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

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

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

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

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

210
    private @Nullable JClassType getProbablyFunctItfType(final JTypeMirror targetType, ExprMirror expr) {
211
        JClassType asClass;
212
        if (targetType instanceof InferenceVar && site != null) {
1✔
213
            if (site.isInFinalInvocation()) {
1✔
214
                asClass = asClassType(softSolve(targetType)); // null if not funct itf
1✔
215
            } else {
216
                return null; // defer
1✔
217
            }
218
        } else {
219
            asClass = asClassType(targetType);
1✔
220
        }
221

222
        if (asClass == null) {
1✔
223
            throw ResolutionFailedException.notAFunctionalInterface(infer.LOG, targetType, expr);
1✔
224
        }
225
        return asClass;
1✔
226
    }
227

228
    // we can't ask the infctx to solve the ivar, as that would require all bounds to be ground
229
    // We want however to be able to add constraints on the functional interface type's inference variables
230
    // This is useful to infer lambdas generic in their return type
231
    // Eg Function<String, R>, where R is not yet instantiated at the time
232
    // we check the argument, should not be ground: we want the lambda to
233
    // add a constraint on R according to its return expressions.
234
    @SuppressWarnings("PMD.CompareObjectsWithEquals")
235
    private @Nullable JTypeMirror softSolve(JTypeMirror t) {
236
        if (!(t instanceof InferenceVar)) {
1✔
237
            return t;
1✔
238
        }
239
        InferenceVar ivar = (InferenceVar) t;
1✔
240
        Set<JTypeMirror> bounds = ivar.getBounds(EQ);
1✔
241
        if (bounds.size() == 1) {
1✔
242
            return bounds.iterator().next();
1✔
243
        }
244
        bounds = ivar.getBounds(LOWER);
1✔
245
        if (!bounds.isEmpty()) {
1✔
246
            JTypeMirror lub = ts.lub(bounds);
×
247
            return lub != ivar ? softSolve(lub) : null;
×
248
        }
249
        bounds = ivar.getBounds(UPPER);
1✔
250
        if (!bounds.isEmpty()) {
1✔
251
            JTypeMirror glb = ts.glb(bounds);
1✔
252
            return glb != ivar ? softSolve(glb) : null;
1✔
253
        }
254
        return null;
×
255
    }
256

257
    private boolean isMethodRefCompatible(@NonNull JClassType functionalItf, MethodRefMirror mref) {
258
        // See JLS§18.2.1. Expression Compatibility Constraints
259

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

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

266
        JClassType nonWildcard = nonWildcardParameterization(functionalItf);
1✔
267
        if (nonWildcard == null) {
1✔
268
            throw ResolutionFailedException.notAFunctionalInterface(infer.LOG, functionalItf, mref);
×
269
        }
270

271
        JMethodSig fun = findFunctionalInterfaceMethod(nonWildcard);
1✔
272
        if (fun == null) {
1✔
273
            throw ResolutionFailedException.notAFunctionalInterface(infer.LOG, functionalItf, mref);
1✔
274
        }
275

276
        JMethodSig exactMethod = ExprOps.getExactMethod(mref);
1✔
277
        if (exactMethod != null) {
1✔
278
            //  if the method reference is exact (§15.13.1), then let P1, ..., Pn be the parameter
279
            //  types of the function type of T, and let F1, ..., Fk be
280
            //  the parameter types of the potentially applicable method
281
            List<JTypeMirror> ps = fun.getFormalParameters();
1✔
282
            List<JTypeMirror> fs = exactMethod.getFormalParameters();
1✔
283

284
            int n = ps.size();
1✔
285
            int k = fs.size();
1✔
286

287
            if (n == k + 1) {
1✔
288
                // The parameter of type P1 is to act as the target reference of the invocation.
289
                // The method reference expression necessarily has the form ReferenceType :: [TypeArguments] Identifier.
290
                // The constraint reduces to ‹P1 <: ReferenceType› and, for all i (2 ≤ i ≤ n), ‹Pi → Fi-1›.
291
                JTypeMirror lhs = mref.getLhsIfType();
1✔
292
                if (lhs == null) {
1✔
293
                    // then the constraint reduces to false (it may be,
294
                    // that the candidate is wrong, so that n is wrong ^^)
295
                    return false;
1✔
296
                }
297

298
                // The receiver may not be boxed
299
                JTypeMirror receiver = ps.get(0);
1✔
300
                if (receiver.isPrimitive()) {
1✔
301
                    throw ResolutionFailedException.cannotInvokeInstanceMethodOnPrimitive(infer.LOG, receiver, mref);
1✔
302
                }
303
                checker.checkExprConstraint(infCtx, receiver, lhs);
1✔
304

305
                for (int i = 1; i < n; i++) {
1✔
306
                    checker.checkExprConstraint(infCtx, ps.get(i), fs.get(i - 1));
×
307
                }
308
            } else if (n != k) {
1✔
309
                throw ResolutionFailedException.incompatibleArity(infer.LOG, k, n, mref);
×
310
            } else {
311
                // n == k
312
                for (int i = 0; i < n; i++) {
1✔
313
                    checker.checkExprConstraint(infCtx, ps.get(i), fs.get(i));
1✔
314
                }
315
            }
316

317
            //  If the function type's result is not void, let R be its
318
            //  return type.
319
            //
320
            JTypeMirror r = fun.getReturnType();
1✔
321
            if (r != ts.NO_TYPE) {
1✔
322
                //  Then, if the result of the potentially applicable
323
                //  compile-time declaration is void, the constraint reduces to false.
324
                JTypeMirror r2 = exactMethod.getReturnType();
1✔
325
                if (r2 == ts.NO_TYPE) {
1✔
326
                    return false;
1✔
327
                }
328

329
                //  Otherwise, the constraint reduces to ‹R' → R›, where R' is the
330
                //  result of applying capture conversion (§5.1.10) to the return
331
                //  type of the potentially applicable compile-time declaration.
332
                checker.checkExprConstraint(infCtx, capture(r2), r);
1✔
333
            }
334
            completeMethodRefInference(mref, nonWildcard, fun, exactMethod, true);
1✔
335
        } else {
1✔
336
            // Otherwise, the method reference is inexact, and:
337

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

341
            // This is related to the input variable trickery used
342
            // to resolve input vars before resolving the constraint:
343
            // https://docs.oracle.com/javase/specs/jls/se12/html/jls-18.html#jls-18.5.2.2
344

345
            // here we defer the check until the variables are ground
346
            infCtx.addInstantiationListener(
1✔
347
                infCtx.freeVarsIn(fun.getFormalParameters()),
1✔
348
                solvedCtx -> solveInexactMethodRefCompatibility(mref, solvedCtx.ground(nonWildcard), solvedCtx.ground(fun))
1✔
349
            );
350
        }
351
        return true;
1✔
352
    }
353

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

358
        // If there is no compile-time declaration for the method reference, the constraint reduces to false.
359
        if (ctdecl0 == null) {
1✔
360
            throw ResolutionFailedException.noCtDeclaration(infer.LOG, fun, mref);
1✔
361
        }
362

363
        JMethodSig ctdecl = ctdecl0.getMethodType();
1✔
364

365
        // Otherwise, there is a compile-time declaration, and: (let R be the result of the function type)
366
        JTypeMirror r = fun.getReturnType();
1✔
367
        if (r == ts.NO_TYPE) {
1✔
368
            // If R is void, the constraint reduces to true.
369
            completeMethodRefInference(mref, nonWildcard, fun, ctdecl, false);
1✔
370
            return;
1✔
371
        }
372

373
        boolean fixInstantiation = false;
1✔
374

375
        // Otherwise, if the method reference expression elides TypeArguments, and the compile-time
376
        // declaration is a generic method, and the return type of the compile-time declaration mentions
377
        // at least one of the method's type parameters, then:
378
        if (mref.getExplicitTypeArguments().isEmpty() && ExprOps.isContextDependent(ctdecl)) {
1✔
379

380
            // If R mentions one of the type parameters of the function type, the constraint reduces to false.
381
            if (mentionsAny(r, fun.getTypeParameters())) {
1✔
382
                // Rationale from JLS
383
                // In this case, a constraint in terms of R might lead an inference variable to
384
                // be bound by an out-of-scope type variable. Since instantiating an inference
385
                // variable with an out-of-scope type variable is nonsensical, we prefer to
386
                // avoid the situation by giving up immediately whenever the possibility arises.
387

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

392
                if (!TypeOps.haveSameTypeParams(ctdecl, fun)) {
1✔
393
                    // then we really can't do anything
394
                    throw ResolutionFailedException.unsolvableDependency(infer.LOG);
×
395
                } else {
396
                    fixInstantiation = true;
1✔
397
                }
398
            }
399

400
            // JLS:
401
            // If R does not mention one of the type parameters of the function type, then the
402
            // constraint reduces to the bound set B3 which would be used to determine the
403
            // method reference's compatibility when targeting the return type of the function
404
            // type, as defined in §18.5.2.1. B3 may contain new inference variables, as well
405
            // as dependencies between these new variables and the inference variables in T.
406

407
            if (phase.isInvocation()) {
1✔
408
                JMethodSig sig = inferMethodRefInvocation(mref, fun, ctdecl0);
1✔
409
                if (fixInstantiation) {
1✔
410
                    // We know that fun & sig have the same type params
411
                    // We need to fix those that are out-of-scope
412
                    sig = sig.subst(Substitution.mapping(fun.getTypeParameters(), sig.getTypeParameters()));
1✔
413
                }
414
                completeMethodRefInference(mref, nonWildcard, fun, sig, false);
1✔
415
            }
1✔
416
        } else {
417
            // Otherwise, let R' be the result of applying capture conversion (§5.1.10) to the return
418
            // type of the invocation type (§15.12.2.6) of the compile-time declaration. If R' is void,
419
            // the constraint reduces to false; otherwise, the constraint reduces to ‹R' → R›.
420
            if (ctdecl.getReturnType() == ts.NO_TYPE) {
1✔
421
                throw ResolutionFailedException.incompatibleReturn(infer.LOG, mref, ctdecl.getReturnType(), r);
×
422
            } else {
423
                checker.checkExprConstraint(infCtx, capture(ctdecl.getReturnType()), r);
1✔
424
                completeMethodRefInference(mref, nonWildcard, fun, ctdecl, false);
1✔
425
            }
426
        }
427
    }
1✔
428

429
    private void completeMethodRefInference(MethodRefMirror mref, JClassType groundTargetType, JMethodSig functionalMethod, JMethodSig ctDecl, boolean isExactMethod) {
430
        if ((phase.isInvocation() || isExactMethod) && mayMutateExpr()) {
1✔
431
            // if exact, then the arg is relevant to applicability and there
432
            // may not be an invocation round
433
            infCtx.addInstantiationListener(
1✔
434
                infCtx.freeVarsIn(groundTargetType),
1✔
435
                solved -> {
436
                    mref.setInferredType(solved.ground(groundTargetType));
1✔
437
                    mref.setFunctionalMethod(solved.ground(functionalMethod).internalApi().withOwner(solved.ground(functionalMethod.getDeclaringType())));
1✔
438
                    mref.setCompileTimeDecl(solved.ground(ctDecl));
1✔
439
                }
1✔
440
            );
441
        }
442
    }
1✔
443

444

445
    JMethodSig inferMethodRefInvocation(MethodRefMirror mref, JMethodSig targetType, MethodCtDecl ctdecl) {
446
        InvocationMirror wrapper = methodRefAsInvocation(mref, targetType, false);
1✔
447
        wrapper.setCtDecl(ctdecl);
1✔
448
        MethodCallSite mockSite = infer.newCallSite(wrapper, /* expected */ targetType.getReturnType(), site, infCtx, isSpecificityCheck());
1✔
449
        return infer.determineInvocationTypeOrFail(mockSite).getMethodType();
1✔
450
    }
451

452
    /**
453
     * Only executed if {@link MethodResolutionPhase#isInvocation()},
454
     * as per {@link ExprOps#isPertinentToApplicability(ExprMirror, JMethodSig, JTypeMirror, InvocationMirror)}.
455
     */
456
    private boolean isLambdaCompatible(@NonNull JClassType functionalItf, LambdaExprMirror lambda) {
457

458
        JClassType groundTargetType = groundTargetType(functionalItf, lambda);
1✔
459
        if (groundTargetType == null) {
1✔
460
            throw ResolutionFailedException.notAFunctionalInterface(infer.LOG, functionalItf, lambda);
1✔
461
        }
462

463
        JMethodSig groundFun = findFunctionalInterfaceMethod(groundTargetType);
1✔
464
        if (groundFun == null) {
1✔
465
            throw ResolutionFailedException.notAFunctionalInterface(infer.LOG, functionalItf, lambda);
1✔
466
        }
467

468

469
        // might be partial, whatever
470
        // We use that, so that the parameters may at least have a type
471
        // in the body of the lambda, to infer its return type
472

473
        // this is because the lazy type resolver uses the functional
474
        // method to resolve the type of a LambdaParameter
475
        if (mayMutateExpr()) {
1✔
476
            lambda.setInferredType(groundTargetType);
1✔
477
            lambda.setFunctionalMethod(groundFun);
1✔
478

479
            // set the final type when done
480
            if (phase.isInvocation()) {
1✔
481
                infCtx.addInstantiationListener(
1✔
482
                    infCtx.freeVarsIn(groundTargetType),
1✔
483
                    solved -> {
484
                        JClassType solvedGround = solved.ground(groundTargetType);
1✔
485
                        lambda.setInferredType(solvedGround);
1✔
486
                        lambda.setFunctionalMethod(solved.ground(groundFun).internalApi().withOwner(solved.ground(groundFun.getDeclaringType())));
1✔
487
                    }
1✔
488
                );
489
            }
490
        }
491

492
        return isLambdaCongruent(functionalItf, groundTargetType, groundFun, lambda);
1✔
493
    }
494

495
    private boolean mayMutateExpr() {
496
        return !isSpecificityCheck();
1✔
497
    }
498

499
    private boolean isSpecificityCheck() {
500
        return site != null && site.isSpecificityCheck();
1✔
501
    }
502

503
    // functionalItf    = T
504
    // groundTargetType = T'
505
    @SuppressWarnings("PMD.UnusedFormalParameter")
506
    private boolean isLambdaCongruent(@NonNull JClassType functionalItf,
507
                                      @NonNull JClassType groundTargetType,
508
                                      @NonNull JMethodSig groundFun,
509
                                      LambdaExprMirror lambda) {
510

511
        if (groundFun.isGeneric()) {
1✔
512
            throw ResolutionFailedException.lambdaCannotTargetGenericFunction(infer.LOG, groundFun, lambda);
×
513
        }
514

515
        //  If the number of lambda parameters differs from the number
516
        //  of parameter types of the function type, the constraint
517
        //  reduces to false.
518
        if (groundFun.getArity() != lambda.getParamCount()) {
1✔
519
            throw ResolutionFailedException.incompatibleArity(infer.LOG, lambda.getParamCount(), groundFun.getArity(), lambda);
1✔
520
        }
521

522
        // If the function type's result is void and the lambda body is
523
        // neither a statement expression nor a void-compatible block,
524
        // the constraint reduces to false.
525
        JTypeMirror result = groundFun.getReturnType();
1✔
526
        if (result == ts.NO_TYPE && !lambda.isVoidCompatible()) {
1✔
527
            throw ResolutionFailedException.lambdaCannotTargetVoidMethod(infer.LOG, lambda);
×
528
        }
529

530
        // If the function type's result is not void and the lambda
531
        // body is a block that is not value-compatible, the constraint
532
        // reduces to false.
533
        if (result != ts.NO_TYPE && !lambda.isValueCompatible()) {
1✔
534
            throw ResolutionFailedException.lambdaCannotTargetValueMethod(infer.LOG, lambda);
×
535
        }
536

537
        // If the lambda parameters have explicitly declared types F1, ..., Fn
538
        // and the function type has parameter types G1, ..., Gn, then
539
        // i) for all i (1 ≤ i ≤ n), ‹Fi = Gi›
540
        if (lambda.isExplicitlyTyped()
1✔
541
            && !areSameTypesInInference(groundFun.getFormalParameters(), lambda.getExplicitParameterTypes())) {
1✔
542
            throw ResolutionFailedException.mismatchedLambdaParameters(infer.LOG, groundFun, lambda.getExplicitParameterTypes(), lambda);
×
543
        }
544

545
        // and ii) ‹T' <: T›.
546
        // if (!groundTargetType.isSubtypeOf(functionalItf)) {
547
        //     return false;
548
        // }
549

550
        // finally, add bounds
551
        if (result != ts.NO_TYPE) {
1✔
552
            infCtx.addInstantiationListener(
1✔
553
                infCtx.freeVarsIn(groundFun.getFormalParameters()),
1✔
554
                solvedCtx -> {
555
                    if (mayMutateExpr()) {
1✔
556
                        lambda.setInferredType(solvedCtx.ground(groundTargetType));
1✔
557
                        JMethodSig solvedGroundFun = solvedCtx.ground(groundFun);
1✔
558
                        lambda.setFunctionalMethod(solvedGroundFun);
1✔
559
                        lambda.updateTypingContext(solvedGroundFun);
1✔
560
                    }
561
                    JTypeMirror groundResult = solvedCtx.ground(result);
1✔
562
                    for (ExprMirror expr : lambda.getResultExpressions()) {
1✔
563
                        if (!isCompatible(groundResult, expr)) {
1✔
564
                            return;
×
565
                        }
566
                    }
1✔
567
                });
1✔
568
        }
569

570
        if (mayMutateExpr()) { // we know that the lambda matches now
1✔
571
            lambda.updateTypingContext(groundFun);
1✔
572
        }
573
        return true;
1✔
574
    }
575

576
    private @Nullable JClassType groundTargetType(JClassType type, LambdaExprMirror lambda) {
577

578

579
        List<JTypeMirror> targs = type.getTypeArgs();
1✔
580
        if (CollectionUtil.none(targs, it -> it instanceof JWildcardType)) {
1✔
581
            return type;
1✔
582
        }
583

584
        if (lambda.isExplicitlyTyped() && lambda.getParamCount() > 0) {
1✔
585
            // TODO infer, normally also for lambdas with no param, i'm just lazy
586
            //  https://docs.oracle.com/javase/specs/jls/se9/html/jls-18.html#jls-18.5.3
587
            return null;
1✔
588
        } else {
589
            return nonWildcardParameterization(type);
1✔
590
        }
591
    }
592

593

594
    @FunctionalInterface
595
    interface ExprChecker {
596

597
        /**
598
         * In JLS terms, adds a constraint formula {@code < exprType -> formalType >}.
599
         *
600
         * <p>This method throws ResolutionFailedException if the constraint
601
         * can be asserted as false immediately. Otherwise the check is
602
         * deferred until both types have been inferred (but bounds on the
603
         * type vars are added).
604
         */
605
        void checkExprConstraint(InferenceContext infCtx, JTypeMirror exprType, JTypeMirror formalType) throws ResolutionFailedException;
606
    }
607

608

609
}
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