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

pmd / pmd / 318

21 Dec 2025 06:01PM UTC coverage: 78.966% (-0.009%) from 78.975%
318

push

github

adangel
[java] Fix #6237: UnnecessaryCast error with switch expr returning lambdas (#6295)

18514 of 24322 branches covered (76.12%)

Branch coverage included in aggregate %.

72 of 82 new or added lines in 7 files covered. (87.8%)

5 existing lines in 2 files now uncovered.

40308 of 50168 relevant lines covered (80.35%)

0.81 hits per line

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

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

5

6
package net.sourceforge.pmd.lang.java.types.ast.internal;
7

8

9
import static java.util.Arrays.asList;
10
import static net.sourceforge.pmd.lang.java.types.TypeConversion.isConvertibleUsingBoxing;
11
import static net.sourceforge.pmd.lang.java.types.ast.InternalApiBridge.canGiveContextToPoly;
12
import static net.sourceforge.pmd.lang.java.types.ast.InternalApiBridge.newInvocContext;
13
import static net.sourceforge.pmd.lang.java.types.ast.InternalApiBridge.newOtherContext;
14
import static net.sourceforge.pmd.util.AssertionUtil.shouldNotReachHere;
15
import static net.sourceforge.pmd.util.CollectionUtil.all;
16
import static net.sourceforge.pmd.util.CollectionUtil.map;
17
import static net.sourceforge.pmd.util.OptionalBool.NO;
18
import static net.sourceforge.pmd.util.OptionalBool.UNKNOWN;
19
import static net.sourceforge.pmd.util.OptionalBool.YES;
20
import static net.sourceforge.pmd.util.OptionalBool.definitely;
21

22
import java.util.EnumMap;
23
import java.util.List;
24
import java.util.Map;
25

26
import org.checkerframework.checker.nullness.qual.NonNull;
27
import org.checkerframework.checker.nullness.qual.Nullable;
28

29
import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
30
import net.sourceforge.pmd.lang.java.ast.ASTArrayAccess;
31
import net.sourceforge.pmd.lang.java.ast.ASTArrayInitializer;
32
import net.sourceforge.pmd.lang.java.ast.ASTAssertStatement;
33
import net.sourceforge.pmd.lang.java.ast.ASTAssignmentExpression;
34
import net.sourceforge.pmd.lang.java.ast.ASTCastExpression;
35
import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
36
import net.sourceforge.pmd.lang.java.ast.ASTEnumConstant;
37
import net.sourceforge.pmd.lang.java.ast.ASTExplicitConstructorInvocation;
38
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
39
import net.sourceforge.pmd.lang.java.ast.ASTForeachStatement;
40
import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
41
import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression;
42
import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression;
43
import net.sourceforge.pmd.lang.java.ast.ASTLoopStatement;
44
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
45
import net.sourceforge.pmd.lang.java.ast.ASTMethodReference;
46
import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
47
import net.sourceforge.pmd.lang.java.ast.ASTSwitchArrowBranch;
48
import net.sourceforge.pmd.lang.java.ast.ASTSwitchExpression;
49
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
50
import net.sourceforge.pmd.lang.java.ast.ASTSwitchLike;
51
import net.sourceforge.pmd.lang.java.ast.ASTType;
52
import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpression;
53
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
54
import net.sourceforge.pmd.lang.java.ast.ASTVoidType;
55
import net.sourceforge.pmd.lang.java.ast.ASTYieldStatement;
56
import net.sourceforge.pmd.lang.java.ast.BinaryOp;
57
import net.sourceforge.pmd.lang.java.ast.InternalApiBridge;
58
import net.sourceforge.pmd.lang.java.ast.InvocationNode;
59
import net.sourceforge.pmd.lang.java.ast.JavaNode;
60
import net.sourceforge.pmd.lang.java.ast.TypeNode;
61
import net.sourceforge.pmd.lang.java.ast.internal.JavaAstUtils;
62
import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol;
63
import net.sourceforge.pmd.lang.java.types.JClassType;
64
import net.sourceforge.pmd.lang.java.types.JMethodSig;
65
import net.sourceforge.pmd.lang.java.types.JPrimitiveType;
66
import net.sourceforge.pmd.lang.java.types.JPrimitiveType.PrimitiveTypeKind;
67
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
68
import net.sourceforge.pmd.lang.java.types.OverloadSelectionResult;
69
import net.sourceforge.pmd.lang.java.types.TypeConversion;
70
import net.sourceforge.pmd.lang.java.types.TypeOps;
71
import net.sourceforge.pmd.lang.java.types.TypeSystem;
72
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
73
import net.sourceforge.pmd.lang.java.types.TypesFromReflection;
74
import net.sourceforge.pmd.lang.java.types.ast.ExprContext;
75
import net.sourceforge.pmd.lang.java.types.ast.ExprContext.ExprContextKind;
76
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.BranchingMirror;
77
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.FunctionalExprMirror;
78
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.InvocationMirror;
79
import net.sourceforge.pmd.lang.java.types.internal.infer.Infer;
80
import net.sourceforge.pmd.lang.java.types.internal.infer.MethodCallSite;
81
import net.sourceforge.pmd.lang.java.types.internal.infer.PolySite;
82
import net.sourceforge.pmd.lang.java.types.internal.infer.ast.JavaExprMirrors;
83
import net.sourceforge.pmd.util.OptionalBool;
84

85
/**
86
 * Routines to handle context around poly expressions.
87
 */
88
public final class PolyResolution {
1✔
89

90
    private final Infer infer;
91
    private final TypeSystem ts;
92
    private final JavaExprMirrors exprMirrors;
93
    private final ExprContext booleanCtx;
94
    private final ExprContext stringCtx;
95
    private final ExprContext intCtx;
96
    private final Map<PrimitiveTypeKind, ExprContext> numericContexts;
97

98
    PolyResolution(Infer infer) {
1✔
99
        this.infer = infer;
1✔
100
        this.ts = infer.getTypeSystem();
1✔
101
        this.exprMirrors = JavaExprMirrors.forTypeResolution(infer);
1✔
102

103
        this.stringCtx = newStringCtx(ts);
1✔
104
        this.booleanCtx = newNonPolyContext(ts.BOOLEAN);
1✔
105
        this.numericContexts = new EnumMap<>(PrimitiveTypeKind.class);
1✔
106
        for (PrimitiveTypeKind kind : PrimitiveTypeKind.values()) {
1✔
107
            if (kind != PrimitiveTypeKind.BOOLEAN) {
1✔
108
                this.numericContexts.put(kind, newOtherContext(ts.getPrimitive(kind), ExprContextKind.NUMERIC));
1✔
109
            }
110
        }
111
        this.intCtx = numericContexts.get(PrimitiveTypeKind.INT);
1✔
112
    }
1✔
113

114
    private boolean isPreJava8() {
115
        return infer.isPreJava8();
1✔
116
    }
117

118
    JTypeMirror computePolyType(final TypeNode e) {
119
        if (!canBePoly(e)) {
1!
120
            throw shouldNotReachHere("Unknown poly " + e);
×
121
        }
122

123
        ExprContext ctx = getTopLevelConversionContext(e);
1✔
124

125
        InvocationNode outerInvocNode = ctx.getInvocNodeIfInvocContext();
1✔
126
        if (outerInvocNode != null) {
1✔
127
            return polyTypeInvocationCtx(e, outerInvocNode);
1✔
128
        }
129

130
        return polyTypeOtherCtx(e, ctx);
1✔
131
    }
132

133
    private JTypeMirror polyTypeOtherCtx(TypeNode e, ExprContext ctx) {
134
        // we have a context, that is not an invocation
135
        if (e instanceof InvocationNode) {
1✔
136
            // The original expr was an invocation, but we have
137
            // a context type (eg assignment context)
138
            JTypeMirror targetType = ctx.getPolyTargetType(false);
1✔
139

140
            return inferInvocation((InvocationNode) e, e, targetType);
1✔
141
        } else if (e instanceof ASTSwitchExpression || e instanceof ASTConditionalExpression) {
1✔
142
            // Those are standalone if possible, otherwise they take
143
            // the target type
144

145
            // in java 7 they are always standalone
146
            if (isPreJava8()) {
1✔
147
                // safe cast because ASTSwitchExpression doesn't exist pre java 13
148
                ASTConditionalExpression conditional = (ASTConditionalExpression) e;
1✔
149
                return computeStandaloneConditionalType(
1✔
150
                    this.ts,
151
                    conditional.getThenBranch().getTypeMirror(),
1✔
152
                    conditional.getElseBranch().getTypeMirror()
1✔
153
                );
154
            }
155

156
            // Note that this creates expr mirrors for all subexpressions,
157
            // and may trigger inference on them (which does not go through PolyResolution).
158
            // Because this process may fail if the conditional is not standalone,
159
            // the ctors for expr mirrors must have only trivial side-effects.
160
            // See comment in MethodRefMirrorImpl
161
            JTypeMirror target = ctx.getPolyTargetType(false);
1✔
162
            if (target != null) {
1✔
163
                // then it is a poly expression
164
                // only reference conditional expressions take the target type,
165
                // but the spec special-cases some forms of conditionals ("numeric" and "boolean")
166
                // The mirror recognizes these special cases
167
                BranchingMirror polyMirror = exprMirrors.getPolyBranchingMirror((ASTExpression) e);
1✔
168
                JTypeMirror standaloneType = polyMirror.getStandaloneType();
1✔
169
                if (standaloneType != null) { // then it is one of those special cases
1✔
170
                    polyMirror.setStandalone(); // record this fact
1✔
171
                    return standaloneType;
1✔
172
                }
173
                // otherwise it's the target type
174
                return target;
1✔
175
            } else {
176
                // then it is standalone
177
                BranchingMirror branchingMirror = exprMirrors.getStandaloneBranchingMirror((ASTExpression) e);
1✔
178
                branchingMirror.setStandalone(); // record this fact
1✔
179

180
                JTypeMirror standalone = branchingMirror.getStandaloneType();
1✔
181
                if (standalone != null) {
1✔
182
                    return standalone;
1✔
183
                } else if (!canGiveContextToPoly(ctx, false)) {
1!
184

185
                    // null standalone, force resolution anyway, because there is no context
186
                    // this is more general than ExprMirror#getStandaloneType, it's not a bug
187
                    if (e instanceof ASTSwitchExpression) {
1!
188
                        // todo merge this fallback into SwitchMirror
189
                        //  That would be less easily testable that what's below...
190
                        List<JTypeMirror> branches = ((ASTSwitchExpression) e).getYieldExpressions().toList(TypeNode::getTypeMirror);
1✔
191
                        return computeStandaloneConditionalType(ts, branches);
1✔
192
                    } else {
NEW
193
                        throw shouldNotReachHere("ConditionalMirrorImpl returns non-null for conditionals: " + e);
×
194
                    }
195
                }
196
                return ts.ERROR;
×
197
            }
198
        } else if (e instanceof ASTMethodReference || e instanceof ASTLambdaExpression) {
1!
199
            // these may use a cast as a target type
200
            JTypeMirror targetType = ctx.getPolyTargetType(true);
1✔
201
            return inferLambdaOrMref((ASTExpression) e, targetType);
1✔
202
        } else {
203
            throw shouldNotReachHere("Unknown poly " + e);
×
204
        }
205
    }
206

207
    // only outside of invocation context
208
    private JTypeMirror inferLambdaOrMref(ASTExpression e, @Nullable JTypeMirror targetType) {
209
        FunctionalExprMirror mirror = exprMirrors.getTopLevelFunctionalMirror(e);
1✔
210
        PolySite<FunctionalExprMirror> site = infer.newFunctionalSite(mirror, targetType);
1✔
211
        infer.inferFunctionalExprInUnambiguousContext(site);
1✔
212
        JTypeMirror result = InternalApiBridge.getTypeMirrorInternal(e);
1✔
213
        assert result != null : "Should be unknown";
1!
214
        return result;
1✔
215
    }
216

217
    private @NonNull JTypeMirror polyTypeInvocationCtx(TypeNode e, InvocationNode ctxInvoc) {
218
        // an outer invocation ctx
219
        if (ctxInvoc instanceof ASTExpression) {
1✔
220
            // method call or regular constructor call
221
            // recurse, that will fetch the outer context
222
            ctxInvoc.getTypeMirror();
1✔
223
            return fetchCascaded(e);
1✔
224
        } else {
225
            return inferInvocation(ctxInvoc, e, null);
1✔
226
        }
227
    }
228

229
    /**
230
     * Given an invocation context (ctxNode), infer its most specific
231
     * method, which will set the type of the 'enclosed' poly expression.
232
     * The 'targetType' can influence the invocation type of the method
233
     * (not applicability).
234
     *
235
     * <p>Eg:
236
     *
237
     * <pre>{@code
238
     *
239
     *     <T> T coerce(int i) {
240
     *         return null;
241
     *     }
242
     *
243
     *     <K> Stream<K> streamK() {
244
     *         return Stream.of(1, 2).map(this::coerce);
245
     *     }
246
     *
247
     * }</pre>
248
     *
249
     * <p>There is only one applicable method for this::coerce so the
250
     * method reference is exact. However the type argument {@code <T>}
251
     * of coerce has no bound. The target type {@code Stream<K>} is
252
     * incorporated and we infer that coerce's type argument is {@code <K>}.
253
     *
254
     * <p>This is also why the following fails type inference:
255
     *
256
     * <pre>{@code
257
     *
258
     *     <K> List<K> streamK2() {
259
     *         // type checks when written this::<K>coerce
260
     *         return Stream.of(1, 2).map(this::coerce).collect(Collectors.toList());
261
     *     }
262
     *
263
     * }</pre>
264
     */
265
    private JTypeMirror inferInvocation(InvocationNode ctxNode, TypeNode actualResultTarget, @Nullable JTypeMirror targetType) {
266
        InvocationMirror mirror = exprMirrors.getTopLevelInvocationMirror(ctxNode);
1✔
267
        MethodCallSite site = infer.newCallSite(mirror, targetType);
1✔
268
        infer.inferInvocationRecursively(site);
1✔
269
        // errors are on the call site if any
270

271
        return fetchCascaded(actualResultTarget);
1✔
272
    }
273

274
    /**
275
     * Fetch the resolved value when it was inferred as part of overload
276
     * resolution of an enclosing invocation context.
277
     */
278
    private @NonNull JTypeMirror fetchCascaded(TypeNode e) {
279
        // Some types are set as part of overload resolution
280
        // Conditional expressions also have their type set if they're
281
        // standalone
282
        JTypeMirror type = InternalApiBridge.getTypeMirrorInternal(e);
1✔
283
        if (type != null) {
1✔
284
            return type;
1✔
285
        }
286

287
        if (e.getParent().getParent() instanceof InvocationNode) {
1✔
288
            // invocation ctx
289
            InvocationNode parentInvoc = (InvocationNode) e.getParent().getParent();
1✔
290
            OverloadSelectionResult info = parentInvoc.getOverloadSelectionInfo();
1✔
291
            if (!info.isFailed()) {
1✔
292
                JTypeMirror targetT = info.ithFormalParam(e.getIndexInParent());
1✔
293
                if (e instanceof ASTLambdaExpression || e instanceof ASTMethodReference) {
1✔
294
                    // their types are not completely set
295
                    return inferLambdaOrMref((ASTExpression) e, targetT);
1✔
296
                }
297
                return targetT;
1✔
298
            }
299
        }
300

301
        // if we're here, we failed
302
        return fallbackIfCtxDidntSet(e);
1✔
303
    }
304

305
    /**
306
     * If resolution of the outer context failed, like if we call an unknown
307
     * method, we may still be able to derive the types of the arguments. We
308
     * treat them as if they occur as standalone expressions.
309
     * TODO would using error-type as a target type be better? could coerce
310
     * generic method params to error naturally
311
     */
312
    private @NonNull JTypeMirror fallbackIfCtxDidntSet(@Nullable TypeNode e) {
313
        // retry with no context
314
        return polyTypeOtherCtx(e, ExprContext.getMissingInstance());
1✔
315
        // infer.LOG.polyResolutionFailure(e);
316
    }
317

318
    /**
319
     * If true, the expression may depends on its target type. There may not
320
     * be a target type though - this is given by the {@link #contextOf(JavaNode, boolean, boolean)}.
321
     *
322
     * <p>If false, then the expression is standalone and its type is
323
     * only determined by the type of its subexpressions.
324
     */
325
    private static boolean canBePoly(TypeNode e) {
326
        return e instanceof ASTLambdaExpression
1!
327
            || e instanceof ASTMethodReference
328
            || e instanceof ASTConditionalExpression
329
            || e instanceof ASTSwitchExpression
330
            || e instanceof InvocationNode;
331
    }
332

333
    /**
334
     * Fallback for some standalone expressions, that may use some context
335
     * to set their type. This must not trigger any type inference process
336
     * that may need this expression. So if this expression is in an invocation
337
     * context, that context must not be called.
338
     */
339
    JTypeMirror getContextTypeForStandaloneFallback(ASTExpression e) {
340
        // Some symbol is not resolved
341
        // go backwards from the context to get it.
342

343
        // The case mentioned by the doc is removed. We could be smarter
344
        // with how we retry failed invocation resolution, see history
345
        // of this comment
346

347
        @NonNull ExprContext ctx = getTopLevelConversionContext(e);
1✔
348

349
        if (e.getParent() instanceof ASTSwitchLabel) {
1✔
350
            ASTSwitchLike switchLike = e.ancestors(ASTSwitchLike.class).firstOrThrow();
1✔
351
            // this may trigger some inference, which doesn't matter
352
            // as it is out of context
353
            return switchLike.getTestedExpression().getTypeMirror();
1✔
354
        }
355

356
        if (ctx instanceof RegularCtx) {
1✔
357
            JTypeMirror targetType = ctx.getPolyTargetType(false);
1✔
358
            if (targetType != null) {
1✔
359
                return targetType;
1✔
360
            }
361
        }
362

363
        return ts.UNKNOWN;
1✔
364
    }
365

366
    /**
367
     * Not meant to be used by the main typeres paths, only for rules.
368
     */
369
    ExprContext getConversionContextForExternalUse(ASTExpression e) {
370
        return contextOf(e, false, false);
1✔
371
    }
372

373
    ExprContext getTopLevelConversionContext(TypeNode e) {
374
        return contextOf(e, false, true);
1✔
375
    }
376

377
    /**
378
     * Returns the node on which the type of the given node depends.
379
     * This addresses the fact that poly expressions depend on their
380
     * surrounding context for a target type. So when someone asks
381
     * for the type of a poly, we have to determine the type of the
382
     * context before we can determine the type of the poly.
383
     *
384
     * <p>The returned context may never be a conditional or switch,
385
     * those just forward an outer context to their branches.
386
     *
387
     * <p>If there is no context node, returns null.
388
     *
389
     * Examples:
390
     * <pre>
391
     *
392
     * new Bar<>(foo())  // contextOf(methodCall) = constructorCall
393
     *
394
     * this(foo())       // contextOf(methodCall) = explicitConstructorInvoc
395
     *
396
     * a = foo()         // contextOf(methodCall) = assignmentExpression
397
     * a = (Cast) foo()  // contextOf(methodCall) = castExpression
398
     * return foo();     // contextOf(methodCall) = returnStatement
399
     *
400
     * foo(a ? () -> b   // the context of each lambda, and of the conditional, is the methodCall
401
     *       : () -> c)
402
     *
403
     * foo();            // expression statement, no target type
404
     *
405
     * 1 + (a ? foo() : 2) //  contextOf(methodCall) = null
406
     *                     //  foo() here has no target type, because the enclosing conditional has none
407
     *
408
     *
409
     * </pre>
410
     */
411
    private @NonNull ExprContext contextOf(final JavaNode node, boolean onlyInvoc, boolean internalUse) {
412
        final JavaNode papa = node.getParent();
1✔
413
        if (papa instanceof ASTArgumentList) {
1✔
414
            // invocation context, return *the first method*
415
            // eg in
416
            // lhs = foo(bar(bog())),
417
            // contextOf(bog) = bar, contextOf(bar) = foo, contextOf(foo) = lhs
418
            // when asked the type of 'foo', we return 'bar'
419
            // we recurse indirectly and ask 'bar' for its type
420
            // it asks 'foo', which binds to 'lhs', and infers all types
421

422
            // we can't just recurse directly up, because then contextOf(bog) = lhs,
423
            // and that's not true (bog() is in an invocation context)
424
            final InvocationNode papi = (InvocationNode) papa.getParent();
1✔
425

426
            if (papi instanceof ASTExplicitConstructorInvocation || papi instanceof ASTEnumConstant) {
1✔
427
                return newInvocContext(papi, node.getIndexInParent());
1✔
428
            } else {
429
                if (isPreJava8()) {
1✔
430
                    // in java < 8 invocation contexts don't provide a target type
431
                    return ExprContext.getMissingInstance();
1✔
432
                }
433

434
                if (!internalUse) {
1✔
435
                    // Only in type resolution do we need to fetch the outermost context
436
                    return newInvocContext(papi, node.getIndexInParent());
1✔
437
                }
438

439
                // Constructor or method call, maybe there's another context around
440
                // We want to fetch the outermost invocation node, but not further
441
                ExprContext outerCtx = contextOf(papi, /*onlyInvoc:*/true, internalUse);
1✔
442
                return canGiveContextToPoly(outerCtx, false)
1✔
443
                       ? outerCtx
1✔
444
                       // otherwise we're done, this is the outermost context
445
                       : newInvocContext(papi, node.getIndexInParent());
1✔
446
            }
447
        } else if (doesCascadesContext(papa, node, internalUse)) {
1✔
448
            // switch/conditional
449
            return contextOf(papa, onlyInvoc, internalUse);
1✔
450
        }
451

452
        if (onlyInvoc) {
1✔
453
            return ExprContext.getMissingInstance();
1✔
454
        }
455

456
        if (papa instanceof ASTArrayInitializer) {
1✔
457

458
            JTypeMirror target = TypeOps.getArrayComponent(((ASTArrayInitializer) papa).getTypeMirror());
1✔
459
            return newAssignmentCtx(target);
1✔
460

461
        } else if (papa instanceof ASTCastExpression) {
1✔
462

463
            JTypeMirror target = ((ASTCastExpression) papa).getCastType().getTypeMirror();
1✔
464
            return newCastCtx(target);
1✔
465

466
        } else if (papa instanceof ASTAssignmentExpression && node.getIndexInParent() == 1) { // second operand
1✔
467

468
            JTypeMirror target = ((ASTAssignmentExpression) papa).getLeftOperand().getTypeMirror();
1✔
469
            return newAssignmentCtx(target);
1✔
470

471
        } else if (papa instanceof ASTReturnStatement) {
1✔
472

473
            return newAssignmentCtx(returnTargetType((ASTReturnStatement) papa, internalUse));
1✔
474

475

476
        } else if (papa instanceof ASTVariableDeclarator
1✔
477
            && !((ASTVariableDeclarator) papa).getVarId().isTypeInferred()) {
1✔
478

479
            return newAssignmentCtx(((ASTVariableDeclarator) papa).getVarId().getTypeMirror());
1✔
480

481
        } else if (papa instanceof ASTYieldStatement) {
1✔
482

483
            // break with value (switch expr)
484
            ASTSwitchExpression owner = ((ASTYieldStatement) papa).getYieldTarget();
1✔
485
            return contextOf(owner, false, internalUse);
1✔
486

487
        } else if (node instanceof ASTExplicitConstructorInvocation
1✔
488
            && ((ASTExplicitConstructorInvocation) node).isSuper()) {
1✔
489

490
            // the superclass type is taken as a target type for inference,
491
            // when the super ctor is generic/ the superclass is generic
492
            return newSuperCtorCtx(node.getEnclosingType().getTypeMirror().getSuperClass());
1✔
493

494
        }
495

496
        if (!internalUse) {
1✔
497
            // Only ASTExpression#getConversionContext needs this level of detail
498
            // These anyway do not give a context to poly expression so can be ignored
499
            // for poly resolution.
500
            return conversionContextOf(node, papa);
1✔
501
        }
502

503
        // stop recursion
504
        return ExprContext.getMissingInstance();
1✔
505
    }
506

507
    // more detailed, only for external use
508
    private ExprContext conversionContextOf(JavaNode node, JavaNode papa) {
509
        if (papa instanceof ASTArrayAccess && node.getIndexInParent() == 1) {
1!
510

511
            // array index
512
            return intCtx;
1✔
513

514
        } else if (papa instanceof ASTAssertStatement) {
1✔
515

516
            return node.getIndexInParent() == 0 ? booleanCtx // condition
1✔
517
                                                : stringCtx; // message
1✔
518

519
        } else if (papa instanceof ASTLambdaExpression && node.getIndexInParent() == 1) {
1!
520
            // lambda expression body
521

522
            JTypeMirror ty = getContextTypeOfLambdaReturnExpr((ASTLambdaExpression) papa, false);
1✔
523
            return ty == null ? ExprContext.getMissingInstance() : newAssignmentCtx(ty);
1✔
524

525
        } else if (papa instanceof ASTIfStatement
1✔
526
            || papa instanceof ASTLoopStatement && !(papa instanceof ASTForeachStatement)) {
527

528
            return booleanCtx; // condition
1✔
529

530
        } else if (papa instanceof ASTConditionalExpression) {
1✔
531

532
            if (node.getIndexInParent() == 0) {
1✔
533
                return booleanCtx; // the condition
1✔
534
            } else {
535
                // a branch
536
                if (isPreJava8()) {
1✔
537
                    return ExprContext.getMissingInstance();
1✔
538
                }
539
                assert InternalApiBridge.isStandaloneInternal((ASTConditionalExpression) papa)
1!
540
                    : "Expected standalone ternary, otherwise doesCascadeContext(..) would have returned true";
541

542
                return newStandaloneTernaryCtx(((ASTConditionalExpression) papa).getTypeMirror());
1✔
543
            }
544

545
        } else if (papa instanceof ASTInfixExpression) {
1✔
546
            // numeric contexts, maybe
547
            BinaryOp op = ((ASTInfixExpression) papa).getOperator();
1✔
548
            JTypeMirror nodeType = ((ASTExpression) node).getTypeMirror();
1✔
549
            JTypeMirror otherType = JavaAstUtils.getOtherOperandIfInInfixExpr(node).getTypeMirror();
1✔
550
            JTypeMirror ctxType = ((ASTInfixExpression) papa).getTypeMirror();
1✔
551
            switch (op) {
1!
552
            case CONDITIONAL_OR:
553
            case CONDITIONAL_AND:
554
                return booleanCtx;
1✔
555
            case OR:
556
            case XOR:
557
            case AND:
558
                return ctxType == ts.BOOLEAN ? booleanCtx : getNumericContext(ctxType);
1✔
559
            case LEFT_SHIFT:
560
            case RIGHT_SHIFT:
561
            case UNSIGNED_RIGHT_SHIFT:
562
                return node.getIndexInParent() == 1 ? intCtx
1✔
563
                                                    : getNumericContext(nodeType.unbox());
1✔
564
            case EQ:
565
            case NE:
566
                if (otherType.isNumeric() || nodeType.isNumeric()) {
1✔
567
                    JTypeMirror prom = TypeConversion.binaryNumericPromotion(otherType.unbox(), nodeType.unbox());
1✔
568
                    if (prom == ts.ERROR) {
1!
569
                        // cannot be promoted
570
                        return ExprContext.getMissingInstance();
×
571
                    }
572
                    return getNumericContext(prom);
1✔
573
                } else if (otherType.isPrimitive(PrimitiveTypeKind.BOOLEAN)
1✔
574
                    || nodeType.isPrimitive(PrimitiveTypeKind.BOOLEAN)) {
1!
575
                    return booleanCtx;
1✔
576
                }
577

578
                return ExprContext.getMissingInstance();
×
579
            case ADD:
580
                if (TypeTestUtil.isA(String.class, ctxType)) {
1✔
581
                    // string concat expr
582
                    return stringCtx;
1✔
583
                }
584
                // fallthrough
585
            case SUB:
586
            case MUL:
587
            case DIV:
588
            case MOD:
589
                return getNumericContext(ctxType); // binary promoted by LazyTypeResolver
1✔
590
            case LE:
591
            case GE:
592
            case GT:
593
            case LT:
594
                return getNumericContext(TypeConversion.binaryNumericPromotion(nodeType, otherType));
1✔
595
            default:
596
                return ExprContext.getMissingInstance();
×
597
            }
598
        } else if (papa instanceof ASTUnaryExpression) {
1✔
599
            switch (((ASTUnaryExpression) papa).getOperator()) {
1✔
600
            case UNARY_PLUS:
601
            case UNARY_MINUS:
602
            case COMPLEMENT:
603
                JTypeMirror parentType = ((ASTUnaryExpression) papa).getTypeMirror();
1✔
604
                if (parentType == ts.ERROR) {
1!
605
                    break;
×
606
                }
607
                // this was already unary promoted
608
                return getNumericContext(parentType);
1✔
609
            case NEGATION:
610
                return booleanCtx;
1✔
611
            default:
612
                break;
613
            }
614
            return ExprContext.getMissingInstance();
1✔
615
        } else if (papa instanceof ASTSwitchLike && node.getIndexInParent() == 0) {
1!
616
            return getNumericContext(((ASTExpression) node).getTypeMirror().unbox());
1✔
617
        } else {
618
            return ExprContext.getMissingInstance();
1✔
619
        }
620
    }
621

622
    private @Nullable JTypeMirror returnTargetType(ASTReturnStatement context, boolean internalUse) {
623
        JavaNode returnTarget = JavaAstUtils.getReturnTarget(context);
1✔
624

625
        if (returnTarget instanceof ASTLambdaExpression) {
1✔
626
            // return within a lambda
627
            // "assignment context", deferred to lambda inference
628
            return getContextTypeOfLambdaReturnExpr((ASTLambdaExpression) returnTarget, internalUse);
1✔
629
        } else if (returnTarget instanceof ASTMethodDeclaration) {
1✔
630
            @NonNull ASTType resultType = ((ASTMethodDeclaration) returnTarget).getResultTypeNode();
1✔
631
            return resultType instanceof ASTVoidType ? null // (this is an error)
1✔
632
                                                     : resultType.getTypeMirror();
1✔
633
        }
634
        // Return within ctor or initializer or the like,
635
        // return with value is disallowed. This is an error.
636
        return null;
1✔
637
    }
638

639
    private @Nullable JTypeMirror getContextTypeOfLambdaReturnExpr(ASTLambdaExpression papa, boolean internalUse) {
640
        JMethodSig fun = papa.getFunctionalMethod();
1✔
641
        // If we're in "internal use" mode, then we cannot call isLambdaReturnExprInDependentContext
642
        // because that would trigger overload resolution of the parent invocation if any.
643
        if (fun == null || !internalUse && isLambdaReturnExprInDependentContext(papa) != NO) {
1!
644
            return null;
1✔
645
        }
646

647
        // This is the target type for the lambda, as given by
648
        // the generic method declaration. For instance if the
649
        // lambda appears in `foo(() -> X)`:
650
        // - if `foo` has signature `<T> foo(Supplier<T>)`, then this is `Supplier<T>`.
651
        //   This means the lambda's return expression type is used in the type inference of
652
        //   `foo`, in which case we return a missing context.
653
        // - if `foo` has signature `foo(Supplier<Long>)`, then this is Supplier<Long>.
654
        //   This means the lambda's return expression type is NOT used in the type inference of
655
        //   `foo`, as it is known to be Long. In this case we return an assignment context.
656
        return fun.getReturnType();
1✔
657
    }
658

659

660
    private OptionalBool isLambdaReturnExprInDependentContext(ASTLambdaExpression lambda) {
661
        // The necessary conditions are:
662
        // - The lambda return type mentions type vars that are missing from its arguments
663
        // (lambda is context dependent)
664
        // - The lambda type is inferred:
665
        //   1. its context is missing, or
666
        //   2. it is invocation and the parent method call
667
        //      a. is inferred (generic + no explicit arguments)
668
        //      b. mentions some of its type params in the argument type corresponding to the lambda
669

670
        JExecutableSymbol symbol = lambda.getFunctionalMethod().getSymbol();
1✔
671
        if (symbol.isUnresolved()) {
1✔
672
            return UNKNOWN;
1✔
673
        }
674

675
        // Note we don't test the functional method directly, because it has been instantiated
676
        // We test its generic signature (the symbol).
677
        boolean contextDependent = TypeOps.isContextDependent(symbol);
1✔
678
        if (!contextDependent) {
1✔
679
            return NO;
1✔
680
        }
681

682
        ExprContext lambdaCtx = getConversionContextForExternalUse(lambda);
1✔
683
        if (lambdaCtx.isMissing()) {
1!
NEW
684
            return YES;
×
685
        } else if (lambdaCtx.hasKind(ExprContextKind.CAST)) {
1✔
686
            return NO;
1✔
687
        } else if (lambdaCtx.hasKind(ExprContextKind.INVOCATION)) {
1✔
688
            // Note an invocation context does not mean the lambda's
689
            // parent is an argument list. There may be branching (switch,
690
            // ternary) exprs between the lambda and the invocation.
691
            int argIdx = lambda.getIndexInParent();
1✔
692
            JavaNode parent = lambda.getParent();
1✔
693
            while (!(parent instanceof ASTArgumentList)) {
1✔
694
                argIdx = parent.getIndexInParent();
1✔
695
                parent = parent.getParent();
1✔
696
            }
697

698
            InvocationNode parentCall = (InvocationNode) parent.getParent();
1✔
699
            if (parentCall.getExplicitTypeArguments() != null) {
1✔
700
                return NO;
1✔
701
            }
702
            OverloadSelectionResult overload = parentCall.getOverloadSelectionInfo();
1✔
703
            if (overload.isFailed()) {
1!
NEW
704
                return UNKNOWN;
×
705
            }
706

707
            // If the overload selection had to pick between several applicable overloads
708
            // then we reply yes. This is an approximation that should avoid FPs in rules that
709
            // use the context.
710
            if (overload.hadSeveralApplicableOverloads()) {
1✔
711
                return UNKNOWN; // maybe
1✔
712
            }
713

714
            JMethodSig parentMethod = overload.getMethodType();
1✔
715
            if (!parentMethod.getSymbol().isGeneric()) {
1✔
716
                return NO;
1✔
717
            }
718

719
            JMethodSig genericSig = parentMethod.getSymbol().getGenericSignature();
1✔
720
            // this is the generic lambda ty as mentioned in the formal parameters
721
            JTypeMirror genericLambdaTy = genericSig.ithFormalParam(argIdx, overload.isVarargsCall());
1✔
722
            if (!(genericLambdaTy instanceof JClassType)) {
1!
NEW
723
                return NO;
×
724
            }
725
            // Note that we don't capture this type, which may make the method type malformed (eg mentioning a wildcard
726
            // as return type). We need these bare wildcards for "mentionsAny" to work properly.
727
            // The "correct" approach here to remove wildcards would be to infer the ground non-wildcard parameterization
728
            // of the lambda but this is pretty deep inside the inference code and not readily usable.
729
            JClassType lambdaTyCapture = (JClassType) genericLambdaTy;
1✔
730

731
            // This is the method signature of the lambda, given the formal parameter type of the parent call.
732
            // The formal type is not instantiated, it may contain type variables of the parent method...
733
            JMethodSig expectedLambdaMethod = genericLambdaTy.getTypeSystem().sigOf(
1✔
734
                lambda.getFunctionalMethod().getSymbol(),
1✔
735
                lambdaTyCapture.getTypeParamSubst()
1✔
736
            );
737
            // but if the return type does not contain such tvars, then the parent method type does
738
            // not depend on the lambda type :)
739
            return definitely(
1✔
740
                TypeOps.mentionsAny(
1✔
741
                    expectedLambdaMethod.getReturnType(),
1✔
742
                    parentMethod.getTypeParameters()
1✔
743
                )
744
            );
745
        }
746
        return UNKNOWN;
1✔
747
    }
748

749

750

751
    /**
752
     * Identifies a node that can forward an invocation/assignment context
753
     * inward. If their parent has no context, then they don't either.
754
     */
755
    private boolean doesCascadesContext(JavaNode node, JavaNode child, boolean internalUse) {
756
        if (child.getParent() != node) {
1!
757
            // means the "node" is a "stop recursion because no context" result in contextOf
758
            return false;
×
759
        } else if (isPreJava8()) {
1✔
760
            // in java < 8, context doesn't flow through ternaries
761
            return false;
1✔
762
        } else if (!internalUse
1✔
763
            && node instanceof ASTConditionalExpression
764
            && child.getIndexInParent() != 0) {
1✔
765
            // conditional branch
766
            ((ASTConditionalExpression) node).getTypeMirror(); // force resolution
1✔
767
            return !InternalApiBridge.isStandaloneInternal((ASTConditionalExpression) node);
1✔
768
        }
769
        return node instanceof ASTSwitchExpression && child.getIndexInParent() != 0 // not the condition
1✔
770
            || node instanceof ASTSwitchArrowBranch
771
            || node instanceof ASTConditionalExpression && child.getIndexInParent() != 0 // not the condition
1✔
772
            || internalUse && node instanceof ASTLambdaExpression && child.getIndexInParent() == 1;
1!
773
    }
774

775

776
    // test only
777
    public static JTypeMirror computeStandaloneConditionalType(TypeSystem ts, JTypeMirror t2, JTypeMirror t3) {
778
        return computeStandaloneConditionalType(ts, asList(t2, t3));
1✔
779
    }
780

781
    /**
782
     * Compute the type of a conditional or switch expression. This is
783
     * how Javac does it for now, and it's exactly an extension of the
784
     * rules for ternary operators to an arbitrary number of branches.
785
     *
786
     * todo can we merge this into the logic of the BranchingMirror implementations?
787
     */
788
    private static JTypeMirror computeStandaloneConditionalType(TypeSystem ts, List<JTypeMirror> branchTypes) {
789
        // There is a corner case with constant values & ternaries, which we don't handle.
790

791
        if (branchTypes.isEmpty()) {
1✔
792
            return ts.OBJECT;
1✔
793
        }
794

795
        JTypeMirror head = branchTypes.get(0);
1✔
796
        List<JTypeMirror> tail = branchTypes.subList(1, branchTypes.size());
1✔
797

798
        if (all(tail, head::equals)) {
1✔
799
            return head;
1✔
800
        }
801

802

803
        List<JTypeMirror> unboxed = map(branchTypes, JTypeMirror::unbox);
1✔
804
        if (all(unboxed, JTypeMirror::isPrimitive)) {
1✔
805
            for (JPrimitiveType a : ts.allPrimitives) {
1✔
806
                if (all(unboxed, it -> it.isConvertibleTo(a).bySubtyping())) {
1✔
807
                    // then all types are convertible to a
808
                    return a;
1✔
809
                }
810
            }
1✔
811
        }
812

813
        List<JTypeMirror> boxed = map(branchTypes, JTypeMirror::box);
1✔
814
        for (JTypeMirror a : boxed) {
1✔
815
            if (all(unboxed, it -> isConvertibleUsingBoxing(it, a))) {
1✔
816
                // then all types are convertible to a through boxing
817
                return a;
1✔
818
            }
819
        }
1✔
820

821
        // at worse returns Object
822
        return ts.lub(branchTypes);
1✔
823
    }
824

825
    static ExprContext newAssignmentCtx(JTypeMirror targetType) {
826
        if (targetType == null) {
1✔
827
            // invalid syntax
828
            return ExprContext.getMissingInstance();
1✔
829
        }
830
        return newOtherContext(targetType, ExprContextKind.ASSIGNMENT);
1✔
831
    }
832

833
    static ExprContext newNonPolyContext(JTypeMirror targetType) {
834
        return newOtherContext(targetType, ExprContextKind.BOOLEAN);
1✔
835
    }
836

837
    static ExprContext newStringCtx(TypeSystem ts) {
838
        JClassType stringType = (JClassType) TypesFromReflection.fromReflect(String.class, ts);
1✔
839
        return newOtherContext(stringType, ExprContextKind.STRING);
1✔
840
    }
841

842
    ExprContext getNumericContext(JTypeMirror targetType) {
843
        if (targetType.isPrimitive()) {
1!
844
            assert targetType.isNumeric() : "Not a numeric type - " + targetType;
1!
845
            return numericContexts.get(((JPrimitiveType) targetType).getKind());
1✔
846
        }
847
        return ExprContext.getMissingInstance(); // error
×
848
    }
849

850
    static ExprContext newCastCtx(JTypeMirror targetType) {
851
        return newOtherContext(targetType, ExprContextKind.CAST);
1✔
852
    }
853

854
    static ExprContext newSuperCtorCtx(JTypeMirror superclassType) {
855
        return newOtherContext(superclassType, ExprContextKind.ASSIGNMENT);
1✔
856
    }
857

858
    static ExprContext newStandaloneTernaryCtx(JTypeMirror ternaryType) {
859
        return newOtherContext(ternaryType, ExprContextKind.TERNARY);
1✔
860
    }
861
}
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