Check repeatable annotation container parameters, retention, target

#KT-12794
 #KT-47928
This commit is contained in:
Alexander Udalov
2021-07-28 01:10:43 +02:00
parent 0a6d010d1c
commit e20b354dbd
28 changed files with 1362 additions and 5 deletions
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.resolve.jvm.checkers
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtAnnotated
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.resolve.AdditionalAnnotationChecker
import org.jetbrains.kotlin.resolve.BindingContext
@@ -21,6 +22,7 @@ object ExplicitMetadataChecker : AdditionalAnnotationChecker {
entries: List<KtAnnotationEntry>,
actualTargets: List<KotlinTarget>,
trace: BindingTrace,
annotated: KtAnnotated?,
languageVersionSettings: LanguageVersionSettings
) {
for (entry in entries) {
@@ -41,6 +41,7 @@ object FileClassAnnotationsChecker : AdditionalAnnotationChecker {
entries: List<KtAnnotationEntry>,
actualTargets: List<KotlinTarget>,
trace: BindingTrace,
annotated: KtAnnotated?,
languageVersionSettings: LanguageVersionSettings
) {
val fileAnnotationsToCheck = arrayListOf<Pair<KtAnnotationEntry, ClassDescriptor>>()
@@ -5,37 +5,48 @@
package org.jetbrains.kotlin.resolve.jvm.checkers
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
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.ModuleDescriptor
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.diagnostics.Diagnostic
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtAnnotated
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.resolve.AdditionalAnnotationChecker
import org.jetbrains.kotlin.resolve.AnnotationChecker
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.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getAnnotationRetention
import org.jetbrains.kotlin.resolve.descriptorUtil.isAnnotatedWithKotlinRepeatable
import org.jetbrains.kotlin.resolve.jvm.JvmPlatformAnnotationFeaturesSupport
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
import org.jetbrains.kotlin.types.TypeUtils
class RepeatableAnnotationChecker(
languageVersionSettings: LanguageVersionSettings,
private val languageVersionSettings: LanguageVersionSettings,
private val jvmTarget: JvmTarget,
private val platformAnnotationFeaturesSupport: JvmPlatformAnnotationFeaturesSupport,
private val module: ModuleDescriptor,
) : AdditionalAnnotationChecker {
override fun checkEntries(
entries: List<KtAnnotationEntry>,
actualTargets: List<KotlinTarget>,
trace: BindingTrace,
annotated: KtAnnotated?,
languageVersionSettings: LanguageVersionSettings
) {
if (entries.isEmpty()) return
@@ -49,6 +60,20 @@ class RepeatableAnnotationChecker(
}
checkRepeatedEntries(annotations, trace)
if (annotated is KtClassOrObject && annotated.hasModifier(KtTokens.ANNOTATION_KEYWORD)) {
val annotationClass = trace.get(BindingContext.CLASS, annotated)
val repeatable = annotations.find { it.descriptor.fqName == JvmAnnotationNames.REPEATABLE_ANNOTATION }
if (annotationClass != null && repeatable != null) {
val containerKClassValue = repeatable.descriptor.allValueArguments[Name.identifier("value")]
if (containerKClassValue is KClassValue) {
val containerClass = TypeUtils.getClassDescriptor(containerKClassValue.getArgumentType(module))
if (containerClass != null) {
checkRepeatableAnnotationContainer(annotationClass, containerClass, trace, repeatable.entry)
}
}
}
}
}
private fun checkRepeatedEntries(annotations: List<ResolvedAnnotation>, trace: BindingTrace) {
@@ -88,6 +113,97 @@ class RepeatableAnnotationChecker(
}
}
private fun checkRepeatableAnnotationContainer(
annotationClass: ClassDescriptor,
containerClass: ClassDescriptor,
trace: BindingTrace,
reportOn: KtAnnotationEntry,
) {
checkContainerParameters(containerClass, annotationClass, reportOn)?.let(trace::report)
checkContainerRetention(containerClass, annotationClass, reportOn)?.let(trace::report)
checkContainerTarget(containerClass, annotationClass, reportOn)?.let(trace::report)
}
private fun checkContainerParameters(
containerClass: ClassDescriptor,
annotationClass: ClassDescriptor,
reportOn: KtAnnotationEntry,
): Diagnostic? {
val containerCtor = containerClass.unsubstitutedPrimaryConstructor ?: return null
val value = containerCtor.valueParameters.find { it.name.asString() == "value" }
if (value == null || !KotlinBuiltIns.isArray(value.type) ||
value.type.arguments.single().type.constructor.declarationDescriptor != annotationClass
) {
return select(
ErrorsJvm.REPEATABLE_CONTAINER_MUST_HAVE_VALUE_ARRAY, ErrorsJvm.REPEATABLE_CONTAINER_MUST_HAVE_VALUE_ARRAY_ERROR
).on(reportOn, containerClass.fqNameSafe, annotationClass.fqNameSafe)
}
val otherNonDefault = containerCtor.valueParameters.find { it.name.asString() != "value" && !it.declaresDefaultValue() }
if (otherNonDefault != null) {
return select(
ErrorsJvm.REPEATABLE_CONTAINER_HAS_NON_DEFAULT_PARAMETER, ErrorsJvm.REPEATABLE_CONTAINER_HAS_NON_DEFAULT_PARAMETER_ERROR
).on(reportOn, containerClass.fqNameSafe, otherNonDefault)
}
return null
}
private fun checkContainerRetention(
containerClass: ClassDescriptor,
annotationClass: ClassDescriptor,
reportOn: KtAnnotationEntry,
): Diagnostic? {
val annotationRetention = annotationClass.getAnnotationRetention() ?: KotlinRetention.RUNTIME
val containerRetention = containerClass.getAnnotationRetention() ?: KotlinRetention.RUNTIME
if (containerRetention > annotationRetention) {
return select(
ErrorsJvm.REPEATABLE_CONTAINER_HAS_SHORTER_RETENTION, ErrorsJvm.REPEATABLE_CONTAINER_HAS_SHORTER_RETENTION_ERROR
).on(reportOn, containerClass.fqNameSafe, containerRetention.name, annotationClass.fqNameSafe, annotationRetention.name)
}
return null
}
private fun checkContainerTarget(
containerClass: ClassDescriptor,
annotationClass: ClassDescriptor,
reportOn: KtAnnotationEntry,
): Diagnostic? {
val annotationTargets = AnnotationChecker.applicableTargetSet(annotationClass) ?: KotlinTarget.DEFAULT_TARGET_SET
val containerTargets = AnnotationChecker.applicableTargetSet(containerClass) ?: KotlinTarget.DEFAULT_TARGET_SET
// See https://docs.oracle.com/javase/specs/jls/se16/html/jls-9.html#jls-9.6.3.
// (TBH, the rules about TYPE/TYPE_USE and TYPE_PARAMETER/TYPE_USE don't seem to make a lot of sense, but it's JLS
// so we better obey it for full interop with the Java language and reflection.)
for (target in containerTargets) {
val ok = when (target) {
in annotationTargets -> true
KotlinTarget.ANNOTATION_CLASS ->
KotlinTarget.CLASS in annotationTargets ||
KotlinTarget.TYPE in annotationTargets
KotlinTarget.CLASS ->
KotlinTarget.TYPE in annotationTargets
KotlinTarget.TYPE_PARAMETER ->
KotlinTarget.TYPE in annotationTargets
else -> false
}
if (!ok) {
return select(
ErrorsJvm.REPEATABLE_CONTAINER_TARGET_SET_NOT_A_SUBSET, ErrorsJvm.REPEATABLE_CONTAINER_TARGET_SET_NOT_A_SUBSET_ERROR
).on(reportOn, containerClass.fqNameSafe, annotationClass.fqNameSafe)
}
}
return null
}
private fun <D> select(warning: D, error: D): D =
if (languageVersionSettings.supportsFeature(LanguageFeature.RepeatableAnnotationContainerConstraints))
error
else
warning
private fun isRepeatableAnnotation(classDescriptor: ClassDescriptor): Boolean =
classDescriptor.isAnnotatedWithKotlinRepeatable() || platformAnnotationFeaturesSupport.isRepeatableAnnotationClass(classDescriptor)
@@ -14,7 +14,6 @@ import org.jetbrains.kotlin.utils.StringsKt;
import java.util.List;
import static kotlin.collections.CollectionsKt.*;
import static org.jetbrains.kotlin.diagnostics.Errors.UPPER_BOUND_VIOLATED_IN_TYPEALIAS_EXPANSION;
import static org.jetbrains.kotlin.diagnostics.rendering.Renderers.*;
import static org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm.*;
@@ -76,6 +75,15 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
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(REPEATABLE_CONTAINER_MUST_HAVE_VALUE_ARRAY, "Container annotation ''{0}'' must have a property ''value'' of type ''Array<{1}>''. This code will be prohibited in Kotlin 1.6", TO_STRING, TO_STRING);
MAP.put(REPEATABLE_CONTAINER_MUST_HAVE_VALUE_ARRAY_ERROR, "Container annotation ''{0}'' must have a property ''value'' of type ''Array<{1}>''", TO_STRING, TO_STRING);
MAP.put(REPEATABLE_CONTAINER_HAS_NON_DEFAULT_PARAMETER, "Container annotation ''{0}'' does not have a default value for ''{1}''. This code will be prohibited in Kotlin 1.6", TO_STRING, NAME);
MAP.put(REPEATABLE_CONTAINER_HAS_NON_DEFAULT_PARAMETER_ERROR, "Container annotation ''{0}'' does not have a default value for ''{1}''", TO_STRING, NAME);
MAP.put(REPEATABLE_CONTAINER_HAS_SHORTER_RETENTION, "Container annotation ''{0}'' has shorter retention (''{1}'') than the repeatable annotation ''{2}'' (''{3}''). This code will be prohibited in Kotlin 1.6", TO_STRING, TO_STRING, TO_STRING, TO_STRING);
MAP.put(REPEATABLE_CONTAINER_HAS_SHORTER_RETENTION_ERROR, "Container annotation ''{0}'' has shorter retention (''{1}'') than the repeatable annotation ''{2}'' (''{3}'')", TO_STRING, TO_STRING, TO_STRING, TO_STRING);
MAP.put(REPEATABLE_CONTAINER_TARGET_SET_NOT_A_SUBSET, "Target set of container annotation ''{0}'' must be a subset of the target set of contained annotation ''{1}''. This code will be prohibited in Kotlin 1.6", TO_STRING, TO_STRING);
MAP.put(REPEATABLE_CONTAINER_TARGET_SET_NOT_A_SUBSET_ERROR, "Target set of container annotation ''{0}'' must be a subset of the target set of contained annotation ''{1}''", TO_STRING, TO_STRING);
MAP.put(JVM_PACKAGE_NAME_CANNOT_BE_EMPTY, "''@JvmPackageName'' annotation value cannot be empty");
MAP.put(JVM_PACKAGE_NAME_MUST_BE_VALID_NAME, "''@JvmPackageName'' annotation value must be a valid dot-qualified name of a package");
MAP.put(JVM_PACKAGE_NAME_NOT_SUPPORTED_IN_FILES_WITH_CLASSES, "''@JvmPackageName'' annotation is not supported for files with class declarations");
@@ -9,6 +9,7 @@ import com.intellij.psi.PsiElement;
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor;
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
import org.jetbrains.kotlin.descriptors.Named;
import org.jetbrains.kotlin.diagnostics.*;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
@@ -69,9 +70,20 @@ public interface ErrorsJvm {
DiagnosticFactory0<KtExpression> POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION = DiagnosticFactory0.create(ERROR);
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);
DiagnosticFactory2<KtAnnotationEntry, FqName, FqName> REPEATABLE_CONTAINER_MUST_HAVE_VALUE_ARRAY = DiagnosticFactory2.create(WARNING);
DiagnosticFactory2<KtAnnotationEntry, FqName, FqName> REPEATABLE_CONTAINER_MUST_HAVE_VALUE_ARRAY_ERROR = DiagnosticFactory2.create(ERROR);
DiagnosticFactory2<KtAnnotationEntry, FqName, Named> REPEATABLE_CONTAINER_HAS_NON_DEFAULT_PARAMETER = DiagnosticFactory2.create(WARNING);
DiagnosticFactory2<KtAnnotationEntry, FqName, Named> REPEATABLE_CONTAINER_HAS_NON_DEFAULT_PARAMETER_ERROR = DiagnosticFactory2.create(ERROR);
DiagnosticFactory4<KtAnnotationEntry, FqName, String, FqName, String> REPEATABLE_CONTAINER_HAS_SHORTER_RETENTION = DiagnosticFactory4.create(WARNING);
DiagnosticFactory4<KtAnnotationEntry, FqName, String, FqName, String> REPEATABLE_CONTAINER_HAS_SHORTER_RETENTION_ERROR = DiagnosticFactory4.create(ERROR);
DiagnosticFactory2<KtAnnotationEntry, FqName, FqName> REPEATABLE_CONTAINER_TARGET_SET_NOT_A_SUBSET = DiagnosticFactory2.create(WARNING);
DiagnosticFactory2<KtAnnotationEntry, FqName, FqName> REPEATABLE_CONTAINER_TARGET_SET_NOT_A_SUBSET_ERROR = 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);