diff --git a/compiler/testData/foreignAnnotations/tests/findBugsSimple.txt b/compiler/testData/foreignAnnotations/tests/findBugsSimple.txt index b5ada9fb616..d08da15a469 100644 --- a/compiler/testData/foreignAnnotations/tests/findBugsSimple.txt +++ b/compiler/testData/foreignAnnotations/tests/findBugsSimple.txt @@ -7,7 +7,7 @@ public open class A { @edu.umd.cs.findbugs.annotations.Nullable public final var field: kotlin.String? @edu.umd.cs.findbugs.annotations.NonNull public open fun bar(): kotlin.String public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean - @edu.umd.cs.findbugs.annotations.PossiblyNull public open fun foo(/*0*/ @edu.umd.cs.findbugs.annotations.NonNull x: kotlin.String, /*1*/ @edu.umd.cs.findbugs.annotations.UnknownNullness y: kotlin.CharSequence!): kotlin.String? + @edu.umd.cs.findbugs.annotations.PossiblyNull public open fun foo(/*0*/ @edu.umd.cs.findbugs.annotations.NonNull x: kotlin.String, /*1*/ @edu.umd.cs.findbugs.annotations.UnknownNullness y: kotlin.CharSequence?): kotlin.String? public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } diff --git a/compiler/testData/foreignAnnotations/tests/jsr305NullabilityNicknames.kt b/compiler/testData/foreignAnnotations/tests/jsr305NullabilityNicknames.kt new file mode 100644 index 00000000000..4c38ebf6bc7 --- /dev/null +++ b/compiler/testData/foreignAnnotations/tests/jsr305NullabilityNicknames.kt @@ -0,0 +1,67 @@ +// !DIAGNOSTICS: -UNUSED_VARIABLE -UNUSED_PARAMETER +// FILE: MyNullable.java +import javax.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.annotation.meta.TypeQualifierNickname; +import javax.annotation.meta.When; + +@Documented +@TypeQualifierNickname +@Nonnull(when = When.MAYBE) +@Retention(RetentionPolicy.RUNTIME) +public @interface MyNullable { + +} + +// FILE: MyNonnull.java +import javax.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.annotation.meta.TypeQualifierNickname; +import javax.annotation.meta.When; + +@Documented +@TypeQualifierNickname +@Nonnull(when = When.ALWAYS) +@Retention(RetentionPolicy.RUNTIME) +public @interface MyNonnull { + +} + +// FILE: A.java + +import javax.annotation.*; + +public class A { + @MyNullable public String field = null; + + @MyNullable + public String foo(@MyNonnull String x, @MyNullable CharSequence y) { + return ""; + } + + @MyNonnull + public String bar() { + return ""; + } + +} + +// FILE: main.kt + +fun main(a: A) { + a.foo("", null)?.length + a.foo("", null).length + a.foo(null, "").length + + a.bar().length + a.bar()!!.length + + a.field?.length + a.field.length +} diff --git a/compiler/testData/foreignAnnotations/tests/jsr305NullabilityNicknames.txt b/compiler/testData/foreignAnnotations/tests/jsr305NullabilityNicknames.txt new file mode 100644 index 00000000000..5c5e7e7a02e --- /dev/null +++ b/compiler/testData/foreignAnnotations/tests/jsr305NullabilityNicknames.txt @@ -0,0 +1,27 @@ +package + +public fun main(/*0*/ a: A): kotlin.Unit + +public open class A { + public constructor A() + @MyNullable public final var field: @MyNullable kotlin.String? + @MyNonnull public open fun bar(): @MyNonnull kotlin.String + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + @MyNullable public open fun foo(/*0*/ @MyNonnull x: @MyNonnull kotlin.String, /*1*/ @MyNullable y: @MyNullable kotlin.CharSequence?): @MyNullable kotlin.String? + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +@kotlin.annotation.MustBeDocumented @javax.annotation.meta.TypeQualifierNickname @javax.annotation.Nonnull(when = When.ALWAYS) @kotlin.annotation.Retention(value = AnnotationRetention.RUNTIME) public final annotation class MyNonnull : kotlin.Annotation { + public constructor MyNonnull() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + +@kotlin.annotation.MustBeDocumented @javax.annotation.meta.TypeQualifierNickname @javax.annotation.Nonnull(when = When.MAYBE) @kotlin.annotation.Retention(value = AnnotationRetention.RUNTIME) public final annotation class MyNullable : kotlin.Annotation { + public constructor MyNullable() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsTestGenerated.java index 9c6ef4a267a..91ca10c2c55 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/ForeignAnnotationsTestGenerated.java @@ -60,6 +60,12 @@ public class ForeignAnnotationsTestGenerated extends AbstractForeignAnnotationsT doTest(fileName); } + @TestMetadata("jsr305NullabilityNicknames.kt") + public void testJsr305NullabilityNicknames() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/foreignAnnotations/tests/jsr305NullabilityNicknames.kt"); + doTest(fileName); + } + @TestMetadata("jsr305Simple.kt") public void testJsr305Simple() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/foreignAnnotations/tests/jsr305Simple.kt"); diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/AnnotationTypeQualifierResolver.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/AnnotationTypeQualifierResolver.kt index d449c8be01e..14bd8a16c06 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/AnnotationTypeQualifierResolver.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/AnnotationTypeQualifierResolver.kt @@ -18,7 +18,10 @@ package org.jetbrains.kotlin.load.java import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor +import org.jetbrains.kotlin.descriptors.resolveClassByFqName +import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass import org.jetbrains.kotlin.storage.StorageManager @@ -49,6 +52,16 @@ class AnnotationTypeQualifierResolver(storageManager: StorageManager) { return resolveTypeQualifierNickname(annotationClass) } + + fun isTypeQualifier(moduleDescriptor: ModuleDescriptor, classFqName: FqName): Boolean { + val classDescriptor = moduleDescriptor.resolveClassByFqName( + classFqName, NoLookupLocation.FROM_JAVA_LOADER + ) ?: return false + + if (classDescriptor.isTypeQualifierAnnotation) return true + + return resolveTypeQualifierNickname(classDescriptor) != null + } } private val ClassDescriptor.isAnnotatedWithTypeQualifier: Boolean diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaScope.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaScope.kt index 43b4f2cbe9e..97cc55a0b13 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaScope.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/descriptors/LazyJavaScope.kt @@ -146,7 +146,9 @@ abstract class LazyJavaScope(protected val c: LazyJavaResolverContext) : MemberS val annotationMethod = method.containingClass.isAnnotationType val returnTypeAttrs = LazyJavaTypeAttributes( TypeUsage.COMMON, annotations, - isForAnnotationParameter = annotationMethod + isForAnnotationParameter = annotationMethod, + moduleDescriptor = c.module, + annotationTypeQualifierResolver = c.components.annotationTypeQualifierResolver ) return c.typeResolver.transformJavaType(method.returnType, returnTypeAttrs) } @@ -163,7 +165,12 @@ abstract class LazyJavaScope(protected val c: LazyJavaResolverContext) : MemberS val (index, javaParameter) = pair val annotations = c.resolveAnnotations(javaParameter) - val typeUsage = LazyJavaTypeAttributes(TypeUsage.COMMON, annotations) + val typeUsage = + LazyJavaTypeAttributes( + TypeUsage.COMMON, annotations, + annotationTypeQualifierResolver = c.components.annotationTypeQualifierResolver, + moduleDescriptor = c.module + ) val (outType, varargElementType) = if (javaParameter.isVararg) { val paramType = javaParameter.type as? JavaArrayType @@ -281,7 +288,11 @@ abstract class LazyJavaScope(protected val c: LazyJavaResolverContext) : MemberS val isNotNullable = !(field.isFinalStatic && field.hasConstantNotNullInitializer) val propertyType = c.typeResolver.transformJavaType( field.type, - LazyJavaTypeAttributes(TypeUsage.COMMON, annotations) + LazyJavaTypeAttributes( + TypeUsage.COMMON, annotations, + annotationTypeQualifierResolver = c.components.annotationTypeQualifierResolver, + moduleDescriptor = c.module + ) ) if (!isNotNullable) { return TypeUtils.makeNotNullable(propertyType) diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/types/JavaTypeResolver.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/types/JavaTypeResolver.kt index 0a314c15c54..ecf0cafe810 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/types/JavaTypeResolver.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/types/JavaTypeResolver.kt @@ -17,11 +17,13 @@ package org.jetbrains.kotlin.load.java.lazy.types import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.annotations.CompositeAnnotations import org.jetbrains.kotlin.descriptors.annotations.FilteredAnnotations import org.jetbrains.kotlin.load.java.ANNOTATIONS_COPIED_TO_TYPES +import org.jetbrains.kotlin.load.java.AnnotationTypeQualifierResolver import org.jetbrains.kotlin.load.java.components.TypeUsage import org.jetbrains.kotlin.load.java.components.TypeUsage.COMMON import org.jetbrains.kotlin.load.java.components.TypeUsage.SUPERTYPE @@ -309,9 +311,13 @@ enum class JavaTypeFlexibility { class LazyJavaTypeAttributes( override val howThisTypeIsUsed: TypeUsage, annotations: Annotations, - override val isForAnnotationParameter: Boolean = false + override val isForAnnotationParameter: Boolean = false, + private val annotationTypeQualifierResolver: AnnotationTypeQualifierResolver, + private val moduleDescriptor: ModuleDescriptor ): JavaTypeAttributes { - override val typeAnnotations = FilteredAnnotations(annotations) { it in ANNOTATIONS_COPIED_TO_TYPES } + override val typeAnnotations = FilteredAnnotations(annotations) { + it in ANNOTATIONS_COPIED_TO_TYPES || annotationTypeQualifierResolver.isTypeQualifier(moduleDescriptor, it) + } } fun TypeUsage.toAttributes( diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt index 32b4e32f7d8..d44d837e8a6 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/typeEnhancement/signatureEnhancement.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.load.java.typeEnhancement import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.load.java.* import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor @@ -25,6 +26,8 @@ import org.jetbrains.kotlin.load.kotlin.SignatureBuildingComponents import org.jetbrains.kotlin.load.kotlin.computeJvmDescriptor import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.platform.JavaToKotlinClassMap +import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.asFlexibleType import org.jetbrains.kotlin.types.checker.KotlinTypeChecker @@ -32,7 +35,7 @@ import org.jetbrains.kotlin.types.isFlexible import org.jetbrains.kotlin.types.typeUtil.isTypeParameter import java.util.* -class SignatureEnhancement { +class SignatureEnhancement(private val annotationTypeQualifierResolver: AnnotationTypeQualifierResolver) { fun enhanceSignatures(platformSignatures: Collection): Collection { return platformSignatures.map { it.enhanceSignature() @@ -143,29 +146,10 @@ class SignatureEnhancement { private fun KotlinType.extractQualifiersFromAnnotations(): JavaTypeQualifiers { fun List.ifPresent(qualifier: T) = if (any { annotations.findAnnotation(it) != null}) qualifier else null - // These two overloads are just for sake of optimization as in most cases last parameter in second overload is null fun uniqueNotNull(x: T?, y: T?) = if (x == null || y == null || x == y) x ?: y else null - fun uniqueNotNull(a: T?, b: T?, c: T?) = - if (c == null) - uniqueNotNull(a, b) - else - listOf(a, b, c).filterNotNull().toSet().singleOrNull() - // Javax/FundBugs NonNull annotation has parameter `when` that determines actual nullability - fun FqName.extractQualifierFromAnnotationWithWhen(): NullabilityQualifier? { - val annotationDescriptor = annotations.findAnnotation(this) ?: return null - return annotationDescriptor.allValueArguments.values.singleOrNull()?.value?.let { - enumEntryDescriptor -> - if (enumEntryDescriptor !is ClassDescriptor) return@let null - if (enumEntryDescriptor.name.asString() == "ALWAYS") NullabilityQualifier.NOT_NULL else NullabilityQualifier.NULLABLE - } ?: NullabilityQualifier.NOT_NULL - } + val nullability = annotations.extractNullability() - val nullability = uniqueNotNull( - NULLABLE_ANNOTATIONS.ifPresent(NullabilityQualifier.NULLABLE), - NOT_NULL_ANNOTATIONS.ifPresent(NullabilityQualifier.NOT_NULL), - JAVAX_NONNULL_ANNOTATION.extractQualifierFromAnnotationWithWhen() - ) return JavaTypeQualifiers( nullability, uniqueNotNull( @@ -180,6 +164,29 @@ class SignatureEnhancement { ) } + private fun Annotations.extractNullability(): NullabilityQualifier? { + for (annotationDescriptor in this) { + when (annotationDescriptor.annotationClass?.fqNameSafe) { + in NULLABLE_ANNOTATIONS -> return NullabilityQualifier.NULLABLE + in NOT_NULL_ANNOTATIONS -> return NullabilityQualifier.NOT_NULL + } + + val typeQualifier = + annotationTypeQualifierResolver + .resolveTypeQualifierAnnotation(annotationDescriptor) + ?.takeIf { it.annotationClass?.fqNameSafe == JAVAX_NONNULL_ANNOTATION } + ?: continue + + return typeQualifier.allValueArguments.values.singleOrNull()?.value?.let { + enumEntryDescriptor -> + if (enumEntryDescriptor !is ClassDescriptor) return@let null + if (enumEntryDescriptor.name.asString() == "ALWAYS") NullabilityQualifier.NOT_NULL else NullabilityQualifier.NULLABLE + } ?: NullabilityQualifier.NOT_NULL + } + + return null + } + fun KotlinType.computeIndexedQualifiersForOverride( fromSupertypes: Collection, isCovariant: Boolean ): (Int) -> JavaTypeQualifiers { diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/RuntimeModuleData.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/RuntimeModuleData.kt index 041d9b25218..7ad7bfaaf60 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/RuntimeModuleData.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/RuntimeModuleData.kt @@ -58,14 +58,15 @@ class RuntimeModuleData private constructor( val runtimePackagePartProvider = RuntimePackagePartProvider(classLoader) val javaResolverCache = JavaResolverCache.EMPTY val notFoundClasses = NotFoundClasses(storageManager, module) + val annotationTypeQualifierResolver = AnnotationTypeQualifierResolver(storageManager) val globalJavaResolverContext = JavaResolverComponents( storageManager, ReflectJavaClassFinder(classLoader), reflectKotlinClassFinder, deserializedDescriptorResolver, ExternalAnnotationResolver.EMPTY, SignaturePropagator.DO_NOTHING, RuntimeErrorReporter, javaResolverCache, JavaPropertyInitializerEvaluator.DoNothing, SamConversionResolver, RuntimeSourceElementFactory, singleModuleClassResolver, runtimePackagePartProvider, SupertypeLoopChecker.EMPTY, LookupTracker.DO_NOTHING, module, ReflectionTypes(module, notFoundClasses), - AnnotationTypeQualifierResolver(storageManager), - SignatureEnhancement() + annotationTypeQualifierResolver, + SignatureEnhancement(annotationTypeQualifierResolver) ) val lazyJavaPackageFragmentProvider = LazyJavaPackageFragmentProvider(globalJavaResolverContext)