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

pmd / pmd / 43

20 Jun 2025 06:39PM UTC coverage: 78.375% (-0.002%) from 78.377%
43

push

github

adangel
Fix #1639 #5832: Use filtered comment text for UnnecessaryImport (#5833)

Merged pull request #5833 from adangel:java/issue-5832-unnecessaryimport

17714 of 23438 branches covered (75.58%)

Branch coverage included in aggregate %.

3 of 3 new or added lines in 1 file covered. (100.0%)

109 existing lines in 17 files now uncovered.

38908 of 48807 relevant lines covered (79.72%)

0.81 hits per line

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

83.84
/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
                return true;
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
                if (phase.canBox()) {
1✔
170
                    JTypeMirror stdExprTy = ei.getStandaloneType();
1✔
171
                    if (stdExprTy != null
1!
172
                        // there is a boxing or unboxing conversion happening
173
                        && stdExprTy.isPrimitive() != si.isPrimitive()
1✔
174
                        && stdExprTy.isPrimitive() != ti.isPrimitive()) {
1!
175
                        // si or ti is more specific if it only involves
176
                        // the boxing/unboxing conversion, without widening
177
                        // afterwards.
178
                        if (stdExprTy.box().equals(si.box())) {
1✔
179
                            return true;
1✔
180
                        } else if (stdExprTy.box().equals(ti.box())) {
1✔
181
                            return false;
1✔
182
                        }
183
                    }
184
                }
185

UNCOV
186
                infer.checkConvertibleOrDefer(ctx, si, ti, ei, phase, site);
×
187
                continue;
×
188
            }
189

190
            // otherwise they're both functional interfaces
191
            if (!isFunctionTypeMoreSpecific(ctx, si, sfun, tfun, ei, site)) {
1✔
192
                return false;
1✔
193
            }
194
        }
195

196
        if (phase.requiresVarargs() && m2p.getArity() == k + 1) {
1!
197
            // that is, the invocation has no arguments for the varargs, eg Stream.of()
198
            infer.checkConvertibleOrDefer(ctx,
1✔
199
                                          m1.ithFormalParam(k, true),
1✔
200
                                          m2p.ithFormalParam(k, true),
1✔
201
                                          // m2Formals.get(k),
202
                                          site.getExpr(), phase, site);
1✔
203
        }
204

205
        ctx.solve();
1✔
206
        ctx.callListeners();
1✔
207

208
        return true;
1✔
209
    }
210

211

212
    private @NonNull OptionalBool unresolvedTypeFallback(JTypeMirror si, JTypeMirror ti, ExprMirror argExpr) {
213
        JTypeMirror standalone = argExpr.getStandaloneType();
1✔
214
        if (TypeOps.hasUnresolvedSymbolOrArray(standalone)) {
1✔
215
            if (standalone.equals(si)) {
1✔
216
                return YES;
1✔
217
            } else if (standalone.equals(ti)) {
1✔
218
                return NO;
1✔
219
            }
220
        }
221
        return UNKNOWN;
1✔
222
    }
223

224
    private boolean isFunctionTypeMoreSpecific(InferenceContext ctx,
225
                                               JTypeMirror si,
226
                                               JMethodSig sfun,
227
                                               JMethodSig tfun,
228
                                               ExprMirror ei, MethodCallSite site) {
229
        if (sfun.getArity() != tfun.getArity()
1✔
230
            || sfun.getTypeParameters().size() != tfun.getTypeParameters().size()) {
1!
231
            return false;
1✔
232
        }
233

234
        // Note that the following is not implemented entirely
235

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

238
        JMethodSig capturedSFun = TypeOps.findFunctionalInterfaceMethod(TypeConversion.capture(si));
1✔
239
        assert capturedSFun != null;
1!
240

241
        if (!TypeOps.haveSameTypeParams(capturedSFun, sfun)) {
1!
UNCOV
242
            return false;
×
243
        }
244

245

246
        List<JTypeVar> sparams = sfun.getTypeParameters();
1✔
247
        List<JTypeVar> tparams = tfun.getTypeParameters();
1✔
248
        Substitution tToS = Substitution.mapping(tparams, sparams);
1✔
249
        for (int j = 0; j < sparams.size(); j++) {
1!
UNCOV
250
            JTypeVar aj = sparams.get(j);
×
251
            JTypeVar bj = tparams.get(j);
×
252

UNCOV
253
            JTypeMirror x = aj.getUpperBound();
×
254
            JTypeMirror y = bj.getUpperBound();
×
255
            if (TypeOps.mentionsAny(x, sfun.getTypeParameters()) && !ctx.isGround(y)) {
×
256
                return false;
×
257
            } else {
UNCOV
258
                InternalApiBridge.isSameTypeInInference(x, y.subst(tToS)); // adds an equality constraint
×
259
            }
260
        }
261

262
        // todo something about formal params
263

264
        JTypeMirror rs = sfun.getReturnType();
1✔
265
        JTypeMirror rt = tfun.getReturnType();
1✔
266
        return (!TypeOps.mentionsAny(rs, sparams) || ctx.isGround(rt))
1!
267
            && addGenericExprConstraintsRecursive(ctx, ei, rs, rt, tToS, site);
1✔
268
    }
269

270
    private boolean addGenericExprConstraintsRecursive(InferenceContext ctx, ExprMirror ei, JTypeMirror rs, JTypeMirror rt, Substitution tToS, MethodCallSite site) {
271
        if (ei instanceof LambdaExprMirror) {
1✔
272
            // Otherwise, if ei is an explicitly typed lambda expression:
273
            //
274
            //    If RT is void, true.
275
            //
276
            //    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.
277
            //
278
            //    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.
279
            //
280
            //    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.
281
            //
282
            //    Otherwise, ‹RS <: RT θ'›.
283

284
            LambdaExprMirror lambda = (LambdaExprMirror) ei;
1✔
285
            if (!lambda.isExplicitlyTyped()) {
1✔
286
                return false;
1✔
287
            }
288

289
            if (getSpecies(rt) != getSpecies(rs)) {
1!
290
                TypeSpecies requiredSpecies = getSpecies(rs);
1✔
291
                boolean sameSpecies = true;
1✔
292
                boolean atLeastOne = false;
1✔
293
                for (ExprMirror rexpr : lambda.getResultExpressions()) {
1✔
294
                    atLeastOne = true;
1✔
295
                    sameSpecies &= requiredSpecies == rexpr.getStandaloneSpecies();
1✔
296
                }
1✔
297

298
                if (sameSpecies && atLeastOne) {
1!
299
                    return true;
1✔
300
                }
301
            }
302

UNCOV
303
            infer.checkConvertibleOrDefer(ctx, rs, rt.subst(tToS), ei, phase, site);
×
304
            return true;
×
305
        } else if (ei instanceof MethodRefMirror) {
1✔
306
            //  Otherwise, if ei is an exact method reference:
307
            //
308
            //    If RT is void, true.
309
            //
310
            //    Otherwise, if RS is a primitive type and RT is not, and the compile-time declaration for ei has a primitive return type, true.
311
            //
312
            //    Otherwise if RT is a primitive type and RS is not, and the compile-time declaration for ei has a reference return type, true.
313
            //
314
            //    Otherwise, ‹RS <: RT θ'›.
315

316
            JMethodSig exact = ExprOps.getExactMethod((MethodRefMirror) ei);
1✔
317
            if (exact == null) {
1✔
318
                return false;
1✔
319
            }
320

321
            if (getSpecies(rs) != getSpecies(rt)
1!
322
                && getSpecies(exact.getReturnType()) == getSpecies(rs)) {
1✔
323
                return true;
1✔
324
            }
325

UNCOV
326
            infer.checkConvertibleOrDefer(ctx, rs, rt.subst(tToS), ei, phase, site);
×
327
            return true;
×
328
        } else if (ei instanceof BranchingMirror) {
1!
UNCOV
329
            return ((BranchingMirror) ei).branchesMatch(e -> addGenericExprConstraintsRecursive(ctx, e, rs, rt, tToS, site));
×
330
        }
331

332
        return false;
1✔
333
    }
334
}
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