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
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);
}
@@ -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));
}
@@ -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<File> =
listOf(MockLibraryUtil.compileJvmLibraryToJar(annotationsPath, "foreign-annotations"))
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.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(
@@ -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)