Add resolution for JSR 305 type qualifier nicknames

#KT-10942 In Progress
This commit is contained in:
Denis Zharkov
2017-06-06 12:45:14 +03:00
parent 79e14f88e4
commit 5c8f2a8ae8
9 changed files with 236 additions and 5 deletions
+13
View File
@@ -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; }
}
+6
View File
@@ -0,0 +1,6 @@
class B {
@MyNullable
String myNullable() {
return null;
}
}
@@ -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 {
}
@@ -651,7 +651,7 @@ public class KotlinTestUtils {
} }
@NotNull @NotNull
public static <M, F> List<F> createTestFiles(String testFileName, String expectedText, TestFileFactory<M, F> factory) { public static <M, F> List<F> createTestFiles(@Nullable String testFileName, String expectedText, TestFileFactory<M, F> factory) {
return createTestFiles(testFileName, expectedText, factory, false); return createTestFiles(testFileName, expectedText, factory, false);
} }
@@ -664,6 +664,7 @@ public class KotlinTestUtils {
Matcher matcher = FILE_OR_MODULE_PATTERN.matcher(expectedText); Matcher matcher = FILE_OR_MODULE_PATTERN.matcher(expectedText);
boolean hasModules = false; boolean hasModules = false;
if (!matcher.find()) { if (!matcher.find()) {
assert testFileName != null : "testFileName should not be null if no FILE directive defined";
// One file // One file
testFiles.add(factory.createFile(null, testFileName, expectedText, directives)); testFiles.add(factory.createFile(null, testFileName, expectedText, directives));
} }
@@ -19,10 +19,12 @@ package org.jetbrains.kotlin.checkers
import org.jetbrains.kotlin.test.MockLibraryUtil import org.jetbrains.kotlin.test.MockLibraryUtil
import java.io.File import java.io.File
val FOREIGN_ANNOTATIONS_SOURCES_PATH = "compiler/testData/foreignAnnotations/annotations"
abstract class AbstractForeignAnnotationsTest : AbstractDiagnosticsWithFullJdkTest() { abstract class AbstractForeignAnnotationsTest : AbstractDiagnosticsWithFullJdkTest() {
override fun getExtraClasspath(): List<File> = override fun getExtraClasspath(): List<File> =
listOf(MockLibraryUtil.compileJvmLibraryToJar(annotationsPath, "foreign-annotations")) listOf(MockLibraryUtil.compileJvmLibraryToJar(annotationsPath, "foreign-annotations"))
open protected val annotationsPath: String open protected val annotationsPath: String
get() = "compiler/testData/foreignAnnotations/annotations" get() = FOREIGN_ANNOTATIONS_SOURCES_PATH
} }
@@ -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<AnnotationTypeQualifierResolver, ClassDescriptor> {
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<JavaResolverComponents>().annotationTypeQualifierResolver
val aClass =
container
.get<ModuleDescriptor>()
.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)!!
}
@@ -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)
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PackagePartProvider import org.jetbrains.kotlin.descriptors.PackagePartProvider
import org.jetbrains.kotlin.descriptors.SupertypeLoopChecker import org.jetbrains.kotlin.descriptors.SupertypeLoopChecker
import org.jetbrains.kotlin.incremental.components.LookupTracker 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.JavaClassFinder
import org.jetbrains.kotlin.load.java.components.* import org.jetbrains.kotlin.load.java.components.*
import org.jetbrains.kotlin.load.java.lazy.types.JavaTypeResolver import org.jetbrains.kotlin.load.java.lazy.types.JavaTypeResolver
@@ -49,7 +50,8 @@ class JavaResolverComponents(
val supertypeLoopChecker: SupertypeLoopChecker, val supertypeLoopChecker: SupertypeLoopChecker,
val lookupTracker: LookupTracker, val lookupTracker: LookupTracker,
val module: ModuleDescriptor, val module: ModuleDescriptor,
val reflectionTypes: ReflectionTypes val reflectionTypes: ReflectionTypes,
val annotationTypeQualifierResolver: AnnotationTypeQualifierResolver
) { ) {
fun replace( fun replace(
javaResolverCache: JavaResolverCache = this.javaResolverCache javaResolverCache: JavaResolverCache = this.javaResolverCache
@@ -57,7 +59,9 @@ class JavaResolverComponents(
storageManager, finder, kotlinClassFinder, deserializedDescriptorResolver, storageManager, finder, kotlinClassFinder, deserializedDescriptorResolver,
externalAnnotationResolver, signaturePropagator, errorReporter, javaResolverCache, externalAnnotationResolver, signaturePropagator, errorReporter, javaResolverCache,
javaPropertyInitializerEvaluator, samConversionResolver, sourceElementFactory, javaPropertyInitializerEvaluator, samConversionResolver, sourceElementFactory,
moduleClassResolver, packageMapper, supertypeLoopChecker, lookupTracker, module, reflectionTypes) moduleClassResolver, packageMapper, supertypeLoopChecker, lookupTracker, module, reflectionTypes,
annotationTypeQualifierResolver
)
} }
class LazyJavaResolverContext( class LazyJavaResolverContext(
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.descriptors.NotFoundClasses
import org.jetbrains.kotlin.descriptors.SupertypeLoopChecker import org.jetbrains.kotlin.descriptors.SupertypeLoopChecker
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.incremental.components.LookupTracker 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.components.*
import org.jetbrains.kotlin.load.java.lazy.JavaResolverComponents import org.jetbrains.kotlin.load.java.lazy.JavaResolverComponents
import org.jetbrains.kotlin.load.java.lazy.LazyJavaPackageFragmentProvider import org.jetbrains.kotlin.load.java.lazy.LazyJavaPackageFragmentProvider
@@ -61,7 +62,8 @@ class RuntimeModuleData private constructor(
ExternalAnnotationResolver.EMPTY, SignaturePropagator.DO_NOTHING, RuntimeErrorReporter, javaResolverCache, ExternalAnnotationResolver.EMPTY, SignaturePropagator.DO_NOTHING, RuntimeErrorReporter, javaResolverCache,
JavaPropertyInitializerEvaluator.DoNothing, SamConversionResolver, RuntimeSourceElementFactory, singleModuleClassResolver, JavaPropertyInitializerEvaluator.DoNothing, SamConversionResolver, RuntimeSourceElementFactory, singleModuleClassResolver,
runtimePackagePartProvider, SupertypeLoopChecker.EMPTY, LookupTracker.DO_NOTHING, module, runtimePackagePartProvider, SupertypeLoopChecker.EMPTY, LookupTracker.DO_NOTHING, module,
ReflectionTypes(module, notFoundClasses) ReflectionTypes(module, notFoundClasses),
AnnotationTypeQualifierResolver(storageManager)
) )
val lazyJavaPackageFragmentProvider = LazyJavaPackageFragmentProvider(globalJavaResolverContext) val lazyJavaPackageFragmentProvider = LazyJavaPackageFragmentProvider(globalJavaResolverContext)