From 857cc92326cb52a1f1f5f9635e2683ad0bfa1c07 Mon Sep 17 00:00:00 2001 From: Victor Petukhov Date: Tue, 8 Dec 2020 18:32:33 +0300 Subject: [PATCH] Support preference of TYPE_USE annotations to enhance over others like METHOD, FIELD and VALUE_PARAMETER to avoid double applying them in case of arrays: @NotNull Integer [] (both to the array element and to the entire array) ^KT-24392 Fixed --- .../AdditionalClassAnnotationLowering.kt | 3 +- .../AnnotationQualifierApplicabilityType.kt | 9 +++- .../kotlin/load/java/JvmAnnotationNames.java | 11 +++++ .../java/AnnotationTypeQualifierResolver.kt | 37 ++++++++++------ .../java/components/JavaAnnotationMapper.kt | 42 ++++++++----------- .../typeEnhancement/signatureEnhancement.kt | 19 +++++++-- .../jetbrains/kotlin/SpecialJvmAnnotations.kt | 6 +-- .../conversions/JavaAnnotationsConversion.kt | 3 +- 8 files changed, 83 insertions(+), 47 deletions(-) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AdditionalClassAnnotationLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AdditionalClassAnnotationLowering.kt index 32445fc7ecd..ae5a6c1eff5 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AdditionalClassAnnotationLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AdditionalClassAnnotationLowering.kt @@ -37,6 +37,7 @@ import org.jetbrains.kotlin.ir.util.defaultType import org.jetbrains.kotlin.ir.util.getAnnotation import org.jetbrains.kotlin.ir.util.hasAnnotation import org.jetbrains.kotlin.ir.util.isAnnotationClass +import org.jetbrains.kotlin.load.java.JvmAnnotationNames import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.utils.addToStdlib.safeAs @@ -191,7 +192,7 @@ private class AdditionalClassAnnotationLowering(private val context: JvmBackendC } private fun generateTargetAnnotation(irClass: IrClass) { - if (irClass.hasAnnotation(FqName("java.lang.annotation.Target"))) return + if (irClass.hasAnnotation(JvmAnnotationNames.TARGET_ANNOTATION)) return val annotationTargetMap = annotationTargetMaps[jvmTarget] ?: throw AssertionError("No annotation target map for JVM target $jvmTarget") diff --git a/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/AnnotationQualifierApplicabilityType.kt b/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/AnnotationQualifierApplicabilityType.kt index a41346fd68f..8ef7e3bf738 100644 --- a/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/AnnotationQualifierApplicabilityType.kt +++ b/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/AnnotationQualifierApplicabilityType.kt @@ -5,6 +5,11 @@ package org.jetbrains.kotlin.load.java -enum class AnnotationQualifierApplicabilityType { - METHOD_RETURN_TYPE, VALUE_PARAMETER, FIELD, TYPE_USE, TYPE_PARAMETER_BOUNDS +enum class AnnotationQualifierApplicabilityType(val javaTarget: String) { + METHOD_RETURN_TYPE("METHOD"), + VALUE_PARAMETER("PARAMETER"), + FIELD("FIELD"), + TYPE_USE("TYPE_USE"), + TYPE_PARAMETER_BOUNDS("TYPE_USE"), + TYPE_PARAMETER("TYPE_PARAMETER") } diff --git a/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java b/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java index 76613d1744b..fbd565b2b17 100644 --- a/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java +++ b/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAnnotationNames.java @@ -16,10 +16,15 @@ package org.jetbrains.kotlin.load.java; +import kotlin.annotation.Repeatable; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.name.Name; import org.jetbrains.kotlin.resolve.jvm.JvmClassName; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + @SuppressWarnings("PointlessBitwiseExpression") public final class JvmAnnotationNames { public static final FqName METADATA_FQ_NAME = new FqName("kotlin.Metadata"); @@ -44,6 +49,12 @@ public final class JvmAnnotationNames { public static final Name DEFAULT_ANNOTATION_MEMBER_NAME = Name.identifier("value"); + public static final FqName TARGET_ANNOTATION = new FqName(Target.class.getCanonicalName()); + public static final FqName RETENTION_ANNOTATION = new FqName(Retention.class.getCanonicalName()); + public static final FqName DEPRECATED_ANNOTATION = new FqName(Deprecated.class.getCanonicalName()); + public static final FqName DOCUMENTED_ANNOTATION = new FqName(Documented.class.getCanonicalName()); + public static final FqName REPEATABLE_ANNOTATION = new FqName("java.lang.annotation.Repeatable"); + public static final FqName JETBRAINS_NOT_NULL_ANNOTATION = new FqName("org.jetbrains.annotations.NotNull"); public static final FqName JETBRAINS_NULLABLE_ANNOTATION = new FqName("org.jetbrains.annotations.Nullable"); public static final FqName JETBRAINS_MUTABLE_ANNOTATION = new FqName("org.jetbrains.annotations.Mutable"); diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/AnnotationTypeQualifierResolver.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/AnnotationTypeQualifierResolver.kt index 44d84d0eb6d..a7917957232 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/AnnotationTypeQualifierResolver.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/AnnotationTypeQualifierResolver.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.load.java import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor +import org.jetbrains.kotlin.load.java.components.JavaAnnotationTargetMapper import org.jetbrains.kotlin.resolve.constants.ArrayValue import org.jetbrains.kotlin.resolve.constants.ConstantValue import org.jetbrains.kotlin.resolve.constants.EnumValue @@ -114,7 +115,7 @@ class AnnotationTypeQualifierResolver(storageManager: StorageManager, private va .allValueArguments .flatMap { (parameter, argument) -> if (parameter == JvmAnnotationNames.DEFAULT_ANNOTATION_MEMBER_NAME) - argument.mapConstantToQualifierApplicabilityTypes() + argument.mapJavaConstantToQualifierApplicabilityTypes() else emptyList() } @@ -126,6 +127,16 @@ class AnnotationTypeQualifierResolver(storageManager: StorageManager, private va return TypeQualifierWithApplicability(typeQualifier, elementTypesMask) } + fun resolveAnnotation(annotationDescriptor: AnnotationDescriptor): TypeQualifierWithApplicability? { + val annotatedClass = annotationDescriptor.annotationClass ?: return null + val target = annotatedClass.annotations.findAnnotation(JvmAnnotationNames.TARGET_ANNOTATION) ?: return null + val elementTypesMask = target.allValueArguments + .flatMap { (_, argument) -> argument.mapKotlinConstantToQualifierApplicabilityTypes() } + .fold(0) { acc: Int, applicabilityType -> acc or (1 shl applicabilityType.ordinal) } + + return TypeQualifierWithApplicability(annotationDescriptor, elementTypesMask) + } + fun resolveJsr305AnnotationState(annotationDescriptor: AnnotationDescriptor): ReportLevel { resolveJsr305CustomState(annotationDescriptor)?.let { return it } return javaTypeEnhancementState.globalJsr305Level @@ -150,20 +161,22 @@ class AnnotationTypeQualifierResolver(storageManager: StorageManager, private va } } - private fun ConstantValue<*>.mapConstantToQualifierApplicabilityTypes(): List = + private fun String.toKotlinTargetNames() = JavaAnnotationTargetMapper.mapJavaTargetArgumentByName(this).map { it.name } + + private fun ConstantValue<*>.mapConstantToQualifierApplicabilityTypes( + findPredicate: EnumValue.(AnnotationQualifierApplicabilityType) -> Boolean + ): List = when (this) { - is ArrayValue -> value.flatMap { it.mapConstantToQualifierApplicabilityTypes() } - is EnumValue -> listOfNotNull( - when (enumEntryName.identifier) { - "METHOD" -> AnnotationQualifierApplicabilityType.METHOD_RETURN_TYPE - "FIELD" -> AnnotationQualifierApplicabilityType.FIELD - "PARAMETER" -> AnnotationQualifierApplicabilityType.VALUE_PARAMETER - "TYPE_USE" -> AnnotationQualifierApplicabilityType.TYPE_USE - else -> null - } - ) + is ArrayValue -> value.flatMap { it.mapConstantToQualifierApplicabilityTypes(findPredicate) } + is EnumValue -> listOfNotNull(AnnotationQualifierApplicabilityType.values().find { findPredicate(it) }) else -> emptyList() } + + private fun ConstantValue<*>.mapJavaConstantToQualifierApplicabilityTypes(): List = + mapConstantToQualifierApplicabilityTypes { enumEntryName.identifier == it.javaTarget } + + private fun ConstantValue<*>.mapKotlinConstantToQualifierApplicabilityTypes(): List = + mapConstantToQualifierApplicabilityTypes { enumEntryName.identifier in it.javaTarget.toKotlinTargetNames() } } private val ClassDescriptor.isAnnotatedWithTypeQualifier: Boolean diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/components/JavaAnnotationMapper.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/components/JavaAnnotationMapper.kt index 00f7a14146b..442aa6f1ee8 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/components/JavaAnnotationMapper.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/components/JavaAnnotationMapper.kt @@ -21,6 +21,8 @@ import org.jetbrains.kotlin.descriptors.SourceElement import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor 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.load.java.JvmAnnotationNames.* import org.jetbrains.kotlin.load.java.descriptors.PossiblyExternalAnnotationDescriptor import org.jetbrains.kotlin.load.java.lazy.LazyJavaResolverContext import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaAnnotationDescriptor @@ -35,31 +37,21 @@ import org.jetbrains.kotlin.resolve.constants.StringValue import org.jetbrains.kotlin.storage.getValue import org.jetbrains.kotlin.types.ErrorUtils import org.jetbrains.kotlin.types.SimpleType -import java.lang.annotation.Documented -import java.lang.annotation.Retention -import java.lang.annotation.Target import java.util.* object JavaAnnotationMapper { - - private val JAVA_TARGET_FQ_NAME = FqName(Target::class.java.canonicalName) - private val JAVA_RETENTION_FQ_NAME = FqName(Retention::class.java.canonicalName) - private val JAVA_DEPRECATED_FQ_NAME = FqName(java.lang.Deprecated::class.java.canonicalName) - private val JAVA_DOCUMENTED_FQ_NAME = FqName(Documented::class.java.canonicalName) // Java8-specific thing - private val JAVA_REPEATABLE_FQ_NAME = FqName("java.lang.annotation.Repeatable") - internal val DEPRECATED_ANNOTATION_MESSAGE = Name.identifier("message") internal val TARGET_ANNOTATION_ALLOWED_TARGETS = Name.identifier("allowedTargets") internal val RETENTION_ANNOTATION_VALUE = Name.identifier("value") fun mapOrResolveJavaAnnotation(annotation: JavaAnnotation, c: LazyJavaResolverContext): AnnotationDescriptor? = when (annotation.classId) { - ClassId.topLevel(JAVA_TARGET_FQ_NAME) -> JavaTargetAnnotationDescriptor(annotation, c) - ClassId.topLevel(JAVA_RETENTION_FQ_NAME) -> JavaRetentionAnnotationDescriptor(annotation, c) - ClassId.topLevel(JAVA_REPEATABLE_FQ_NAME) -> JavaAnnotationDescriptor(c, annotation, StandardNames.FqNames.repeatable) - ClassId.topLevel(JAVA_DOCUMENTED_FQ_NAME) -> JavaAnnotationDescriptor(c, annotation, StandardNames.FqNames.mustBeDocumented) - ClassId.topLevel(JAVA_DEPRECATED_FQ_NAME) -> null + ClassId.topLevel(TARGET_ANNOTATION) -> JavaTargetAnnotationDescriptor(annotation, c) + ClassId.topLevel(RETENTION_ANNOTATION) -> JavaRetentionAnnotationDescriptor(annotation, c) + ClassId.topLevel(REPEATABLE_ANNOTATION) -> JavaAnnotationDescriptor(c, annotation, StandardNames.FqNames.repeatable) + ClassId.topLevel(DOCUMENTED_ANNOTATION) -> JavaAnnotationDescriptor(c, annotation, StandardNames.FqNames.mustBeDocumented) + ClassId.topLevel(DEPRECATED_ANNOTATION) -> null else -> LazyJavaAnnotationDescriptor(c, annotation) } @@ -69,7 +61,7 @@ object JavaAnnotationMapper { c: LazyJavaResolverContext ): AnnotationDescriptor? { if (kotlinName == StandardNames.FqNames.deprecated) { - val javaAnnotation = annotationOwner.findAnnotation(JAVA_DEPRECATED_FQ_NAME) + val javaAnnotation = annotationOwner.findAnnotation(DEPRECATED_ANNOTATION) if (javaAnnotation != null || annotationOwner.isDeprecatedInJavaDoc) { return JavaDeprecatedAnnotationDescriptor(javaAnnotation, c) } @@ -84,19 +76,19 @@ object JavaAnnotationMapper { // kotlin.annotation.annotation is treated separately private val kotlinToJavaNameMap: Map = mapOf( - StandardNames.FqNames.target to JAVA_TARGET_FQ_NAME, - StandardNames.FqNames.retention to JAVA_RETENTION_FQ_NAME, - StandardNames.FqNames.repeatable to JAVA_REPEATABLE_FQ_NAME, - StandardNames.FqNames.mustBeDocumented to JAVA_DOCUMENTED_FQ_NAME + StandardNames.FqNames.target to TARGET_ANNOTATION, + StandardNames.FqNames.retention to RETENTION_ANNOTATION, + StandardNames.FqNames.repeatable to REPEATABLE_ANNOTATION, + StandardNames.FqNames.mustBeDocumented to DOCUMENTED_ANNOTATION ) val javaToKotlinNameMap: Map = mapOf( - JAVA_TARGET_FQ_NAME to StandardNames.FqNames.target, - JAVA_RETENTION_FQ_NAME to StandardNames.FqNames.retention, - JAVA_DEPRECATED_FQ_NAME to StandardNames.FqNames.deprecated, - JAVA_REPEATABLE_FQ_NAME to StandardNames.FqNames.repeatable, - JAVA_DOCUMENTED_FQ_NAME to StandardNames.FqNames.mustBeDocumented + TARGET_ANNOTATION to StandardNames.FqNames.target, + RETENTION_ANNOTATION to StandardNames.FqNames.retention, + DEPRECATED_ANNOTATION to StandardNames.FqNames.deprecated, + REPEATABLE_ANNOTATION to StandardNames.FqNames.repeatable, + DOCUMENTED_ANNOTATION to StandardNames.FqNames.mustBeDocumented ) } diff --git a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt index 38e6ecdbc04..908c98444fc 100644 --- a/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt +++ b/core/descriptors.jvm/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt @@ -327,10 +327,23 @@ class SignatureEnhancement( isFromStarProjection: Boolean ): JavaTypeQualifiers { val composedAnnotation = - if (isHeadTypeConstructor && typeContainer != null) + if (isHeadTypeConstructor && typeContainer is TypeParameterDescriptor) { composeAnnotations(typeContainer.annotations, annotations) - else - annotations + } else if (isHeadTypeConstructor && typeContainer != null) { + val filteredContainerAnnotations = typeContainer.annotations.filter { + val (_, targets) = annotationTypeQualifierResolver.resolveAnnotation(it) ?: return@filter false + /* + * We don't apply container type use annotations to avoid double applying them like with arrays: + * @NotNull Integer [] f15(); + * Otherwise, in the example above we would apply `@NotNull` to `Integer` (i.e. array element; as TYPE_USE annotation) + * and to entire array (as METHOD annotation). + * In other words, we prefer TYPE_USE target of an annotation, and apply the annotation only according to it, if it's present. + * See KT-24392 for more details. + */ + AnnotationQualifierApplicabilityType.TYPE_USE !in targets + } + composeAnnotations(Annotations.create(filteredContainerAnnotations), annotations) + } else annotations fun List.ifPresent(qualifier: T) = if (any { composedAnnotation.findAnnotation(it) != null }) qualifier else null diff --git a/core/deserialization.common.jvm/src/org/jetbrains/kotlin/SpecialJvmAnnotations.kt b/core/deserialization.common.jvm/src/org/jetbrains/kotlin/SpecialJvmAnnotations.kt index 6479c5183cb..f2f2247ccfa 100644 --- a/core/deserialization.common.jvm/src/org/jetbrains/kotlin/SpecialJvmAnnotations.kt +++ b/core/deserialization.common.jvm/src/org/jetbrains/kotlin/SpecialJvmAnnotations.kt @@ -14,8 +14,8 @@ object SpecialJvmAnnotations { JvmAnnotationNames.METADATA_FQ_NAME, JvmAnnotationNames.JETBRAINS_NOT_NULL_ANNOTATION, JvmAnnotationNames.JETBRAINS_NULLABLE_ANNOTATION, - FqName("java.lang.annotation.Target"), - FqName("java.lang.annotation.Retention"), - FqName("java.lang.annotation.Documented") + JvmAnnotationNames.TARGET_ANNOTATION, + JvmAnnotationNames.RETENTION_ANNOTATION, + JvmAnnotationNames.DOCUMENTED_ANNOTATION ).mapTo(mutableSetOf(), ClassId::topLevel) } diff --git a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/JavaAnnotationsConversion.kt b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/JavaAnnotationsConversion.kt index a40ac220f98..2fba5cc8e14 100644 --- a/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/JavaAnnotationsConversion.kt +++ b/nj2k/src/org/jetbrains/kotlin/nj2k/conversions/JavaAnnotationsConversion.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.nj2k.conversions +import org.jetbrains.kotlin.load.java.JvmAnnotationNames import org.jetbrains.kotlin.nj2k.NewJ2kConverterContext import org.jetbrains.kotlin.nj2k.tree.* @@ -30,7 +31,7 @@ class JavaAnnotationsConversion(context: NewJ2kConverterContext) : RecursiveAppl annotation.classSymbol = symbolProvider.provideClassSymbol("kotlin.Deprecated") annotation.arguments = listOf(JKAnnotationParameterImpl(JKLiteralExpression("\"\"", JKLiteralExpression.LiteralType.STRING))) } - if (annotation.classSymbol.fqName == "java.lang.annotation.Target") { + if (annotation.classSymbol.fqName == JvmAnnotationNames.TARGET_ANNOTATION.asString()) { annotation.classSymbol = symbolProvider.provideClassSymbol("kotlin.annotation.Target") val arguments = annotation.arguments.singleOrNull()?.let { parameter ->