diff --git a/compiler/testData/typeQualifierNickname/A.java b/compiler/testData/typeQualifierNickname/A.java new file mode 100644 index 00000000000..a583830ba05 --- /dev/null +++ b/compiler/testData/typeQualifierNickname/A.java @@ -0,0 +1,13 @@ +class A { + @javax.annotation.Nullable + String nullable() { return null; } + + @javax.annotation.CheckForNull + String checkForNull() { return null; } + + @javax.annotation.Nonnull + String nonNull() { return null; } + + @javax.annotation.Nonnull(when = javax.annotation.meta.When.ALWAYS) + String nonNullExplicitArgument() { return null; } +} diff --git a/compiler/testData/typeQualifierNickname/B.java b/compiler/testData/typeQualifierNickname/B.java new file mode 100644 index 00000000000..a1613bef86d --- /dev/null +++ b/compiler/testData/typeQualifierNickname/B.java @@ -0,0 +1,6 @@ +class B { + @MyNullable + String myNullable() { + return null; + } +} diff --git a/compiler/testData/typeQualifierNickname/MyNullable.java b/compiler/testData/typeQualifierNickname/MyNullable.java new file mode 100644 index 00000000000..2aabdbea88a --- /dev/null +++ b/compiler/testData/typeQualifierNickname/MyNullable.java @@ -0,0 +1,7 @@ +@java.lang.annotation.Documented +@javax.annotation.meta.TypeQualifierNickname +@javax.annotation.CheckForNull +@java.lang.annotation.Target({ElementType.METHOD, ElementType.PARAMETER}) +@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) +public @interface MyNullable { +} diff --git a/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java b/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java index c840a8c62ec..0908e531e92 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java +++ b/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java @@ -651,7 +651,7 @@ public class KotlinTestUtils { } @NotNull - public static List createTestFiles(String testFileName, String expectedText, TestFileFactory factory) { + public static List createTestFiles(@Nullable String testFileName, String expectedText, TestFileFactory factory) { return createTestFiles(testFileName, expectedText, factory, false); } @@ -664,6 +664,7 @@ public class KotlinTestUtils { Matcher matcher = FILE_OR_MODULE_PATTERN.matcher(expectedText); boolean hasModules = false; if (!matcher.find()) { + assert testFileName != null : "testFileName should not be null if no FILE directive defined"; // One file testFiles.add(factory.createFile(null, testFileName, expectedText, directives)); } diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/AbstractForeignAnnotationsTest.kt b/compiler/tests/org/jetbrains/kotlin/checkers/AbstractForeignAnnotationsTest.kt index a19336eeed1..1beb2db61d2 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/AbstractForeignAnnotationsTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/checkers/AbstractForeignAnnotationsTest.kt @@ -19,10 +19,12 @@ package org.jetbrains.kotlin.checkers import org.jetbrains.kotlin.test.MockLibraryUtil import java.io.File +val FOREIGN_ANNOTATIONS_SOURCES_PATH = "compiler/testData/foreignAnnotations/annotations" + abstract class AbstractForeignAnnotationsTest : AbstractDiagnosticsWithFullJdkTest() { override fun getExtraClasspath(): List = listOf(MockLibraryUtil.compileJvmLibraryToJar(annotationsPath, "foreign-annotations")) open protected val annotationsPath: String - get() = "compiler/testData/foreignAnnotations/annotations" + get() = FOREIGN_ANNOTATIONS_SOURCES_PATH } diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/TypeQualifierAnnotationResolverTest.kt b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/TypeQualifierAnnotationResolverTest.kt new file mode 100644 index 00000000000..04c7cc98459 --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/TypeQualifierAnnotationResolverTest.kt @@ -0,0 +1,141 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.jvm.compiler + +import org.jetbrains.kotlin.checkers.FOREIGN_ANNOTATIONS_SOURCES_PATH +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.container.get +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.resolveClassByFqName +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.load.java.AnnotationTypeQualifierResolver +import org.jetbrains.kotlin.load.java.lazy.JavaResolverComponents +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.renderer.AnnotationArgumentsRenderingPolicy +import org.jetbrains.kotlin.renderer.DescriptorRenderer +import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil +import org.jetbrains.kotlin.test.ConfigurationKind +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.test.MockLibraryUtil +import org.jetbrains.kotlin.test.TestJdkKind +import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase +import java.io.File + +class TypeQualifierAnnotationResolverTest : KtUsefulTestCase() { + companion object { + private val TEST_DATA_PATH = "compiler/testData/typeQualifierNickname/" + } + + fun testBasicJSRNullabilityAnnotations() { + val (typeQualifierResolver, aClass) = buildTypeQualifierResolverAndFindClass("A") + + assertMethodHasUnwrappedAnnotation( + aClass, typeQualifierResolver, + "nullable", + "@javax.annotation.Nonnull(when = When.UNKNOWN)" + ) + + assertMethodHasUnwrappedAnnotation( + aClass, typeQualifierResolver, + "checkForNull", + "@javax.annotation.Nonnull(when = When.MAYBE)" + ) + + assertMethodHasUnwrappedAnnotation( + aClass, typeQualifierResolver, + "nonNull", + "@javax.annotation.Nonnull()" + ) + + assertMethodHasUnwrappedAnnotation( + aClass, typeQualifierResolver, + "nonNullExplicitArgument", + "@javax.annotation.Nonnull(when = When.ALWAYS)" + ) + } + + fun testCustomNullabilityAnnotation() { + val (typeQualifierResolver, aClass) = buildTypeQualifierResolverAndFindClass("B") + + assertMethodHasUnwrappedAnnotation( + aClass, typeQualifierResolver, + "myNullable", + "@javax.annotation.Nonnull(when = When.MAYBE)" + ) + } + + private fun buildTypeQualifierResolverAndFindClass(className: String): Pair { + val environment = + KotlinCoreEnvironment.createForTests( + myTestRootDisposable, + KotlinTestUtils.newConfiguration( + ConfigurationKind.ALL, TestJdkKind.FULL_JDK, + listOf( + KotlinTestUtils.getAnnotationsJar(), + MockLibraryUtil.compileJavaFilesLibraryToJar( + FOREIGN_ANNOTATIONS_SOURCES_PATH, + "foreign-annotations" + ) + ), + listOf(File(TEST_DATA_PATH)) + + ), + EnvironmentConfigFiles.JVM_CONFIG_FILES + ) + + val container = JvmResolveUtil.createContainer(environment) + + val typeQualifierResolver = container.get().annotationTypeQualifierResolver + + val aClass = + container + .get() + .resolveClassByFqName(FqName(className), NoLookupLocation.FROM_TEST)!! + + + return typeQualifierResolver to aClass + } + + private fun assertMethodHasUnwrappedAnnotation( + aClass: ClassDescriptor, + typeQualifierResolver: AnnotationTypeQualifierResolver, + methodName: String, + annotationText: String + ) { + assertEquals( + annotationText, + DescriptorRenderer.withOptions { + annotationArgumentsRenderingPolicy = AnnotationArgumentsRenderingPolicy.ALWAYS_PARENTHESIZED + }.renderAnnotation( + aClass.findSingleTypeQualifierAnnotationOnMethod(methodName, typeQualifierResolver) + ) + ) + } + + private fun ClassDescriptor.findSingleTypeQualifierAnnotationOnMethod( + name: String, + typeQualifierResolver: AnnotationTypeQualifierResolver + ) = unsubstitutedMemberScope + .getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_TEST) + .single() + .annotations.single() + .let(typeQualifierResolver::resolveTypeQualifierAnnotation) + .also(::assertNotNull)!! +} 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 new file mode 100644 index 00000000000..d449c8be01e --- /dev/null +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/AnnotationTypeQualifierResolver.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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.name.FqName +import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass +import org.jetbrains.kotlin.storage.StorageManager +import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult + +private val TYPE_QUALIFIER_NICKNAME_FQNAME = FqName("javax.annotation.meta.TypeQualifierNickname") +private val TYPE_QUALIFIER_FQNAME = FqName("javax.annotation.meta.TypeQualifier") + +class AnnotationTypeQualifierResolver(storageManager: StorageManager) { + private val resolvedNicknames = + storageManager.createMemoizedFunctionWithNullableValues(this::computeTypeQualifierNickname) + + private fun computeTypeQualifierNickname(classDescriptor: ClassDescriptor): AnnotationDescriptor? { + if (!classDescriptor.annotations.hasAnnotation(TYPE_QUALIFIER_NICKNAME_FQNAME)) return null + + return classDescriptor.annotations.firstNotNullResult(this::resolveTypeQualifierAnnotation) + } + + private fun resolveTypeQualifierNickname(classDescriptor: ClassDescriptor): AnnotationDescriptor? { + if (classDescriptor.kind != ClassKind.ANNOTATION_CLASS) return null + + return resolvedNicknames(classDescriptor) + } + + fun resolveTypeQualifierAnnotation(annotationDescriptor: AnnotationDescriptor): AnnotationDescriptor? { + val annotationClass = annotationDescriptor.annotationClass ?: return null + if (annotationClass.isAnnotatedWithTypeQualifier) return annotationDescriptor + + return resolveTypeQualifierNickname(annotationClass) + } +} + +private val ClassDescriptor.isAnnotatedWithTypeQualifier: Boolean + get() = annotations.hasAnnotation(TYPE_QUALIFIER_FQNAME) diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/context.kt b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/context.kt index 4f683b8e589..8ffc75aeee2 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/context.kt +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/lazy/context.kt @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.PackagePartProvider import org.jetbrains.kotlin.descriptors.SupertypeLoopChecker import org.jetbrains.kotlin.incremental.components.LookupTracker +import org.jetbrains.kotlin.load.java.AnnotationTypeQualifierResolver import org.jetbrains.kotlin.load.java.JavaClassFinder import org.jetbrains.kotlin.load.java.components.* import org.jetbrains.kotlin.load.java.lazy.types.JavaTypeResolver @@ -49,7 +50,8 @@ class JavaResolverComponents( val supertypeLoopChecker: SupertypeLoopChecker, val lookupTracker: LookupTracker, val module: ModuleDescriptor, - val reflectionTypes: ReflectionTypes + val reflectionTypes: ReflectionTypes, + val annotationTypeQualifierResolver: AnnotationTypeQualifierResolver ) { fun replace( javaResolverCache: JavaResolverCache = this.javaResolverCache @@ -57,7 +59,9 @@ class JavaResolverComponents( storageManager, finder, kotlinClassFinder, deserializedDescriptorResolver, externalAnnotationResolver, signaturePropagator, errorReporter, javaResolverCache, javaPropertyInitializerEvaluator, samConversionResolver, sourceElementFactory, - moduleClassResolver, packageMapper, supertypeLoopChecker, lookupTracker, module, reflectionTypes) + moduleClassResolver, packageMapper, supertypeLoopChecker, lookupTracker, module, reflectionTypes, + annotationTypeQualifierResolver + ) } class LazyJavaResolverContext( 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 aade8def6fa..082416b6b96 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 @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.descriptors.NotFoundClasses import org.jetbrains.kotlin.descriptors.SupertypeLoopChecker import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl import org.jetbrains.kotlin.incremental.components.LookupTracker +import org.jetbrains.kotlin.load.java.AnnotationTypeQualifierResolver import org.jetbrains.kotlin.load.java.components.* import org.jetbrains.kotlin.load.java.lazy.JavaResolverComponents import org.jetbrains.kotlin.load.java.lazy.LazyJavaPackageFragmentProvider @@ -61,7 +62,8 @@ class RuntimeModuleData private constructor( ExternalAnnotationResolver.EMPTY, SignaturePropagator.DO_NOTHING, RuntimeErrorReporter, javaResolverCache, JavaPropertyInitializerEvaluator.DoNothing, SamConversionResolver, RuntimeSourceElementFactory, singleModuleClassResolver, runtimePackagePartProvider, SupertypeLoopChecker.EMPTY, LookupTracker.DO_NOTHING, module, - ReflectionTypes(module, notFoundClasses) + ReflectionTypes(module, notFoundClasses), + AnnotationTypeQualifierResolver(storageManager) ) val lazyJavaPackageFragmentProvider = LazyJavaPackageFragmentProvider(globalJavaResolverContext)