Report error if both repeatable annotation and its container are used

#KT-12794
This commit is contained in:
Alexander Udalov
2021-07-24 00:47:35 +02:00
parent b2550f69bc
commit 67128c022a
13 changed files with 833 additions and 12 deletions
@@ -9,14 +9,18 @@ import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget
import org.jetbrains.kotlin.descriptors.annotations.KotlinRetention
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.resolve.AdditionalAnnotationChecker
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.constants.KClassValue
import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass
import org.jetbrains.kotlin.resolve.descriptorUtil.getAnnotationRetention
import org.jetbrains.kotlin.resolve.descriptorUtil.isAnnotatedWithKotlinRepeatable
@@ -28,8 +32,6 @@ class RepeatableAnnotationChecker(
private val jvmTarget: JvmTarget,
private val platformAnnotationFeaturesSupport: JvmPlatformAnnotationFeaturesSupport,
) : AdditionalAnnotationChecker {
private val nonSourceDisallowed = !languageVersionSettings.supportsFeature(LanguageFeature.RepeatableAnnotations)
override fun checkEntries(
entries: List<KtAnnotationEntry>,
actualTargets: List<KotlinTarget>,
@@ -38,14 +40,24 @@ class RepeatableAnnotationChecker(
) {
if (entries.isEmpty()) return
val annotations = entries.mapNotNull { entry ->
val descriptor = trace.get(BindingContext.ANNOTATION, entry)
val useSiteTarget = entry.useSiteTarget?.getAnnotationUseSiteTarget()
if (descriptor != null) {
ResolvedAnnotation(entry, descriptor, useSiteTarget)
} else null
}
checkRepeatedEntries(annotations, trace)
}
private fun checkRepeatedEntries(annotations: List<ResolvedAnnotation>, trace: BindingTrace) {
val entryTypesWithAnnotations = hashMapOf<FqName, MutableList<AnnotationUseSiteTarget?>>()
for (entry in entries) {
val descriptor = trace.get(BindingContext.ANNOTATION, entry) ?: continue
for ((entry, descriptor, useSiteTarget) in annotations) {
val fqName = descriptor.fqName ?: continue
val classDescriptor = descriptor.annotationClass ?: continue
val useSiteTarget = entry.useSiteTarget?.getAnnotationUseSiteTarget()
val existingTargetsForAnnotation = entryTypesWithAnnotations.getOrPut(fqName) { arrayListOf() }
val duplicateAnnotation = useSiteTarget in existingTargetsForAnnotation
|| (existingTargetsForAnnotation.any { (it == null) != (useSiteTarget == null) })
@@ -54,13 +66,21 @@ class RepeatableAnnotationChecker(
&& isRepeatableAnnotation(classDescriptor)
&& classDescriptor.getAnnotationRetention() != KotlinRetention.SOURCE
) {
val error = when {
jvmTarget == JvmTarget.JVM_1_6 -> ErrorsJvm.REPEATED_ANNOTATION_TARGET6
nonSourceDisallowed -> ErrorsJvm.NON_SOURCE_REPEATED_ANNOTATION
else -> null
}
if (error != null) {
trace.report(error.on(entry))
when {
jvmTarget == JvmTarget.JVM_1_6 -> {
trace.report(ErrorsJvm.REPEATED_ANNOTATION_TARGET6.on(entry))
}
languageVersionSettings.supportsFeature(LanguageFeature.RepeatableAnnotations) -> {
// It's not allowed to have both a repeated annotation (applied more than once) and its container
// on the same element. See https://docs.oracle.com/javase/specs/jls/se16/html/jls-9.html#jls-9.7.5.
val explicitContainer = resolveContainerAnnotation(classDescriptor)
if (explicitContainer != null && annotations.any { it.descriptor.fqName == explicitContainer }) {
trace.report(ErrorsJvm.REPEATED_ANNOTATION_WITH_CONTAINER.on(entry, fqName, explicitContainer))
}
}
else -> {
trace.report(ErrorsJvm.NON_SOURCE_REPEATED_ANNOTATION.on(entry))
}
}
}
@@ -70,4 +90,21 @@ class RepeatableAnnotationChecker(
private fun isRepeatableAnnotation(classDescriptor: ClassDescriptor): Boolean =
classDescriptor.isAnnotatedWithKotlinRepeatable() || platformAnnotationFeaturesSupport.isRepeatableAnnotationClass(classDescriptor)
private data class ResolvedAnnotation(
val entry: KtAnnotationEntry,
val descriptor: AnnotationDescriptor,
val useSiteTarget: AnnotationUseSiteTarget?,
)
// For a repeatable annotation class, returns FQ name of the container annotation if it can be resolved in Kotlin.
// This only exists when the annotation class (whether declared in Java or Kotlin) is annotated with java.lang.annotation.Repeatable,
// in which case the container annotation is @j.l.a.Repeatable's only argument.
private fun resolveContainerAnnotation(annotationClass: ClassDescriptor): FqName? {
val javaRepeatable = annotationClass.annotations.findAnnotation(JvmAnnotationNames.REPEATABLE_ANNOTATION) ?: return null
val value = javaRepeatable.allValueArguments[Name.identifier("value")] as? KClassValue ?: return null
// Local annotations are supported neither in Java nor in Kotlin.
val normalClass = value.value as? KClassValue.Value.NormalClass ?: return null
return normalClass.classId.asSingleFqName()
}
}
@@ -73,6 +73,7 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
MAP.put(DEPRECATED_JAVA_ANNOTATION, "This annotation is deprecated in Kotlin. Use ''@{0}'' instead", TO_STRING);
MAP.put(NON_SOURCE_REPEATED_ANNOTATION, "Repeatable annotations with non-SOURCE retention are only supported starting from Kotlin 1.6");
MAP.put(REPEATED_ANNOTATION_TARGET6, "Repeatable annotations with non-SOURCE retention are not supported with JVM target 1.6. Use -jvm-target 1.8");
MAP.put(REPEATED_ANNOTATION_WITH_CONTAINER, "Repeated annotation ''@{0}'' cannot be used on a declaration which is annotated with its container annotation ''@{1}''", TO_STRING, TO_STRING);
MAP.put(ANNOTATION_IS_NOT_APPLICABLE_TO_MULTIFILE_CLASSES, "Annotation ''@{0}'' is not applicable to the multi-file classes", TO_STRING);
MAP.put(JVM_PACKAGE_NAME_CANNOT_BE_EMPTY, "''@JvmPackageName'' annotation value cannot be empty");
@@ -71,6 +71,7 @@ public interface ErrorsJvm {
DiagnosticFactory1<KtAnnotationEntry, FqName> DEPRECATED_JAVA_ANNOTATION = DiagnosticFactory1.create(WARNING);
DiagnosticFactory0<KtAnnotationEntry> NON_SOURCE_REPEATED_ANNOTATION = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtAnnotationEntry> REPEATED_ANNOTATION_TARGET6 = DiagnosticFactory0.create(ERROR);
DiagnosticFactory2<KtAnnotationEntry, FqName, FqName> REPEATED_ANNOTATION_WITH_CONTAINER = DiagnosticFactory2.create(ERROR);
DiagnosticFactory1<KtAnnotationEntry, FqName> ANNOTATION_IS_NOT_APPLICABLE_TO_MULTIFILE_CLASSES = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<KtAnnotationEntry> JVM_PACKAGE_NAME_CANNOT_BE_EMPTY = DiagnosticFactory0.create(ERROR);