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

pmd / pmd / 277

27 Nov 2025 01:37PM UTC coverage: 78.778% (+0.03%) from 78.749%
277

push

github

adangel
[java] UseArraysAsList: skip when if-statements (#6228)

18419 of 24233 branches covered (76.01%)

Branch coverage included in aggregate %.

40090 of 50038 relevant lines covered (80.12%)

0.81 hits per line

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

95.07
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/table/internal/JavaResolvers.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.symbols.table.internal;
6

7
import static net.sourceforge.pmd.lang.java.symbols.table.internal.SuperTypesEnumerator.DIRECT_STRICT_SUPERTYPES;
8
import static net.sourceforge.pmd.lang.java.symbols.table.internal.SuperTypesEnumerator.JUST_SELF;
9
import static net.sourceforge.pmd.util.CollectionUtil.listOfNotNull;
10

11
import java.lang.reflect.Modifier;
12
import java.util.ArrayList;
13
import java.util.BitSet;
14
import java.util.Collections;
15
import java.util.List;
16
import java.util.Set;
17
import java.util.function.BiFunction;
18
import java.util.function.BinaryOperator;
19
import java.util.function.Function;
20
import java.util.function.Predicate;
21

22
import org.apache.commons.lang3.tuple.Pair;
23
import org.checkerframework.checker.nullness.qual.NonNull;
24
import org.checkerframework.checker.nullness.qual.Nullable;
25
import org.pcollections.HashTreePSet;
26
import org.pcollections.PSet;
27

28
import net.sourceforge.pmd.lang.java.symbols.JAccessibleElementSymbol;
29
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
30
import net.sourceforge.pmd.lang.java.symbols.JElementSymbol;
31
import net.sourceforge.pmd.lang.java.symbols.JFieldSymbol;
32
import net.sourceforge.pmd.lang.java.symbols.JMethodSymbol;
33
import net.sourceforge.pmd.lang.java.symbols.JModuleSymbol;
34
import net.sourceforge.pmd.lang.java.symbols.SymbolResolver;
35
import net.sourceforge.pmd.lang.java.symbols.table.coreimpl.CoreResolvers;
36
import net.sourceforge.pmd.lang.java.symbols.table.coreimpl.NameResolver;
37
import net.sourceforge.pmd.lang.java.symbols.table.coreimpl.NameResolver.SingleNameResolver;
38
import net.sourceforge.pmd.lang.java.symbols.table.coreimpl.ShadowChainBuilder;
39
import net.sourceforge.pmd.lang.java.types.JClassType;
40
import net.sourceforge.pmd.lang.java.types.JMethodSig;
41
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
42
import net.sourceforge.pmd.lang.java.types.JVariableSig;
43
import net.sourceforge.pmd.lang.java.types.JVariableSig.FieldSig;
44
import net.sourceforge.pmd.lang.java.types.TypeOps;
45
import net.sourceforge.pmd.lang.java.types.internal.infer.OverloadSet;
46
import net.sourceforge.pmd.util.AssertionUtil;
47
import net.sourceforge.pmd.util.CollectionUtil;
48

49
public final class JavaResolvers {
50

51
    private JavaResolvers() {
52
        // utility class
53
    }
54

55
    /** Prepend the package name, handling empty package. */
56
    static String prependPackageName(String pack, String name) {
57
        return pack.isEmpty() ? name : pack + "." + name;
1✔
58
    }
59

60
    /**
61
     * Returns true if the given element can be imported in the current file
62
     * (it's visible & accessible). This is not a general purpose accessibility
63
     * check and is only appropriate for imports.
64
     *
65
     *
66
     * <p>We consider protected members inaccessible outside of the package they were declared in,
67
     * which is an approximation but won't cause problems in practice.
68
     * In an ACU in another package, the name is accessible only inside classes that inherit
69
     * from the declaring class. But inheriting from a class makes its static members
70
     * accessible via simple name too. So this will actually be picked up by some other symbol table
71
     * when in the subclass. Usages outside of the subclass would have made the compilation fail.
72
     */
73
    static boolean canBeImportedIn(String thisPackage, JAccessibleElementSymbol member) {
74
        return isAccessibleIn(null, thisPackage, member, false);
1✔
75
    }
76

77
    static @NonNull NameResolver<JTypeMirror> moduleImport(Set<String> moduleNames,
78
                                                  final SymbolResolver symbolResolver,
79
                                                  final String thisPackage) {
80
        return new SingleNameResolver<JTypeMirror>() {
1✔
81
            @Override
82
            public @Nullable JTypeMirror resolveFirst(String simpleName) {
83
                for (String module : moduleNames) {
1✔
84
                    JModuleSymbol moduleSymbol = symbolResolver.resolveModule(module);
1✔
85

86
                    if (moduleSymbol == null) {
1✔
87
                        // TODO: log warning about incomplete auxclasspath
88
                        return null;
1✔
89
                    }
90

91
                    for (String packageName : moduleSymbol.getExportedPackages()) {
1✔
92
                        JClassSymbol sym = symbolResolver.resolveClassFromCanonicalName(packageName + "." + simpleName);
1✔
93
                        if (sym != null && canBeImportedIn(thisPackage, sym)) {
1!
94
                            return sym.getTypeSystem().typeOf(sym, false);
1✔
95
                        }
96
                    }
1✔
97
                }
1✔
98

99
                return null;
1✔
100
            }
101

102
            @Override
103
            public String toString() {
104
                return "ModuleImportResolver(" + moduleNames + ")";
×
105
            }
106
        };
107
    }
108

109
    static @NonNull NameResolver<JTypeMirror> importedOnDemand(Set<String> lazyImportedPackagesAndTypes,
110
                                                      final SymbolResolver symResolver,
111
                                                      final String thisPackage) {
112
        return new SingleNameResolver<JTypeMirror>() {
1✔
113
            @Nullable
114
            @Override
115
            public JTypeMirror resolveFirst(String simpleName) {
116
                for (String pack : lazyImportedPackagesAndTypes) {
1✔
117
                    // here 'pack' may be a package or a type name, so we must resolve by canonical name
118
                    String name = prependPackageName(pack, simpleName);
1✔
119
                    JClassSymbol sym = symResolver.resolveClassFromCanonicalName(name);
1✔
120
                    if (sym != null && canBeImportedIn(thisPackage, sym)) {
1!
121
                        return sym.getTypeSystem().typeOf(sym, false);
1✔
122
                    }
123
                }
1✔
124
                return null;
1✔
125
            }
126

127
            @Override
128
            public String toString() {
129
                return "ImportOnDemandResolver(" + lazyImportedPackagesAndTypes + ")";
×
130
            }
131
        };
132
    }
133

134
    static @NonNull NameResolver<JTypeMirror> packageResolver(SymbolResolver symResolver, String packageName) {
135
        return new SingleNameResolver<JTypeMirror>() {
1✔
136
            @Nullable
137
            @Override
138
            public JTypeMirror resolveFirst(String simpleName) {
139
                JClassSymbol sym = symResolver.resolveClassFromBinaryName(prependPackageName(packageName, simpleName));
1✔
140
                if (sym != null) {
1✔
141
                    return sym.getTypeSystem().typeOf(sym, false);
1✔
142
                }
143
                return null;
1✔
144
            }
145

146
            @Override
147
            public String toString() {
148
                return "PackageResolver(" + packageName + ")";
×
149
            }
150
        };
151
    }
152

153
    /** All methods in a type, taking care of hiding/overriding. */
154
    static NameResolver<JMethodSig> subtypeMethodResolver(JClassType t) {
155
        JClassSymbol nestRoot = t.getSymbol().getNestRoot();
1✔
156
        return new NameResolver<JMethodSig>() {
1✔
157
            @Override
158
            public @NonNull List<JMethodSig> resolveHere(String simpleName) {
159
                return t.streamMethods(
1✔
160
                    it -> it.nameEquals(simpleName)
1✔
161
                        && isAccessibleIn(nestRoot, it, true) // fetch protected methods
1✔
162
                        && isNotStaticInterfaceMethod(it)
1✔
163
                ).collect(OverloadSet.collectMostSpecific(t)); // remove overridden, hidden methods
1✔
164
            }
165

166
            // Static interface methods are not inherited and are in fact not in scope in the subtypes.
167
            // They must be explicitly qualified or imported.
168
            private boolean isNotStaticInterfaceMethod(JMethodSymbol it) {
169
                return !it.isStatic() || it.getEnclosingClass().equals(t.getSymbol())
1✔
170
                    || !it.getEnclosingClass().isInterface();
1✔
171
            }
172

173
            @Override
174
            public String toString() {
175
                return "methods of " + t;
×
176
            }
177
        };
178
    }
179

180
    /** Static methods with a given name. */
181
    static NameResolver<JMethodSig> staticImportMethodResolver(JClassType container, @NonNull String accessPackageName, String importedSimpleName) {
182
        assert importedSimpleName != null;
1!
183
        assert accessPackageName != null;
1!
184
        return new NameResolver<JMethodSig>() {
1✔
185
            @Override
186
            public @NonNull List<JMethodSig> resolveHere(String simpleName) {
187
                if (!simpleName.equals(importedSimpleName)) {
1✔
188
                    return Collections.emptyList();
1✔
189
                }
190
                return container.streamMethods(
1✔
191
                    it -> Modifier.isStatic(it.getModifiers())
1✔
192
                        && it.nameEquals(simpleName)
1✔
193
                        // Technically, importing a static protected method may be valid
194
                        // inside some of the classes in the compilation unit. This test
195
                        // makes it not in scope in those classes. But it's also visible
196
                        // from the subclass as an "inherited" member, so is in scope in
197
                        // the relevant contexts.
198
                        && canBeImportedIn(accessPackageName, it)
1✔
199
                ).collect(OverloadSet.collectMostSpecific(container)); // remove overridden, hidden methods
1✔
200
            }
201

202
            @Override
203
            public String toString() {
204
                return "static methods w/ name " + importedSimpleName + " of " + container;
×
205
            }
206
        };
207
    }
208

209
    /** Static fields with a given name. */
210
    static NameResolver<FieldSig> staticImportFieldResolver(JClassType containerType, @NonNull String accessPackageName, String importedSimpleName) {
211
        return new NameResolver<FieldSig>() {
1✔
212
            List<FieldSig> result;
213

214
            @Override
215
            public @NonNull List<FieldSig> resolveHere(String simpleName) {
216
                if (!simpleName.equals(importedSimpleName)) {
1✔
217
                    return Collections.emptyList();
1✔
218
                }
219
                if (result == null) {
1✔
220
                    result = JavaResolvers.getMemberFieldResolver(containerType, accessPackageName, null, simpleName)
1✔
221
                                          .resolveHere(simpleName);
1✔
222
                }
223
                return result;
1✔
224
            }
225

226
            @Override
227
            public String toString() {
228
                return "static methods w/ name " + importedSimpleName + " of " + containerType;
×
229
            }
230
        };
231
    }
232

233
    /** Static classes with a given name. */
234
    static NameResolver<JClassType> staticImportClassResolver(JClassType containerType, @NonNull String accessPackageName, String importedSimpleName) {
235
        return new NameResolver<JClassType>() {
1✔
236
            List<JClassType> result;
237

238
            @Override
239
            public @NonNull List<JClassType> resolveHere(String simpleName) {
240
                if (!simpleName.equals(importedSimpleName)) {
1✔
241
                    return Collections.emptyList();
1✔
242
                }
243

244
                if (result == null) {
1✔
245
                    result = JavaResolvers.getMemberClassResolver(containerType, accessPackageName, null, simpleName)
1✔
246
                                          .resolveHere(simpleName);
1✔
247
                }
248
                return result;
1✔
249
            }
250

251
            @Override
252
            public String toString() {
253
                return "static classes w/ name " + importedSimpleName + " of " + containerType;
×
254
            }
255
        };
256
    }
257

258
    static NameResolver<JMethodSig> staticImportOnDemandMethodResolver(JClassType container, @NonNull String accessPackageName) {
259
        assert accessPackageName != null;
1!
260
        return new NameResolver<JMethodSig>() {
1✔
261
            @Override
262
            public @NonNull List<JMethodSig> resolveHere(String simpleName) {
263
                return container.streamMethods(
1✔
264
                    it -> Modifier.isStatic(it.getModifiers())
1✔
265
                        && it.nameEquals(simpleName)
1✔
266
                        && canBeImportedIn(accessPackageName, it)
1✔
267
                ).collect(OverloadSet.collectMostSpecific(container)); // remove overridden, hidden methods
1✔
268
            }
269

270
            @Override
271
            public String toString() {
272
                return "all static methods of " + container;
×
273
            }
274
        };
275
    }
276

277
    private static final BinaryOperator<List<JMethodSig>> STATIC_MERGER =
1✔
278
        (as, bs) -> methodMerger(true, as, bs);
1✔
279

280
    private static final BinaryOperator<List<JMethodSig>> NON_STATIC_MERGER =
1✔
281
        (as, bs) -> methodMerger(false, as, bs);
1✔
282

283

284
    static BinaryOperator<List<JMethodSig>> methodMerger(boolean inStaticType) {
285
        return inStaticType ? STATIC_MERGER : NON_STATIC_MERGER;
1✔
286
    }
287

288
    /**
289
     * Merges two method scopes, the otherResult is the one of an enclosing class,
290
     * the inner result is the one inherited from supertypes (which take precedence
291
     * in case of override equivalence).
292
     *
293
     * <p>Non-static methods of the outer result are excluded if the inner scope is static.
294
     */
295
    private static List<JMethodSig> methodMerger(boolean inStaticType, List<JMethodSig> myResult, List<JMethodSig> otherResult) {
296
        if (otherResult.isEmpty()) {
1✔
297
            return myResult;
1✔
298
        } // don't check myResult for emptiness, we might need to remove static methods
299

300
        // For both the input lists, their elements are pairwise non-equivalent.
301
        // If any element of myResult is override-equivalent to
302
        // another in otherResult, then we must exclude the otherResult
303

304
        BitSet isShadowed = new BitSet(otherResult.size());
1✔
305

306
        for (JMethodSig m1 : myResult) {
1✔
307
            int i = 0;
1✔
308
            for (JMethodSig m2 : otherResult) {
1✔
309
                boolean isAlreadyShadowed = isShadowed.get(i);
1✔
310
                if (!isAlreadyShadowed && TypeOps.areOverrideEquivalent(m1, m2)
1✔
311
                    || inStaticType && !m2.isStatic()) {
1✔
312
                    isShadowed.set(i); // we'll remove it later
1✔
313
                }
314
                i++;
1✔
315
            }
1✔
316
        }
1✔
317

318
        if (isShadowed.isEmpty()) {
1✔
319
            return CollectionUtil.concatView(myResult, otherResult);
1✔
320
        } else {
321
            List<JMethodSig> result = new ArrayList<>(myResult.size() + otherResult.size() - 1);
1✔
322
            result.addAll(myResult);
1✔
323
            copyIntoWithMask(otherResult, isShadowed, result);
1✔
324
            return Collections.unmodifiableList(result);
1✔
325
        }
326
    }
327

328
    /**
329
     * Copy the elements of the input list into the result list, excluding
330
     * all elements marked by the bitset.
331
     */
332
    private static <T> void copyIntoWithMask(List<? extends T> input, BitSet denyList, List<? super T> result) {
333
        int last = 0;
1✔
334
        for (int i = denyList.nextSetBit(0); i >= 0; i = denyList.nextSetBit(i + 1)) {
1✔
335
            if (last != i) {
1✔
336
                result.addAll(input.subList(last, i));
1✔
337
            }
338
            last = i + 1;
1✔
339
        }
340
        if (last != input.size()) {
1✔
341
            result.addAll(input.subList(last, input.size()));
1✔
342
        }
343
    }
1✔
344

345

346
    /**
347
     * Resolvers for inherited member types and fields. We can't process
348
     * methods that way, because there may be duplicates and the equals
349
     * of {@link JMethodSymbol} is not reliable for now (cannot differentiate
350
     * overloads). But also, usually a subset of methods is used in a subclass,
351
     * and it's ok performance-wise to process them on-demand.
352
     */
353
    static Pair<NameResolver<JTypeMirror>, NameResolver<JVariableSig>> inheritedMembersResolvers(JClassType t) {
354
        Pair<ShadowChainBuilder<JTypeMirror, ?>.ResolverBuilder, ShadowChainBuilder<JVariableSig, ?>.ResolverBuilder> builders =
1✔
355
            hidingWalkResolvers(t, t, t.getSymbol().getPackageName(), true, /* onlyStatic: */false, DIRECT_STRICT_SUPERTYPES);
1✔
356
        return Pair.of(builders.getLeft().build(), builders.getRight().build());
1✔
357
    }
358

359
    static Pair<ShadowChainBuilder<JTypeMirror, ?>.ResolverBuilder,
360
        ShadowChainBuilder<JVariableSig, ?>.ResolverBuilder> importOnDemandMembersResolvers(JClassType t, @NonNull String accessPackageName) {
361
        return hidingWalkResolvers(t, null, accessPackageName, false, /* onlyStatic: */ true, JUST_SELF /* include self members */);
1✔
362
    }
363

364
    private static Pair<ShadowChainBuilder<JTypeMirror, ?>.ResolverBuilder, ShadowChainBuilder<JVariableSig, ?>.ResolverBuilder> hidingWalkResolvers(JClassType t,
365
                                                                                                                                                     @Nullable JClassType accessType,
366
                                                                                                                                                     @NonNull String accessPackageName,
367
                                                                                                                                                     boolean accessIsSubtypeOfOwner,
368
                                                                                                                                                     boolean onlyStatic,
369
                                                                                                                                                     SuperTypesEnumerator enumerator) {
370
        JClassSymbol nestRoot = accessType == null ? null : accessType.getSymbol().getNestRoot();
1✔
371

372
        ShadowChainBuilder<JVariableSig, ?>.ResolverBuilder fields = SymTableFactory.VARS.new ResolverBuilder();
1✔
373
        ShadowChainBuilder<JTypeMirror, ?>.ResolverBuilder types = SymTableFactory.TYPES.new ResolverBuilder();
1✔
374

375
        Predicate<JVariableSig> isFieldAccessible =
1✔
376
            s -> filterStatic(onlyStatic, s.getSymbol())
1✔
377
                && isAccessibleIn(nestRoot, accessPackageName, (JFieldSymbol) s.getSymbol(), accessIsSubtypeOfOwner);
1✔
378
        Predicate<JClassType> isTypeAccessible =
1✔
379
            s -> filterStatic(onlyStatic, s.getSymbol())
1✔
380
                && isAccessibleIn(nestRoot, accessPackageName, s.getSymbol(), accessIsSubtypeOfOwner);
1✔
381

382
        for (JClassType next : enumerator.iterable(t)) {
1✔
383
            walkSelf(next, isFieldAccessible, isTypeAccessible, fields, types, HashTreePSet.empty(), HashTreePSet.empty());
1✔
384
        }
1✔
385

386
        return Pair.of(types, fields);
1✔
387
    }
388

389
    private static boolean filterStatic(boolean onlyStatic, JElementSymbol symbol) {
390
        return !onlyStatic || Modifier.isStatic(((JAccessibleElementSymbol) symbol).getModifiers());
1✔
391
    }
392

393
    private static void walkSelf(JClassType t,
394
                                 Predicate<? super JVariableSig> isFieldAccessible,
395
                                 Predicate<? super JClassType> isTypeAccessible,
396
                                 ShadowChainBuilder<JVariableSig, ?>.ResolverBuilder fields,
397
                                 ShadowChainBuilder<JTypeMirror, ?>.ResolverBuilder types,
398
                                 // persistent because may change in every path of the recursion
399
                                 final PSet<String> hiddenFields,
400
                                 final PSet<String> hiddenTypes) {
401

402
        // Note that it is possible that this process recurses several
403
        // times into the same interface (if it is reachable from several paths)
404
        // This is because the set of hidden declarations depends on the
405
        // full path, and may be different each time.
406
        // Profiling shows that this doesn't occur very often, and adding
407
        // a recursion guard is counter-productive performance-wise
408

409
        PSet<String> hiddenTypesInSup = processDeclarations(types, hiddenTypes, isTypeAccessible, t.getDeclaredClasses());
1✔
410
        PSet<String> hiddenFieldsInSup = processDeclarations(fields, hiddenFields, isFieldAccessible, t.getDeclaredFields());
1✔
411

412
        // depth first
413
        for (JClassType next : DIRECT_STRICT_SUPERTYPES.iterable(t)) {
1✔
414
            walkSelf(next, isFieldAccessible, isTypeAccessible, fields, types, hiddenFieldsInSup, hiddenTypesInSup);
1✔
415
        }
1✔
416
    }
1✔
417

418
    private static <S> PSet<String> processDeclarations(
419
        ShadowChainBuilder<? super S, ?>.ResolverBuilder builder,
420
        PSet<String> hidden,
421
        Predicate<? super S> isAccessible,
422
        List<? extends S> syms
423
    ) {
424
        for (S inner : syms) {
1✔
425
            String simpleName = builder.getSimpleName(inner);
1✔
426
            if (hidden.contains(simpleName)) {
1✔
427
                continue;
1✔
428
            }
429

430
            hidden = hidden.plus(simpleName);
1✔
431

432
            if (isAccessible.test(inner)) {
1✔
433
                builder.appendWithoutDuplicate(inner);
1✔
434
            }
435
        }
1✔
436
        return hidden;
1✔
437
    }
438

439
    /**
440
     * A general-purpose accessibility check, which can be used if you
441
     * know in advance whether the context is a supertype of the class
442
     * (ie, whether the sym is accessible if "protected").
443
     *
444
     * @param nestRoot Root enclosing type for the context of the reference
445
     * @param sym      Symbol to test
446
     */
447
    public static boolean isAccessibleIn(@NonNull JClassSymbol nestRoot,
448
                                         JAccessibleElementSymbol sym,
449
                                         boolean isOwnerASupertypeOfContext) {
450
        return isAccessibleIn(nestRoot, nestRoot.getPackageName(), sym, isOwnerASupertypeOfContext);
1✔
451
    }
452

453
    /**
454
     * Whether the given sym is accessible in some type T, given
455
     * the 'nestRoot' of T, and whether T is a subtype of the class
456
     * declaring 'sym'. This is a general purpose accessibility check,
457
     * albeit a bit low-level (but only needs subtyping to be computed once).
458
     */
459
    private static boolean isAccessibleIn(@Nullable JClassSymbol nestRoot,
460
                                          @NonNull String packageName,
461
                                          JAccessibleElementSymbol sym,
462
                                          boolean isOwnerASupertypeOfContext) {
463
        int modifiers = sym.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE);
1✔
464

465
        switch (modifiers) {
1!
466
        case Modifier.PUBLIC:
467
            return true;
1✔
468
        case Modifier.PRIVATE:
469
            return nestRoot != null && nestRoot.equals(sym.getEnclosingClass().getNestRoot());
1✔
470
        case Modifier.PROTECTED:
471
            if (isOwnerASupertypeOfContext) {
1✔
472
                return true;
1✔
473
            }
474
            // fallthrough
475
        case 0:
476
            return sym.getPackageName().equals(packageName);
1✔
477
        default:
478
            // TODO this is reachable for invalid declarations, like a private field of an interface
479
            throw AssertionUtil.shouldNotReachHere("private field of an interface? " + sym + ", modifiers: " + Modifier.toString(sym.getModifiers()));
×
480
        }
481
    }
482

483

484
    /**
485
     * Produce a name resolver that resolves member classes with the
486
     * given name declared or inherited by the given type. Each access
487
     * may perform a hierarchy traversal, but this handles hidden and
488
     * ambiguous declarations nicely.
489
     *
490
     * @param c      Class to search
491
     * @param access Context of where the declaration is referenced
492
     * @param name   Name of the class to find
493
     */
494
    public static NameResolver<JClassType> getMemberClassResolver(JClassType c, @NonNull String accessPackageName, @Nullable JClassSymbol access, String name) {
495
        return getNamedMemberResolver(c, access, accessPackageName, JClassType::getDeclaredClass, name, JClassType::getSymbol, SymTableFactory.TYPES);
1✔
496
    }
497

498
    public static NameResolver<FieldSig> getMemberFieldResolver(JClassType c, @NonNull String accessPackageName, @Nullable JClassSymbol access, String name) {
499
        return getNamedMemberResolver(c, access, accessPackageName, JClassType::getDeclaredField, name, FieldSig::getSymbol, SymTableFactory.VARS);
1✔
500
    }
501

502
    private static <S> NameResolver<S> getNamedMemberResolver(JClassType c,
503
                                                              @Nullable JClassSymbol access,
504
                                                              @NonNull String accessPackageName,
505
                                                              BiFunction<? super JClassType, String, ? extends S> getter,
506
                                                              String name,
507
                                                              Function<? super S, ? extends JAccessibleElementSymbol> symbolGetter,
508
                                                              ShadowChainBuilder<? super S, ?> classes) {
509
        S found = getter.apply(c, name);
1✔
510
        if (found != null) {
1✔
511
            // fast path, doesn't need to check accessibility, etc
512
            return CoreResolvers.singleton(name, found);
1✔
513
        }
514

515
        JClassSymbol nestRoot = access == null ? null : access.getNestRoot();
1✔
516
        Predicate<S> isAccessible = s -> {
1✔
517
            JAccessibleElementSymbol sym = symbolGetter.apply(s);
1✔
518
            return isAccessibleIn(nestRoot, accessPackageName, sym, isSubtype(access, sym.getEnclosingClass()));
1✔
519
        };
520

521
        @SuppressWarnings("unchecked")
522
        ShadowChainBuilder<S, ?>.ResolverBuilder builder = (ShadowChainBuilder<S, ?>.ResolverBuilder) classes.new ResolverBuilder();
1✔
523

524
        for (JClassType next : DIRECT_STRICT_SUPERTYPES.iterable(c)) {
1✔
525
            walkForSingleName(next, isAccessible, name, getter, builder, HashTreePSet.empty());
1✔
526
        }
1✔
527

528
        return builder.build();
1✔
529
    }
530

531
    private static boolean isSubtype(JClassSymbol sub, JClassSymbol sup) {
532
        return sub != null && sub.getTypeSystem().typeOf(sub, true).getAsSuper(sup) != null;
1✔
533
    }
534

535
    private static <S> void walkForSingleName(JClassType t,
536
                                              Predicate<? super S> isAccessible,
537
                                              String name,
538
                                              BiFunction<? super JClassType, String, ? extends S> getter,
539
                                              ShadowChainBuilder<? super S, ?>.ResolverBuilder builder,
540
                                              final PSet<String> hidden) {
541

542
        PSet<String> hiddenInSup = processDeclarations(builder, hidden, isAccessible, listOfNotNull(getter.apply(t, name)));
1✔
543

544
        if (!hiddenInSup.isEmpty()) {
1✔
545
            // found it in this branch
546
            // in this method the hidden set is either empty or one element only
547
            return;
1✔
548
        }
549

550
        // depth first
551
        for (JClassType next : DIRECT_STRICT_SUPERTYPES.iterable(t)) {
1✔
552
            walkForSingleName(next, isAccessible, name, getter, builder, hiddenInSup);
1✔
553
        }
1✔
554
    }
1✔
555

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