From ceb909d26117a80943c6a5b32fc28c73a0452d32 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Fri, 3 Aug 2018 18:02:23 +0200 Subject: [PATCH] Inherit KType from KAnnotatedElement, implement KType.annotations #KT-16795 Fixed --- .../onTypes/differentArgumentTypes.kt | 75 +++++++++++++++++++ .../annotations/onTypes/differentPositions.kt | 49 ++++++++++++ .../codegen/BlackBoxCodegenTestGenerated.java | 23 ++++++ .../LightAnalysisModeTestGenerated.java | 23 ++++++ .../ir/IrBlackBoxCodegenTestGenerated.java | 23 ++++++ core/builtins/src/kotlin/reflect/KType.kt | 2 +- .../internal/AnnotationConstructorCaller.kt | 13 +++- .../kotlin/reflect/jvm/internal/KTypeImpl.kt | 3 + .../src/kotlin/reflect/jvm/internal/Util.java | 13 ++++ .../src/kotlin/reflect/jvm/internal/util.kt | 33 +++++++- .../IrJsCodegenBoxTestGenerated.java | 13 ++++ .../semantics/JsCodegenBoxTestGenerated.java | 13 ++++ .../reference-public-api/kotlin-runtime.txt | 2 +- .../kotlin-stdlib-runtime-merged.txt | 2 +- 14 files changed, 280 insertions(+), 7 deletions(-) create mode 100644 compiler/testData/codegen/box/reflection/annotations/onTypes/differentArgumentTypes.kt create mode 100644 compiler/testData/codegen/box/reflection/annotations/onTypes/differentPositions.kt create mode 100644 core/reflection.jvm/src/kotlin/reflect/jvm/internal/Util.java diff --git a/compiler/testData/codegen/box/reflection/annotations/onTypes/differentArgumentTypes.kt b/compiler/testData/codegen/box/reflection/annotations/onTypes/differentArgumentTypes.kt new file mode 100644 index 00000000000..fd50a2847b0 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/annotations/onTypes/differentArgumentTypes.kt @@ -0,0 +1,75 @@ +// IGNORE_BACKEND: JVM_IR +// TARGET_BACKEND: JVM +// WITH_REFLECT + +import kotlin.reflect.KClass +import kotlin.test.assertEquals + +annotation class Nested(val value: String) + +@Target(AnnotationTarget.TYPE) +annotation class Anno( + val b: Byte, + val c: Char, + val d: Double, + val f: Float, + val i: Int, + val j: Long, + val s: Short, + val z: Boolean, + val ba: ByteArray, + val ca: CharArray, + val da: DoubleArray, + val fa: FloatArray, + val ia: IntArray, + val ja: LongArray, + val sa: ShortArray, + val za: BooleanArray, + val str: String, + val k: KClass<*>, + val e: AnnotationTarget, + val a: Nested, + val stra: Array, + // val ka: Array>, // Arrays of class literals are not supported yet in AnnotationDeserializer + val ea: Array, + val aa: Array +) + +fun f(): @Anno( + 1.toByte(), + 'x', + 3.14, + -2.72f, + 42424242, + 239239239239239L, + 42.toShort(), + true, + [(-1).toByte()], + ['y'], + [-3.14159], + [2.7218f], + [424242], + [239239239239L], + [(-43).toShort()], + [false, true], + "lol", + Number::class, + AnnotationTarget.EXPRESSION, + Nested("1"), + ["lmao"], + // [Double::class, Unit::class], + [AnnotationTarget.TYPEALIAS, AnnotationTarget.FIELD], + [Nested("2"), Nested("3")] +) Unit {} + +fun box(): String { + assertEquals( + "[@Anno(b=1, c=x, d=3.14, f=-2.72, i=42424242, j=239239239239239, s=42, z=true, " + + "ba=[-1], ca=[y], da=[-3.14159], fa=[2.7218], ia=[424242], ja=[239239239239], sa=[-43], za=[false, true], " + + "str=lol, k=class java.lang.Number, e=EXPRESSION, a=@Nested(value=1), " + + "stra=[lmao], ea=[TYPEALIAS, FIELD], aa=[@Nested(value=2), @Nested(value=3)])]", + ::f.returnType.annotations.toString() + ) + + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/annotations/onTypes/differentPositions.kt b/compiler/testData/codegen/box/reflection/annotations/onTypes/differentPositions.kt new file mode 100644 index 00000000000..f8549fe39c4 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/annotations/onTypes/differentPositions.kt @@ -0,0 +1,49 @@ +// TARGET_BACKEND: JVM +// IGNORE_BACKEND: JVM_IR +// WITH_REFLECT + +import kotlin.reflect.KCallable +import kotlin.reflect.KType + +@Target(AnnotationTarget.TYPE) +annotation class InRange(val from: Int, val to: Int) + +val propertyType: @InRange(1, 10) Int = 5 + +fun functionType(): @InRange(1, 10) Int = 5 + +fun parameterType(param: @InRange(1, 10) Int) {} + +fun (@InRange(1, 10) Int).receiverType() {} + +abstract class Supertype : @InRange(1, 10) Number() { + fun typeParameterBound(t: T): T = t + + inner class Inner +} + +fun typeArgument(): List<@InRange(1, 10) Int>? = null + +// ------- + +private fun check(what: String, type: KType) { + val annotations = type.annotations + if (annotations.isEmpty()) throw AssertionError("No annotations found on $what") + val a = annotations.single() as InRange + if (a.from != 1 || a.to != 10) throw AssertionError("Incorrect from/to values: ${a.from} ${a.to}") +} + +fun box(): String { + check("property return type", ::propertyType.returnType) + check("function return type", ::functionType.returnType) + check("parameter type", ::parameterType.parameters.single().type) + check("receiver type", Int::receiverType.parameters.single().type) + check("supertype", Supertype::class.supertypes.single()) + + val typeParameterBound = Supertype::class.members.single { it.name == "typeParameterBound" } as KCallable + check("type parameter bound", typeParameterBound.typeParameters.single().upperBounds.single()) + + check("type argument", ::typeArgument.returnType.arguments.single().type!!) + + return "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index d7615784943..af176b186e1 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -17370,6 +17370,29 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { public void testSimpleValAnnotation() throws Exception { runTest("compiler/testData/codegen/box/reflection/annotations/simpleValAnnotation.kt"); } + + @TestMetadata("compiler/testData/codegen/box/reflection/annotations/onTypes") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class OnTypes extends AbstractBlackBoxCodegenTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInOnTypes() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/annotations/onTypes"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("differentArgumentTypes.kt") + public void testDifferentArgumentTypes() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/onTypes/differentArgumentTypes.kt"); + } + + @TestMetadata("differentPositions.kt") + public void testDifferentPositions() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/onTypes/differentPositions.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/reflection/builtins") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 752a3ca66c3..1c79cbee92e 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -17370,6 +17370,29 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes public void testSimpleValAnnotation() throws Exception { runTest("compiler/testData/codegen/box/reflection/annotations/simpleValAnnotation.kt"); } + + @TestMetadata("compiler/testData/codegen/box/reflection/annotations/onTypes") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class OnTypes extends AbstractLightAnalysisModeTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInOnTypes() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/annotations/onTypes"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("differentArgumentTypes.kt") + public void testDifferentArgumentTypes() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/onTypes/differentArgumentTypes.kt"); + } + + @TestMetadata("differentPositions.kt") + public void testDifferentPositions() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/onTypes/differentPositions.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/reflection/builtins") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index b5d840b5a12..ebaf00160b8 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -17370,6 +17370,29 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes public void testSimpleValAnnotation() throws Exception { runTest("compiler/testData/codegen/box/reflection/annotations/simpleValAnnotation.kt"); } + + @TestMetadata("compiler/testData/codegen/box/reflection/annotations/onTypes") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class OnTypes extends AbstractIrBlackBoxCodegenTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath); + } + + public void testAllFilesPresentInOnTypes() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/annotations/onTypes"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true); + } + + @TestMetadata("differentArgumentTypes.kt") + public void testDifferentArgumentTypes() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/onTypes/differentArgumentTypes.kt"); + } + + @TestMetadata("differentPositions.kt") + public void testDifferentPositions() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/onTypes/differentPositions.kt"); + } + } } @TestMetadata("compiler/testData/codegen/box/reflection/builtins") diff --git a/core/builtins/src/kotlin/reflect/KType.kt b/core/builtins/src/kotlin/reflect/KType.kt index 2ad64a7ed47..cfe5e0feaf5 100644 --- a/core/builtins/src/kotlin/reflect/KType.kt +++ b/core/builtins/src/kotlin/reflect/KType.kt @@ -20,7 +20,7 @@ package kotlin.reflect * Represents a type. Type is usually either a class with optional type arguments, * or a type parameter of some declaration, plus nullability. */ -public interface KType { +public interface KType : KAnnotatedElement { /** * The declaration of the classifier used in this type. * For example, in the type `List` the classifier would be the [KClass] instance for [List]. diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/AnnotationConstructorCaller.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/AnnotationConstructorCaller.kt index 1b57df815a1..ca7000c888a 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/AnnotationConstructorCaller.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/AnnotationConstructorCaller.kt @@ -61,7 +61,7 @@ internal class AnnotationConstructorCaller( value ?: throwIllegalArgumentType(index, parameterNames[index], erasedParameterTypes[index]) } - return createAnnotationInstance(jClass, methods, parameterNames.zip(values).toMap()) + return createAnnotationInstance(jClass, parameterNames.zip(values).toMap(), methods) } } @@ -101,7 +101,11 @@ private fun throwIllegalArgumentType(index: Int, name: String, expectedJvmType: throw IllegalArgumentException("Argument #$index $name is not of the required type $typeString") } -private fun createAnnotationInstance(annotationClass: Class<*>, methods: List, values: Map): Any { +internal fun createAnnotationInstance( + annotationClass: Class, + values: Map, + methods: List = values.keys.map { name -> annotationClass.getDeclaredMethod(name) } +): T { fun equals(other: Any?): Boolean = (other as? Annotation)?.annotationClass?.java == annotationClass && methods.all { method -> @@ -163,7 +167,7 @@ private fun createAnnotationInstance(annotationClass: Class<*>, methods: List + val result = Proxy.newProxyInstance(annotationClass.classLoader, arrayOf(annotationClass)) { _, method, args -> val name = method.name when (name) { "annotationType" -> annotationClass @@ -176,4 +180,7 @@ private fun createAnnotationInstance(annotationClass: Class<*>, methods: List + get() = type.computeAnnotations() + override fun equals(other: Any?) = other is KTypeImpl && type == other.type diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/Util.java b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/Util.java new file mode 100644 index 00000000000..cc7750361da --- /dev/null +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/Util.java @@ -0,0 +1,13 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package kotlin.reflect.jvm.internal; + +/* package */ class Util { + public static Object getEnumConstantByName(Class> enumClass, String name) { + // This is a workaround for KT-5191. Enum#valueOf cannot be called in Kotlin + return Enum.valueOf((Class) enumClass, name); + } +} diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt index 77d9eb3f7dc..ef53b5757e0 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt @@ -19,6 +19,7 @@ package kotlin.reflect.jvm.internal import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotated +import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.annotations.isEffectivelyInlineOnly import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement @@ -30,6 +31,8 @@ import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.protobuf.MessageLite import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.constants.* +import org.jetbrains.kotlin.resolve.descriptorUtil.annotationClass import org.jetbrains.kotlin.resolve.descriptorUtil.classId import org.jetbrains.kotlin.serialization.deserialization.DeserializationContext import org.jetbrains.kotlin.serialization.deserialization.MemberDeserializer @@ -102,10 +105,38 @@ internal fun Annotated.computeAnnotations(): List = when (source) { is ReflectAnnotationSource -> source.annotation is RuntimeSourceElementFactory.RuntimeSourceElement -> (source.javaElement as? ReflectJavaAnnotation)?.annotation - else -> null + else -> it.toAnnotationInstance() } } +private fun AnnotationDescriptor.toAnnotationInstance(): Annotation? { + @Suppress("UNCHECKED_CAST") + val annotationClass = annotationClass?.toJavaClass() as? Class ?: return null + + return createAnnotationInstance( + annotationClass, + allValueArguments.entries + .mapNotNull { (name, value) -> value.toRuntimeValue(annotationClass.classLoader)?.let(name.asString()::to) } + .toMap() + ) +} + +// TODO: consider throwing exceptions such as AnnotationFormatError/AnnotationTypeMismatchException if a value of unexpected type is found +private fun ConstantValue<*>.toRuntimeValue(classLoader: ClassLoader): Any? = when (this) { + is AnnotationValue -> value.toAnnotationInstance() + is ArrayValue -> value.map { it.toRuntimeValue(classLoader) }.toTypedArray() + is EnumValue -> { + val (enumClassId, entryName) = value + loadClass(classLoader, enumClassId.packageFqName.asString(), enumClassId.relativeClassName.asString())?.let { enumClass -> + @Suppress("UNCHECKED_CAST") + Util.getEnumConstantByName(enumClass as Class>, entryName.asString()) + } + } + is KClassValue -> (value.constructor.declarationDescriptor as? ClassDescriptor)?.toJavaClass() + is ErrorValue, is NullValue -> null + else -> value // Primitives and strings +} + // TODO: wrap other exceptions internal inline fun reflectionCall(block: () -> R): R = try { diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java index 3678383ab4f..c06329c705c 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java @@ -15525,6 +15525,19 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest { public void testSimpleValAnnotation() throws Exception { runTest("compiler/testData/codegen/box/reflection/annotations/simpleValAnnotation.kt"); } + + @TestMetadata("compiler/testData/codegen/box/reflection/annotations/onTypes") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class OnTypes extends AbstractIrJsCodegenBoxTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInOnTypes() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/annotations/onTypes"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS_IR, true); + } + } } @TestMetadata("compiler/testData/codegen/box/reflection/builtins") diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index 71d5b45bf5e..d9148983f1b 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -16585,6 +16585,19 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { public void testSimpleValAnnotation() throws Exception { runTest("compiler/testData/codegen/box/reflection/annotations/simpleValAnnotation.kt"); } + + @TestMetadata("compiler/testData/codegen/box/reflection/annotations/onTypes") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class OnTypes extends AbstractJsCodegenBoxTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInOnTypes() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/annotations/onTypes"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true); + } + } } @TestMetadata("compiler/testData/codegen/box/reflection/builtins") diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-runtime.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-runtime.txt index 840f1ac3abf..83a47f2d03f 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-runtime.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-runtime.txt @@ -1293,7 +1293,7 @@ public abstract interface class kotlin/reflect/KProperty2 : kotlin/jvm/functions public abstract interface class kotlin/reflect/KProperty2$Getter : kotlin/jvm/functions/Function2, kotlin/reflect/KProperty$Getter { } -public abstract interface class kotlin/reflect/KType { +public abstract interface class kotlin/reflect/KType : kotlin/reflect/KAnnotatedElement { public abstract fun getArguments ()Ljava/util/List; public abstract fun getClassifier ()Lkotlin/reflect/KClassifier; public abstract fun isMarkedNullable ()Z diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 41310a36cea..76a6ec47493 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -4274,7 +4274,7 @@ public abstract interface class kotlin/reflect/KProperty2 : kotlin/jvm/functions public abstract interface class kotlin/reflect/KProperty2$Getter : kotlin/jvm/functions/Function2, kotlin/reflect/KProperty$Getter { } -public abstract interface class kotlin/reflect/KType { +public abstract interface class kotlin/reflect/KType : kotlin/reflect/KAnnotatedElement { public abstract fun getArguments ()Ljava/util/List; public abstract fun getClassifier ()Lkotlin/reflect/KClassifier; public abstract fun isMarkedNullable ()Z