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

pmd / pmd / 5

15 May 2025 01:19PM UTC coverage: 77.761% (+0.006%) from 77.755%
5

push

github

adangel
Fix #5724: [java] Implicit functional interface FP with sealed interface (#5726)

Merge pull request #5726 from oowekyala:issue5724-implicit-fun-interface

17657 of 23664 branches covered (74.62%)

Branch coverage included in aggregate %.

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

24 existing lines in 4 files now uncovered.

38678 of 48782 relevant lines covered (79.29%)

0.8 hits per line

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

95.28
/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symbols/internal/ast/AstClassSym.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.internal.ast;
6

7
import java.util.ArrayList;
8
import java.util.Collections;
9
import java.util.List;
10
import java.util.Objects;
11

12
import org.checkerframework.checker.nullness.qual.NonNull;
13
import org.checkerframework.checker.nullness.qual.Nullable;
14
import org.pcollections.HashTreePSet;
15
import org.pcollections.PSet;
16

17
import net.sourceforge.pmd.lang.ast.NodeStream;
18
import net.sourceforge.pmd.lang.java.ast.ASTBodyDeclaration;
19
import net.sourceforge.pmd.lang.java.ast.ASTClassDeclaration;
20
import net.sourceforge.pmd.lang.java.ast.ASTClassType;
21
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
22
import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
23
import net.sourceforge.pmd.lang.java.ast.ASTEnumConstant;
24
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
25
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
26
import net.sourceforge.pmd.lang.java.ast.ASTPermitsList;
27
import net.sourceforge.pmd.lang.java.ast.ASTRecordComponent;
28
import net.sourceforge.pmd.lang.java.ast.ASTRecordComponentList;
29
import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration;
30
import net.sourceforge.pmd.lang.java.ast.ASTVariableId;
31
import net.sourceforge.pmd.lang.java.ast.InternalApiBridge;
32
import net.sourceforge.pmd.lang.java.ast.JModifier;
33
import net.sourceforge.pmd.lang.java.internal.JavaAstProcessor;
34
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
35
import net.sourceforge.pmd.lang.java.symbols.JConstructorSymbol;
36
import net.sourceforge.pmd.lang.java.symbols.JElementSymbol;
37
import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol;
38
import net.sourceforge.pmd.lang.java.symbols.JFieldSymbol;
39
import net.sourceforge.pmd.lang.java.symbols.JMethodSymbol;
40
import net.sourceforge.pmd.lang.java.symbols.JRecordComponentSymbol;
41
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
42
import net.sourceforge.pmd.lang.java.symbols.JTypeParameterOwnerSymbol;
43
import net.sourceforge.pmd.lang.java.symbols.internal.ImplicitMemberSymbols;
44
import net.sourceforge.pmd.lang.java.types.JClassType;
45
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
46
import net.sourceforge.pmd.lang.java.types.Substitution;
47
import net.sourceforge.pmd.lang.java.types.TypeOps;
48
import net.sourceforge.pmd.lang.java.types.TypeSystem;
49
import net.sourceforge.pmd.util.CollectionUtil;
50

51

52
final class AstClassSym
1✔
53
    extends AbstractAstTParamOwner<ASTTypeDeclaration>
54
    implements JClassSymbol {
55

56
    private final @Nullable JTypeParameterOwnerSymbol enclosing;
57
    private final List<JClassSymbol> declaredClasses;
58
    private final List<JMethodSymbol> declaredMethods;
59
    private final List<JConstructorSymbol> declaredCtors;
60
    private final List<JFieldSymbol> declaredFields;
61
    private final List<JFieldSymbol> enumConstants; // subset of declaredFields
62
    private final List<JRecordComponentSymbol> recordComponents;
63
    private final PSet<String> annotAttributes;
64

65
    private List<JClassSymbol> permittedSubclasses;
66

67
    AstClassSym(ASTTypeDeclaration node,
68
                AstSymFactory factory,
69
                @Nullable JTypeParameterOwnerSymbol enclosing) {
70
        super(node, factory);
1✔
71
        this.enclosing = enclosing;
1✔
72

73
        // evaluate everything strictly
74
        // this populates symbols on the relevant AST nodes
75

76
        final List<JClassSymbol> myClasses = new ArrayList<>();
1✔
77
        final List<JMethodSymbol> myMethods = new ArrayList<>();
1✔
78
        final List<JConstructorSymbol> myCtors = new ArrayList<>();
1✔
79
        final List<JFieldSymbol> myFields = new ArrayList<>();
1✔
80
        final List<JFieldSymbol> enumConstants;
81
        final List<JRecordComponentSymbol> recordComponents;
82

83
        if (isRecord()) {
1✔
84
            ASTRecordComponentList components = Objects.requireNonNull(node.getRecordComponents(),
1✔
85
                                                                       "Null component list for " + node);
86
            recordComponents = mapComponentsToMutableList(factory, components, myFields);
1✔
87

88
            JConstructorSymbol canonicalRecordCtor = ImplicitMemberSymbols.recordConstructor(this, recordComponents, components.isVarargs());
1✔
89
            myCtors.add(canonicalRecordCtor);
1✔
90
            InternalApiBridge.setSymbol(components, canonicalRecordCtor);
1✔
91

92
        } else {
1✔
93
            recordComponents = Collections.emptyList();
1✔
94
        }
95

96
        if (isEnum()) {
1✔
97
            enumConstants = new ArrayList<>();
1✔
98
            node.getEnumConstants()
1✔
99
                .forEach(constant -> {
1✔
100
                    AstFieldSym fieldSym = new AstFieldSym(constant.getVarId(), factory, this);
1✔
101
                    enumConstants.add(fieldSym);
1✔
102
                    myFields.add(fieldSym);
1✔
103
                });
1✔
104
        } else {
105
            enumConstants = null;
1✔
106
        }
107

108
        for (ASTBodyDeclaration dnode : node.getDeclarations()) {
1✔
109

110
            if (dnode instanceof ASTTypeDeclaration) {
1✔
111
                myClasses.add(new AstClassSym((ASTTypeDeclaration) dnode, factory, this));
1✔
112
            } else if (dnode instanceof ASTMethodDeclaration) {
1✔
113
                if (!recordComponents.isEmpty() && ((ASTMethodDeclaration) dnode).getArity() == 0) {
1✔
114
                    // filter out record component, so that the accessor is not generated
115
                    recordComponents.removeIf(f -> f.nameEquals(((ASTMethodDeclaration) dnode).getName()));
1✔
116
                }
117
                myMethods.add(new AstMethodSym((ASTMethodDeclaration) dnode, factory, this));
1✔
118
            } else if (dnode instanceof ASTConstructorDeclaration) {
1✔
119
                myCtors.add(new AstCtorSym((ASTConstructorDeclaration) dnode, factory, this));
1✔
120
            } else if (dnode instanceof ASTFieldDeclaration) {
1✔
121
                for (ASTVariableId varId : ((ASTFieldDeclaration) dnode).getVarIds()) {
1✔
122
                    myFields.add(new AstFieldSym(varId, factory, this));
1✔
123
                }
1✔
124
            }
125
        }
1✔
126
        
127

128
        if (!recordComponents.isEmpty()) {
1✔
129
            // then the recordsComponents contains all record components
130
            // for which we must synthesize an accessor (explicitly declared
131
            // accessors have been filtered out)
132
            for (JRecordComponentSymbol component : recordComponents) {
1✔
133
                myMethods.add(ImplicitMemberSymbols.recordAccessor(this, component));
1✔
134
            }
1✔
135
        }
136

137
        if (myCtors.isEmpty() && isClass() && !isAnonymousClass()) {
1✔
138
            myCtors.add(ImplicitMemberSymbols.defaultCtor(this));
1✔
139
        }
140

141
        if (this.isEnum()) {
1✔
142
            myMethods.add(ImplicitMemberSymbols.enumValues(this));
1✔
143
            myMethods.add(ImplicitMemberSymbols.enumValueOf(this));
1✔
144
        }
145

146

147
        this.declaredClasses = myClasses;
1✔
148
        this.declaredMethods = myMethods;
1✔
149
        this.declaredCtors = myCtors;
1✔
150
        this.declaredFields = myFields;
1✔
151
        this.enumConstants = CollectionUtil.makeUnmodifiableAndNonNull(enumConstants);
1✔
152
        this.recordComponents = CollectionUtil.makeUnmodifiableAndNonNull(recordComponents);
1✔
153
        this.annotAttributes = isAnnotation()
1✔
154
                               ? getDeclaredMethods().stream().filter(JMethodSymbol::isAnnotationAttribute).map(JElementSymbol::getSimpleName).collect(CollectionUtil.toPersistentSet())
1✔
155
                               : HashTreePSet.empty();
1✔
156
    }
1✔
157

158
    public void processLombok(JavaAstProcessor processor) {
159
        if (node.isAnnotationPresent("lombok.extern.slf4j.Slf4j")) {
1✔
160
            declaredFields.add(ImplicitMemberSymbols.lombokSlf4jLoggerField(this, processor));
1✔
161
        }
162
    }
1✔
163

164

165
    private List<JRecordComponentSymbol> mapComponentsToMutableList(AstSymFactory factory,
166
                                                          ASTRecordComponentList components,
167
                                                          List<JFieldSymbol> fieldSyms) {
168
        List<JRecordComponentSymbol> list = new ArrayList<>();
1✔
169
        for (ASTRecordComponent comp : components) {
1✔
170
            list.add(new AstRecordComponentSym(comp, factory, this));
1✔
171
            fieldSyms.add(new AstFieldSym(comp.getVarId(), factory, this));
1✔
172
        }
1✔
173
        return list;
1✔
174
    }
175

176
    @Override
177
    public @NonNull String getSimpleName() {
178
        return node.getSimpleName();
1✔
179
    }
180

181

182
    @Override
183
    public @NonNull String getBinaryName() {
184
        return node.getBinaryName();
1✔
185
    }
186

187
    @Override
188
    public @Nullable String getCanonicalName() {
189
        return node.getCanonicalName();
1✔
190
    }
191

192
    @Override
193
    public boolean isUnresolved() {
194
        return false;
1✔
195
    }
196

197
    @Override
198
    public @Nullable JClassSymbol getEnclosingClass() {
199
        if (enclosing instanceof JClassSymbol) {
1✔
200
            return (JClassSymbol) enclosing;
1✔
201
        } else if (enclosing instanceof JExecutableSymbol) {
1✔
202
            return enclosing.getEnclosingClass();
1✔
203
        }
204
        assert enclosing == null;
1!
205
        return null;
1✔
206
    }
207

208
    @Override
209
    public @Nullable JExecutableSymbol getEnclosingMethod() {
210
        return enclosing instanceof JExecutableSymbol ? (JExecutableSymbol) enclosing : null;
1✔
211
    }
212

213
    @Override
214
    public List<JClassSymbol> getDeclaredClasses() {
215
        return Collections.unmodifiableList(declaredClasses);
1✔
216
    }
217

218
    @Override
219
    public List<JMethodSymbol> getDeclaredMethods() {
220
        return Collections.unmodifiableList(declaredMethods);
1✔
221
    }
222

223
    @Override
224
    public List<JConstructorSymbol> getConstructors() {
225
        return Collections.unmodifiableList(declaredCtors);
1✔
226
    }
227

228
    @Override
229
    public List<JFieldSymbol> getDeclaredFields() {
230
        return Collections.unmodifiableList(declaredFields);
1✔
231
    }
232

233
    @Override
234
    public @NonNull List<JFieldSymbol> getEnumConstants() {
235
        return enumConstants;
1✔
236
    }
237

238
    @Override
239
    public @NonNull List<JRecordComponentSymbol> getRecordComponents() {
240
        return recordComponents;
1✔
241
    }
242

243

244
    @Override
245
    public List<JClassSymbol> getPermittedSubtypes() {
246
        // permitted subclasses are populated lazily because they require
247
        // symbol and type resolution to determine which types are sealed.
248
        if (permittedSubclasses == null) {
1✔
249
            ASTPermitsList permits = node.getPermitsClause();
1✔
250
            if (permits != null) {
1✔
251
                this.permittedSubclasses = permits.toList().stream().map(it -> {
1✔
252
                    JTypeDeclSymbol symbol = it.getTypeMirror().getSymbol();
1✔
253
                    if (symbol instanceof JClassSymbol) {
1!
254
                        return (JClassSymbol) symbol;
1✔
255
                    } else {
UNCOV
256
                        return null;
×
257
                    }
258
                }).filter(Objects::nonNull).collect(CollectionUtil.toUnmodifiableList());
1✔
259
            } else if (isSealed()) {
1✔
260
                // sealed with no permits clause: infer permitted
261
                this.permittedSubclasses = inferPermittedSubclasses();
1✔
262
            } else {
263
                this.permittedSubclasses = Collections.emptyList();
1✔
264
            }
265
        }
266
        return permittedSubclasses;
1✔
267
    }
268

269
    private List<JClassSymbol> inferPermittedSubclasses() {
270
        /*
271
         *  If the declaration of a sealed class C lacks a permits clause,
272
         * then the permitted direct subclasses of C are as follows:
273
         *
274
         *  1. If C is not an enum class, then its permitted direct subclasses
275
         *     are those classes declared in the same compilation unit as C (§7.3)
276
         *     which have a canonical name (§6.7) and whose direct superclass is C.
277
         *
278
         *     That is, the permitted direct subclasses are inferred as the classes
279
         *     in the same compilation unit that specify C as their direct superclass.
280
         *     The requirement for a canonical name means that no local classes or
281
         *     anonymous classes will be considered.
282
         *
283
         *     It is a compile-time error if the declaration of a sealed class C lacks
284
         *     a permits clause and C has no permitted direct subclasses.
285
         *
286
         *  2. If C is an enum class, then its permitted direct subclasses, if any,
287
         *     are specified in §8.9.
288
         */
289
        if (!isEnum()) {
1!
290
            boolean isInterface = isInterface();
1✔
291
            List<JClassSymbol> list = node
1✔
292
                .getRoot().descendants(ASTTypeDeclaration.class).crossFindBoundaries()
1✔
293
                .filter(it -> it.getCanonicalName() != null)
1✔
294
                .filter(it -> {
1✔
295
                    if (isInterface) {
1✔
296
                        return it.getSuperInterfaceTypeNodes().any(ty -> Objects.equals(ty.getTypeMirror().getSymbol(), this));
1✔
297
                    }
298
                    return NodeStream.of(it.getSuperClassTypeNode()).any(ty -> Objects.equals(ty.getTypeMirror().getSymbol(), this));
1✔
299
                }).toList(ASTTypeDeclaration::getSymbol);
1✔
300
            return Collections.unmodifiableList(list);
1✔
301
        }
UNCOV
302
        return Collections.emptyList();
×
303
    }
304

305
    @Override
306
    public boolean isSealed() {
307
        return node.hasModifiers(JModifier.SEALED);
1✔
308
    }
309

310
    @Override
311
    public @Nullable JClassType getSuperclassType(Substitution substitution) {
312
        TypeSystem ts = getTypeSystem();
1✔
313

314
        if (node.isEnum()) {
1✔
315

316
            return factory.enumSuperclass(this);
1✔
317

318
        } else if (node instanceof ASTClassDeclaration) {
1✔
319

320
            ASTClassType superClass = node.getSuperClassTypeNode();
1✔
321
            return superClass == null
1✔
322
                   ? ts.OBJECT
1✔
323
                   // this cast relies on the fact that the superclass is not a type variable
324
                   : (JClassType) TypeOps.subst(superClass.getTypeMirror(), substitution);
1✔
325

326
        } else if (isAnonymousClass()) {
1✔
327

328
            if (node.getParent() instanceof ASTEnumConstant) {
1✔
329

330
                return node.getEnclosingType().getTypeMirror().subst(substitution);
1✔
331

332
            } else if (node.getParent() instanceof ASTConstructorCall) {
1!
333

334
                @NonNull JTypeMirror sym = ((ASTConstructorCall) node.getParent()).getTypeMirror();
1✔
335

336
                return sym instanceof JClassType && !sym.isInterface()
1!
337
                       ? (JClassType) sym
1✔
338
                       : factory.types().OBJECT;
1✔
339
            }
340

341
        } else if (isRecord()) {
1✔
342

343
            return factory.recordSuperclass();
1✔
344

345
        } else if (isAnnotation()) {
1✔
346

347
            return ts.OBJECT;
1✔
348

349
        }
350

351
        return null;
1✔
352
    }
353

354
    @Override
355
    public @Nullable JClassSymbol getSuperclass() {
356
        // notice this relies on the fact that the extends clause
357
        // (or the type node of the constructor call, for an anonymous class),
358
        // was disambiguated early
359

360
        // We special case anonymous classes so as not to trigger overload resolution
361
        if (isAnonymousClass() && node.getParent() instanceof ASTConstructorCall) {
1✔
362

363
            @NonNull JTypeMirror sym = ((ASTConstructorCall) node.getParent()).getTypeNode().getTypeMirror();
1✔
364

365
            return sym instanceof JClassType && !sym.isInterface()
1!
366
                   ? ((JClassType) sym).getSymbol()
1✔
367
                   : factory.types().OBJECT.getSymbol();
1✔
368

369
        }
370

371
        JClassType sup = getSuperclassType(Substitution.EMPTY);
1✔
372
        return sup == null ? null : sup.getSymbol();
1!
373
    }
374

375
    @Override
376
    public List<JClassSymbol> getSuperInterfaces() {
377
        List<JClassSymbol> itfs = CollectionUtil.mapNotNull(
1✔
378
            node.getSuperInterfaceTypeNodes(),
1✔
379
            n -> {
380
                // we play safe here, but the symbol is either a JClassSymbol
381
                // or a JTypeParameterSymbol, with the latter case being a
382
                // compile-time error
383
                JTypeDeclSymbol sym = n.getTypeMirror().getSymbol();
1✔
384
                return sym instanceof JClassSymbol ? (JClassSymbol) sym : null;
1!
385
            }
386
        );
387
        if (isAnnotation()) {
1✔
388
            itfs = CollectionUtil.concatView(Collections.singletonList(factory.annotationSym()), itfs);
1✔
389
        }
390
        return itfs;
1✔
391
    }
392

393
    @Override
394
    public List<JClassType> getSuperInterfaceTypes(Substitution subst) {
395
        List<JClassType> itfs = CollectionUtil.map(node.getSuperInterfaceTypeNodes(), n -> (JClassType) TypeOps.subst(n.getTypeMirror(), subst));
1✔
396
        if (isAnnotation()) {
1✔
397
            itfs = CollectionUtil.concatView(Collections.singletonList(factory.annotationType()), itfs);
1✔
398
        }
399
        return itfs;
1✔
400
    }
401

402
    @Override
403
    public @Nullable JTypeDeclSymbol getArrayComponent() {
UNCOV
404
        return null;
×
405
    }
406

407
    @Override
408
    public boolean isArray() {
409
        return false;
1✔
410
    }
411

412
    @Override
413
    public boolean isPrimitive() {
414
        return false;
1✔
415
    }
416

417
    @Override
418
    public boolean isInterface() {
419
        return node.isInterface();
1✔
420
    }
421

422
    @Override
423
    public boolean isEnum() {
424
        return node.isEnum();
1✔
425
    }
426

427
    @Override
428
    public boolean isRecord() {
429
        return node.isRecord();
1✔
430
    }
431

432
    @Override
433
    public boolean isAnnotation() {
434
        return node.isAnnotation();
1✔
435
    }
436

437
    @Override
438
    public boolean isLocalClass() {
439
        return node.isLocal();
1✔
440
    }
441

442
    @Override
443
    public boolean isAnonymousClass() {
444
        return node.isAnonymous();
1✔
445
    }
446

447
    @Override
448
    public PSet<String> getAnnotationAttributeNames() {
UNCOV
449
        return annotAttributes;
×
450
    }
451
}
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