Check repeatable annotation container parameters, retention, target
#KT-12794 #KT-47928
This commit is contained in:
+2
@@ -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) {
|
||||
|
||||
+1
@@ -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>>()
|
||||
|
||||
+117
-1
@@ -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)
|
||||
|
||||
|
||||
+9
-1
@@ -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");
|
||||
|
||||
+12
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user