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

pmd / pmd / 899

27 Oct 2026 08:46AM UTC coverage: 79.994% (+0.005%) from 79.989%
899

push

github

web-flow
[java] Fix #7146, #7149: Loose invocation conversion and the most specific check for boxing (#7163)

Infer.isConvertible boxed both sides and, when that failed, unboxed
both sides, so int was convertible to Long and null to int. JLS 5.3
allows boxing followed by widening reference conversion and unboxing
followed by widening primitive conversion. The most specific check in
PhaseOverloadSet compared parameter types with the same loose
convertibility and had a special case for boxed parameters. JLS
15.12.2.5 does not use boxing or unboxing for this comparison, so the
check now uses the strict conversion path and the special case is
removed.

A conditional with a null literal and a numeric or boolean branch is
typed as the box of that branch, as javac does, so h(b ? 1 : null)
still resolves to h(int). The branches of a standalone conditional
are checked against their own standalone types.

20557 of 26714 branches covered (76.95%)

Branch coverage included in aggregate %.

14 of 15 new or added lines in 3 files covered. (93.33%)

1 existing line in 1 file now uncovered.

43938 of 53911 relevant lines covered (81.5%)

0.82 hits per line

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

83.65
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/PhaseOverloadSet.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.TypeOps.areOverrideEquivalent;
8
import static net.sourceforge.pmd.lang.java.types.internal.InternalMethodTypeItf.cast;
9
import static net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.TypeSpecies.getSpecies;
10
import static net.sourceforge.pmd.util.OptionalBool.NO;
11
import static net.sourceforge.pmd.util.OptionalBool.UNKNOWN;
12
import static net.sourceforge.pmd.util.OptionalBool.YES;
13
import static net.sourceforge.pmd.util.OptionalBool.definitely;
14

15
import java.util.List;
16

17
import org.checkerframework.checker.nullness.qual.NonNull;
18

19
import net.sourceforge.pmd.lang.java.types.InternalApiBridge;
20
import net.sourceforge.pmd.lang.java.types.JMethodSig;
21
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
22
import net.sourceforge.pmd.lang.java.types.JTypeVar;
23
import net.sourceforge.pmd.lang.java.types.Substitution;
24
import net.sourceforge.pmd.lang.java.types.TypeConversion;
25
import net.sourceforge.pmd.lang.java.types.TypeOps;
26
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.BranchingMirror;
27
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.InvocationMirror.MethodCtDecl;
28
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.LambdaExprMirror;
29
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.MethodRefMirror;
30
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.TypeSpecies;
31
import net.sourceforge.pmd.util.OptionalBool;
32

33
/**
34
 * An {@link OverloadSet} that is specific to an invocation expression
35
 * and resolution phase. It selects the most specific methods using the
36
 * actual values of arguments, as specified in https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.5.
37
 */
38
final class PhaseOverloadSet extends OverloadSet<MethodCtDecl> {
1 ✔
39
    // TODO by mocking a call site we may be able to remove that logic
40
    //  Basically infer m1 against m2 with the same core routines as the
41
    //  main Infer.
42
    // m1 is more specific than m2 if m2 can handle more calls than m1
43

44
    // so try to infer applicability of m2 for argument types == formal param types of m1
45
    // yeah but this would ignore the shape of lambdas, we'd only get constraints
46
    // like f1(m1) <: f1(m2), ignoring that a lambda may be compatible with both types
47

48
    private final Infer infer;
49
    private final MethodResolutionPhase phase;
50
    private final MethodCallSite site;
51

52

53
    PhaseOverloadSet(Infer infer, MethodResolutionPhase phase, MethodCallSite site) {
1 ✔
54
        this.infer = infer;
1 ✔
55
        this.phase = phase;
1 ✔
56
        this.site = site;
1 ✔
57
    }
1 ✔
58

59
    public MethodResolutionPhase getPhase() {
60
        return phase;
×
61
    }
62

63
    public MethodCallSite getSite() {
64
        return site;
×
65
    }
66

67
    public Infer getInfer() {
68
        return infer;
×
69
    }
70

71
    /**
72
     * It's a given that the method is applicable to the site.
73
     *
74
     * <p>https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.5
75
     */
76
    @Override
77
    void add(MethodCtDecl sig) { // NOPMD UselessOverridingMethod
78
        super.add(sig);
1 ✔
79
    }
1 ✔
80

81
    public @NonNull MethodCtDecl getMostSpecificOrLogAmbiguity(TypeInferenceLogger logger) {
82
        assert nonEmpty();
1 !
83
        List<MethodCtDecl> overloads = getOverloadsMutable();
1 ✔
84
        MethodCtDecl main = overloads.get(0);
1 ✔
85
        if (overloads.size() != 1) {
1 ✔
86
            logger.ambiguityError(site, main, overloads);
1 ✔
87
            main = main.asFailed();
1 ✔
88
        }
89
        return main;
1 ✔
90
    }
91

92
    @Override
93
    protected OptionalBool shouldTakePrecedence(MethodCtDecl m1, MethodCtDecl m2) {
94
        return isMoreSpecific(cast(m1.getMethodType()).adaptedMethod(),
1 ✔
95
                              cast(m2.getMethodType()).adaptedMethod());
1 ✔
96
    }
97

98

99
    private OptionalBool isMoreSpecific(@NonNull JMethodSig m1, @NonNull JMethodSig m2) {
100

101
        OptionalBool m1OverM2 = isMoreSpecificForExpr(m1, m2);
1 ✔
102

103
        if (m1OverM2.isKnown()) {
1 ✔
104
            return m1OverM2;
1 ✔
105
        } else if (areOverrideEquivalent(m1, m2)) {
1 ✔
106
            JTypeMirror observingSite = site.getExpr().getReceiverType();
1 ✔
107
            if (observingSite == null) {
1 ✔
108
                observingSite = site.getExpr().getEnclosingType();
1 ✔
109
            }
110
            return OverloadSet.shouldAlwaysTakePrecedence(m1, m2, observingSite);
1 ✔
111
        }
112

113
        return UNKNOWN;
1 ✔
114
    }
115

116
    private OptionalBool isMoreSpecificForExpr(JMethodSig m1, JMethodSig m2) {
117
        boolean m1OverM2 = isInferredMoreSpecific(m1, m2);
1 ✔
118
        boolean m2OverM1 = isInferredMoreSpecific(m2, m1);
1 ✔
119
        if (m1OverM2 ^ m2OverM1) {
1 ✔
120
            return definitely(m1OverM2);
1 ✔
121
        }
122
        return UNKNOWN;
1 ✔
123
    }
124

125
    private boolean isInferredMoreSpecific(JMethodSig m1, JMethodSig m2) {
126
        // https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.5.4
127
        try {
128
            return doInfer(m1, m2);
1 ✔
129
        } catch (ResolutionFailedException e) {
1 ✔
130
            return false;
1 ✔
131
        }
132
    }
133

134
    private boolean doInfer(JMethodSig m1, JMethodSig m2) {
135
        MethodCallSite site = this.site.cloneForSpecificityCheck(infer);
1 ✔
136
        InferenceContext ctx = infer.newContextFor(m2);
1 ✔
137

138
        // even if m1 is generic, the type parameters of m1 are treated as type variables, not inference variables.
139

140
        JMethodSig m2p = ctx.mapToIVars(m2);
1 ✔
141

142
        List<ExprMirror> es = site.getExpr().getArgumentExpressions();
1 ✔
143

144
        int k = es.size();
1 ✔
145

146
        for (int i = 0; i < k; i++) {
1 ✔
147
            JTypeMirror ti = m2p.ithFormalParam(i, phase.requiresVarargs());
1 ✔
148
            JTypeMirror si = m1.ithFormalParam(i, phase.requiresVarargs());
1 ✔
149
            ExprMirror ei = es.get(i);
1 ✔
150

151
            if (si.equals(ti)) {
1 ✔
152
                continue;
1 ✔
153
            }
154

155
            // needs to go before subtyping checks, because those will always succeed
156
            if (unresolvedTypeFallback(si, ti, ei) == NO) {
1 ✔
157
                return false;
1 ✔
158
            }
159

160
            if (si.isSubtypeOf(ti)) {
1 ✔
161
                continue;
1 ✔
162
            } else if (ti.isSubtypeOf(si)) {
1 ✔
163
                return false;
1 ✔
164
            }
165

166
            JMethodSig sfun = TypeOps.findFunctionalInterfaceMethod(si);
1 ✔
167
            JMethodSig tfun = TypeOps.findFunctionalInterfaceMethod(ti);
1 ✔
168
            if (sfun == null || tfun == null) {
1 ✔
169
                // Boxing and unboxing take no part in this comparison (JLS 15.12.2.5,
170
                // 18.5.4), even in the loose and varargs phases.
NEW
171
                infer.checkConvertibleOrDefer(ctx, si, ti, ei, MethodResolutionPhase.STRICT, site);
×
UNCOV
172
                continue;
×
173
            }
174

175
            // otherwise they're both functional interfaces
176
            if (!isFunctionTypeMoreSpecific(ctx, si, sfun, tfun, ei, site)) {
1 ✔
177
                return false;
1 ✔
178
            }
179
        }
180

181
        if (phase.requiresVarargs() && m2p.getArity() == k + 1) {
1 ✔
182
            // that is, the invocation has no arguments for the varargs, eg Stream.of()
183
            infer.checkConvertibleOrDefer(ctx,
1 ✔
184
                                          m1.ithFormalParam(k, true),
1 ✔
185
                                          m2p.ithFormalParam(k, true),
1 ✔
186
                                          // m2Formals.get(k),
187
                                          site.getExpr(), MethodResolutionPhase.STRICT, site);
1 ✔
188
        }
189

190
        ctx.solve();
1 ✔
191
        ctx.callListeners();
1 ✔
192

193
        return true;
1 ✔
194
    }
195

196

197
    private @NonNull OptionalBool unresolvedTypeFallback(JTypeMirror si, JTypeMirror ti, ExprMirror argExpr) {
198
        JTypeMirror standalone = argExpr.getStandaloneType();
1 ✔
199
        if (TypeOps.hasUnresolvedSymbolOrArray(standalone)) {
1 ✔
200
            if (standalone.equals(si)) {
1 ✔
201
                return YES;
1 ✔
202
            } else if (standalone.equals(ti)) {
1 ✔
203
                return NO;
1 ✔
204
            }
205
        }
206
        return UNKNOWN;
1 ✔
207
    }
208

209
    private boolean isFunctionTypeMoreSpecific(InferenceContext ctx,
210
                                               JTypeMirror si,
211
                                               JMethodSig sfun,
212
                                               JMethodSig tfun,
213
                                               ExprMirror ei, MethodCallSite site) {
214
        if (sfun.getArity() != tfun.getArity()
1 ✔
215
            || sfun.getTypeParameters().size() != tfun.getTypeParameters().size()) {
1 !
216
            return false;
1 ✔
217
        }
218

219
        // Note that the following is not implemented entirely
220

221
        // The rest is described in https://docs.oracle.com/javase/specs/jls/se13/html/jls-18.html#jls-18.5.4
222

223
        JMethodSig capturedSFun = TypeOps.findFunctionalInterfaceMethod(TypeConversion.capture(si));
1 ✔
224
        assert capturedSFun != null;
1 !
225

226
        if (!TypeOps.haveSameTypeParams(capturedSFun, sfun)) {
1 !
227
            return false;
×
228
        }
229

230

231
        List<JTypeVar> sparams = sfun.getTypeParameters();
1 ✔
232
        List<JTypeVar> tparams = tfun.getTypeParameters();
1 ✔
233
        Substitution tToS = Substitution.mapping(tparams, sparams);
1 ✔
234
        for (int j = 0; j < sparams.size(); j++) {
1 !
235
            JTypeVar aj = sparams.get(j);
×
236
            JTypeVar bj = tparams.get(j);
×
237

238
            JTypeMirror x = aj.getUpperBound();
×
239
            JTypeMirror y = bj.getUpperBound();
×
240
            if (TypeOps.mentionsAny(x, sfun.getTypeParameters()) && !ctx.isGround(y)) {
×
241
                return false;
×
242
            } else {
243
                InternalApiBridge.isSameTypeInInference(x, y.subst(tToS)); // adds an equality constraint
×
244
            }
245
        }
246

247
        // todo something about formal params
248

249
        JTypeMirror rs = sfun.getReturnType();
1 ✔
250
        JTypeMirror rt = tfun.getReturnType();
1 ✔
251
        return (!TypeOps.mentionsAny(rs, sparams) || ctx.isGround(rt))
1 !
252
            && addGenericExprConstraintsRecursive(ctx, ei, rs, rt, tToS, site);
1 ✔
253
    }
254

255
    private boolean addGenericExprConstraintsRecursive(InferenceContext ctx, ExprMirror ei, JTypeMirror rs, JTypeMirror rt, Substitution tToS, MethodCallSite site) {
256
        if (ei instanceof LambdaExprMirror) {
1 ✔
257
            // Otherwise, if ei is an explicitly typed lambda expression:
258
            //
259
            //    If RT is void, true.
260
            //
261
            //    todo: Otherwise, if RS and RT are functional interface types, and ei has at least one result expression, then for each result expression in ei, this entire second step is repeated to infer constraints under which RS is more specific than RT θ' for the given result expression.
262
            //
263
            //    Otherwise, if RS is a primitive type and RT is not, and ei has at least one result expression, and each result expression of ei is a standalone expression (§15.2) of a primitive type, true.
264
            //
265
            //    Otherwise, if RT is a primitive type and RS is not, and ei has at least one result expression, and each result expression of ei is either a standalone expression of a reference type or a poly expression, true.
266
            //
267
            //    Otherwise, ‹RS <: RT θ'›.
268

269
            LambdaExprMirror lambda = (LambdaExprMirror) ei;
1 ✔
270
            if (!lambda.isExplicitlyTyped()) {
1 ✔
271
                return false;
1 ✔
272
            }
273

274
            if (getSpecies(rt) != getSpecies(rs)) {
1 !
275
                TypeSpecies requiredSpecies = getSpecies(rs);
1 ✔
276
                boolean sameSpecies = true;
1 ✔
277
                boolean atLeastOne = false;
1 ✔
278
                for (ExprMirror rexpr : lambda.getResultExpressions()) {
1 ✔
279
                    atLeastOne = true;
1 ✔
280
                    sameSpecies &= requiredSpecies == rexpr.getStandaloneSpecies();
1 ✔
281
                }
1 ✔
282

283
                if (sameSpecies && atLeastOne) {
1 !
284
                    return true;
1 ✔
285
                }
286
            }
287

288
            infer.checkConvertibleOrDefer(ctx, rs, rt.subst(tToS), ei, phase, site);
×
289
            return true;
×
290
        } else if (ei instanceof MethodRefMirror) {
1 ✔
291
            //  Otherwise, if ei is an exact method reference:
292
            //
293
            //    If RT is void, true.
294
            //
295
            //    Otherwise, if RS is a primitive type and RT is not, and the compile-time declaration for ei has a primitive return type, true.
296
            //
297
            //    Otherwise if RT is a primitive type and RS is not, and the compile-time declaration for ei has a reference return type, true.
298
            //
299
            //    Otherwise, ‹RS <: RT θ'›.
300

301
            JMethodSig exact = ExprOps.getExactMethod((MethodRefMirror) ei);
1 ✔
302
            if (exact == null) {
1 ✔
303
                return false;
1 ✔
304
            }
305

306
            if (getSpecies(rs) != getSpecies(rt)
1 !
307
                && getSpecies(exact.getReturnType()) == getSpecies(rs)) {
1 ✔
308
                return true;
1 ✔
309
            }
310

311
            infer.checkConvertibleOrDefer(ctx, rs, rt.subst(tToS), ei, phase, site);
×
312
            return true;
×
313
        } else if (ei instanceof BranchingMirror) {
1 !
314
            return ((BranchingMirror) ei).branchesMatch(e -> addGenericExprConstraintsRecursive(ctx, e, rs, rt, tToS, site));
×
315
        }
316

317
        return false;
1 ✔
318
    }
319
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc