diff --git a/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java index 22809919f57..c01716408a2 100644 --- a/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java +++ b/checker-qual/src/main/java/org/checkerframework/framework/qual/TypeUseLocation.java @@ -18,6 +18,12 @@ */ public enum TypeUseLocation { + /** + * Apply default annotations to unannotated top-level types of class, interfaces, enums and + * record * + */ + TYPE, + /** Apply default annotations to unannotated top-level types of fields. */ FIELD, diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index 530b178c177..b0dd326981b 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -752,6 +752,11 @@ public AnnotatedTypeFactory(BaseTypeChecker checker) { mergeStubsWithSource = checker.hasOption("mergeStubsWithSource"); } + /** Get the element cache. */ + public Map getElementCache() { + return elementCache; + } + /** * Parse a string in the format {@code * FQN.canonical.Qualifier:FQN.alias1.Qual1,FQN.alias2.Qual2} to a pair of {@code diff --git a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java index a7b7c149fe8..5cfaf8efbeb 100644 --- a/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java +++ b/framework/src/main/java/org/checkerframework/framework/util/defaults/QualifierDefaults.java @@ -1010,6 +1010,24 @@ public Void scan(@FindDistinct AnnotatedTypeMirror t, AnnotationMirror qual) { // Some defaults only apply to the top level type. boolean isTopLevelType = t == outer.type; switch (outer.location) { + case TYPE: + if (outer.scope != null && outer.scope.getKind().isClass() && isTopLevelType) { + AnnotationMirror annotation = + outer.qualHierarchy.findAnnotationInHierarchy( + atypeFactory + .getElementCache() + .get(outer.scope) + .getAnnotations(), + qual); + if (annotation == null + || outer.qualHierarchy.isSubtypeQualifiersOnly(qual, annotation)) { + outer.addAnnotation(t, qual); + atypeFactory.getElementCache().put(outer.scope, t); + } else { + // should report error; + } + } + break; case FIELD: if (outer.scope != null && outer.scope.getKind() == ElementKind.FIELD diff --git a/framework/tests/viewpointtest/DefaultQualifierTest.java b/framework/tests/viewpointtest/DefaultQualifierTest.java new file mode 100644 index 00000000000..f6b7e3d229e --- /dev/null +++ b/framework/tests/viewpointtest/DefaultQualifierTest.java @@ -0,0 +1,13 @@ +import org.checkerframework.framework.qual.DefaultQualifier; +import org.checkerframework.framework.qual.TypeUseLocation; + +import viewpointtest.quals.*; + +@DefaultQualifier( + value = A.class, + locations = {TypeUseLocation.TYPE}) +public class DefaultQualifierTest { + // :: error: (type.invalid.annotations.on.use) :: error: (super.invocation.invalid) :: warning: + // (inconsistent.constructor.type) + @B DefaultQualifierTest() {} +}