diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java index fe501f75592..8a13adfe2e9 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java @@ -35120,6 +35120,40 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT runTest("compiler/testData/codegen/box/reflection/annotations/onTypes/differentPositions.kt"); } } + + @Nested + @TestMetadata("compiler/testData/codegen/box/reflection/annotations/repeatable") + @TestDataPath("$PROJECT_ROOT") + public class Repeatable { + @Test + public void testAllFilesPresentInRepeatable() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/annotations/repeatable"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @Test + @TestMetadata("javaAnnotation.kt") + public void testJavaAnnotation() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/repeatable/javaAnnotation.kt"); + } + + @Test + @TestMetadata("jvmRepeatableKotlinAnnotation.kt") + public void testJvmRepeatableKotlinAnnotation() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/repeatable/jvmRepeatableKotlinAnnotation.kt"); + } + + @Test + @TestMetadata("kotlinAnnotation.kt") + public void testKotlinAnnotation() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/repeatable/kotlinAnnotation.kt"); + } + + @Test + @TestMetadata("nonRepeatedAnnotationWithItsContainer.kt") + public void testNonRepeatedAnnotationWithItsContainer() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/repeatable/nonRepeatedAnnotationWithItsContainer.kt"); + } + } } @Nested diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmCachedDeclarations.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmCachedDeclarations.kt index 409c0339fb6..23d717d4679 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmCachedDeclarations.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmCachedDeclarations.kt @@ -314,7 +314,8 @@ class JvmCachedDeclarations( it.isAnnotationWithEqualFqName(StandardNames.FqNames.retention) || it.isAnnotationWithEqualFqName(StandardNames.FqNames.target) } - .map { it.deepCopyWithSymbols(containerClass) } + .map { it.deepCopyWithSymbols(containerClass) } + + context.createJvmIrBuilder(containerClass.symbol).irCall(context.ir.symbols.repeatableContainer.constructors.single()) containerClass } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt index 00d6dc1a392..73cd6c2a631 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt @@ -947,6 +947,11 @@ class JvmSymbols( val runSuspendFunction: IrSimpleFunctionSymbol = kotlinCoroutinesJvmInternalRunSuspendKt.functionByName("runSuspend") + val repeatableContainer: IrClassSymbol = + createClass(FqName("kotlin.jvm.internal.RepeatableContainer"), ClassKind.ANNOTATION_CLASS).apply { + owner.addConstructor { isPrimary = true } + } + val javaAnnotations = JavaAnnotations() inner class JavaAnnotations { diff --git a/compiler/testData/codegen/box/reflection/annotations/findAnnotation.kt b/compiler/testData/codegen/box/reflection/annotations/findAnnotation.kt index b9a38636a3b..34528ab3ce6 100644 --- a/compiler/testData/codegen/box/reflection/annotations/findAnnotation.kt +++ b/compiler/testData/codegen/box/reflection/annotations/findAnnotation.kt @@ -1,9 +1,12 @@ // IGNORE_BACKEND: JS_IR // IGNORE_BACKEND: JS_IR_ES6 // IGNORE_BACKEND: JS, NATIVE +// !USE_EXPERIMENTAL: kotlin.ExperimentalStdlibApi // WITH_REFLECT import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.findAnnotations +import kotlin.test.assertEquals import kotlin.test.assertNull annotation class Yes(val value: String) @@ -19,5 +22,7 @@ fun box(): String { assertNull(Bar::class.findAnnotation()) assertNull(Bar::class.findAnnotation()) + assertEquals("OK", Foo::class.findAnnotations().single().value) + return Foo::class.findAnnotation()?.value ?: "Fail: no annotation" } diff --git a/compiler/testData/codegen/box/reflection/annotations/repeatable/javaAnnotation.kt b/compiler/testData/codegen/box/reflection/annotations/repeatable/javaAnnotation.kt new file mode 100644 index 00000000000..be46fd6e28a --- /dev/null +++ b/compiler/testData/codegen/box/reflection/annotations/repeatable/javaAnnotation.kt @@ -0,0 +1,44 @@ +// !LANGUAGE: +RepeatableAnnotations +// !USE_EXPERIMENTAL: kotlin.ExperimentalStdlibApi +// TARGET_BACKEND: JVM_IR +// JVM_TARGET: 1.8 +// FULL_JDK +// WITH_REFLECT +// FILE: box.kt + +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.findAnnotations +import kotlin.reflect.full.hasAnnotation + +@A("O") +@A("") +@A("K") +fun f() {} + +fun box(): String { + val element = ::f + if (element.hasAnnotation()) return "Fail hasAnnotation $element" + val find = element.findAnnotation() + if (find != null) return "Fail findAnnotation $element: $find" + + val all = (element.annotations.single() as A.Container).value.asList() + val findAll = element.findAnnotations() + if (all != findAll) throw AssertionError("Fail findAnnotations $element: $all != $findAll") + + return all.fold("") { acc, it -> acc + it.value } +} + +// FILE: A.java + +import java.lang.annotation.*; + +@Repeatable(A.Container.class) +@Retention(RetentionPolicy.RUNTIME) +public @interface A { + String value(); + + @Retention(RetentionPolicy.RUNTIME) + public @interface Container { + A[] value(); + } +} diff --git a/compiler/testData/codegen/box/reflection/annotations/repeatable/jvmRepeatableKotlinAnnotation.kt b/compiler/testData/codegen/box/reflection/annotations/repeatable/jvmRepeatableKotlinAnnotation.kt new file mode 100644 index 00000000000..3a3f079779f --- /dev/null +++ b/compiler/testData/codegen/box/reflection/annotations/repeatable/jvmRepeatableKotlinAnnotation.kt @@ -0,0 +1,36 @@ +// !LANGUAGE: +RepeatableAnnotations +// !USE_EXPERIMENTAL: kotlin.ExperimentalStdlibApi +// TARGET_BACKEND: JVM_IR +// JVM_TARGET: 1.8 +// FULL_JDK +// WITH_REFLECT + +// Android doesn't have @Repeatable, so findAnnotations can't unpack repeatable annotations. +// IGNORE_BACKEND: ANDROID + +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.findAnnotations +import kotlin.reflect.full.hasAnnotation + +@java.lang.annotation.Repeatable(A.Container::class) +annotation class A(val value: String) { + annotation class Container(val value: Array) +} + +@A("O") +@A("") +@A("K") +fun f() {} + +fun box(): String { + val element = ::f + if (element.hasAnnotation()) return "Fail hasAnnotation $element" + val find = element.findAnnotation() + if (find != null) return "Fail findAnnotation $element: $find" + + val all = (element.annotations.single() as A.Container).value.asList() + val findAll = element.findAnnotations() + if (all != findAll) throw AssertionError("Fail findAnnotations $element: $all != $findAll") + + return all.fold("") { acc, it -> acc + it.value } +} diff --git a/compiler/testData/codegen/box/reflection/annotations/repeatable/kotlinAnnotation.kt b/compiler/testData/codegen/box/reflection/annotations/repeatable/kotlinAnnotation.kt new file mode 100644 index 00000000000..f2f46dcf221 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/annotations/repeatable/kotlinAnnotation.kt @@ -0,0 +1,54 @@ +// !LANGUAGE: +RepeatableAnnotations +// !USE_EXPERIMENTAL: kotlin.ExperimentalStdlibApi +// TARGET_BACKEND: JVM_IR +// JVM_TARGET: 1.8 +// FULL_JDK +// WITH_REFLECT + +// Test failed to run to completion. Reason: 'Instrumentation run failed due to 'Native crash''. Check device logcat for details +// IGNORE_BACKEND: ANDROID + +import kotlin.annotation.AnnotationTarget.* +import kotlin.reflect.KAnnotatedElement +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.findAnnotations +import kotlin.reflect.full.hasAnnotation + +fun check(element: KAnnotatedElement) { + if (!element.hasAnnotation()) throw AssertionError("Fail hasAnnotation $element") + + val find = element.findAnnotation() + if (find == null || find.value != "O") throw AssertionError("Fail findAnnotation $element: $find") + + val all = element.annotations + val findAll = element.findAnnotations() + if (all != findAll) throw AssertionError("Fail findAnnotations $element: $all != $findAll") + + if (all.any { it !is A }) + throw AssertionError("Fail 1 $element: $all") + if (all.fold("") { acc, it -> acc + (it as A).value } != "OK") + throw AssertionError("Fail 2 $element: $all") +} + +@Repeatable +@Target(CLASS, FUNCTION, PROPERTY, TYPE) +annotation class A(val value: String) + +@A("O") @A("") @A("K") +fun f() {} + +@A("O") @A("") @A("") @A("K") +var p = 1 + +@A("O") @A("K") +class Z + +fun g(): @A("O") @A("K") @A("") Unit {} + +fun box(): String { + check(::f) + check(::p) + check(Z::class) + check(::g.returnType) + return "OK" +} diff --git a/compiler/testData/codegen/box/reflection/annotations/repeatable/nonRepeatedAnnotationWithItsContainer.kt b/compiler/testData/codegen/box/reflection/annotations/repeatable/nonRepeatedAnnotationWithItsContainer.kt new file mode 100644 index 00000000000..5acc0729ed7 --- /dev/null +++ b/compiler/testData/codegen/box/reflection/annotations/repeatable/nonRepeatedAnnotationWithItsContainer.kt @@ -0,0 +1,44 @@ +// !LANGUAGE: +RepeatableAnnotations +// !USE_EXPERIMENTAL: kotlin.ExperimentalStdlibApi +// TARGET_BACKEND: JVM_IR +// JVM_TARGET: 1.8 +// FULL_JDK +// WITH_REFLECT + +// Android doesn't have @Repeatable. +// IGNORE_BACKEND: ANDROID + +package test + +import kotlin.test.assertEquals +import kotlin.reflect.full.* + +@java.lang.annotation.Repeatable(As::class) +annotation class A(val value: Int) + +annotation class As(val value: Array) + +@A(1) +@As([A(2), A(3)]) +class Z + +@As([A(1), A(2)]) +@A(3) +class ZZ + +// JDK 9+ uses {} for array arguments instead of [], JDK 15+ doesn't render "value=" +fun Any?.render(): String = + toString().replace("value=", "").replace("{", "[").replace("}", "]") + +// Explicit container is not unwrapped. +fun box(): String { + assertEquals("[@test.A(1), @test.As([@test.A(2), @test.A(3)])]", Z::class.annotations.render()) + assertEquals("[@test.A(1)]", Z::class.findAnnotations().render()) + assertEquals("@test.A(1)", Z::class.findAnnotation().render()) + + assertEquals("[@test.As([@test.A(1), @test.A(2)]), @test.A(3)]", ZZ::class.annotations.render()) + assertEquals("[@test.A(3)]", ZZ::class.findAnnotations().render()) + assertEquals("@test.A(3)", ZZ::class.findAnnotation().render()) + + return "OK" +} diff --git a/compiler/testData/codegen/bytecodeListing/annotations/repeatable/kotlinAnnotation.txt b/compiler/testData/codegen/bytecodeListing/annotations/repeatable/kotlinAnnotation.txt index aca990eba40..bbf40690047 100644 --- a/compiler/testData/codegen/bytecodeListing/annotations/repeatable/kotlinAnnotation.txt +++ b/compiler/testData/codegen/bytecodeListing/annotations/repeatable/kotlinAnnotation.txt @@ -1,3 +1,4 @@ +@kotlin.jvm.internal.RepeatableContainer @java.lang.annotation.Retention(value=RUNTIME) @kotlin.Metadata public annotation class test/A$Container { diff --git a/compiler/testData/codegen/bytecodeListing/annotations/repeatable/kotlinSpecificTargets.txt b/compiler/testData/codegen/bytecodeListing/annotations/repeatable/kotlinSpecificTargets.txt index 3bb1befc085..cd11e711bd8 100644 --- a/compiler/testData/codegen/bytecodeListing/annotations/repeatable/kotlinSpecificTargets.txt +++ b/compiler/testData/codegen/bytecodeListing/annotations/repeatable/kotlinSpecificTargets.txt @@ -1,4 +1,5 @@ @kotlin.annotation.Target(allowedTargets=[FILE, TYPEALIAS]) +@kotlin.jvm.internal.RepeatableContainer @java.lang.annotation.Retention(value=RUNTIME) @java.lang.annotation.Target(value=[]) @kotlin.Metadata diff --git a/compiler/testData/codegen/bytecodeListing/annotations/repeatable/multipleRepeatableOrder.txt b/compiler/testData/codegen/bytecodeListing/annotations/repeatable/multipleRepeatableOrder.txt index fedc16c240f..c43bb61248d 100644 --- a/compiler/testData/codegen/bytecodeListing/annotations/repeatable/multipleRepeatableOrder.txt +++ b/compiler/testData/codegen/bytecodeListing/annotations/repeatable/multipleRepeatableOrder.txt @@ -1,3 +1,4 @@ +@kotlin.jvm.internal.RepeatableContainer @java.lang.annotation.Retention(value=RUNTIME) @kotlin.Metadata public annotation class test/A$Container { @@ -16,6 +17,7 @@ public annotation class test/A { public inner class test/A$Container } +@kotlin.jvm.internal.RepeatableContainer @java.lang.annotation.Retention(value=RUNTIME) @kotlin.Metadata public annotation class test/B$Container { @@ -34,6 +36,7 @@ public annotation class test/B { public inner class test/B$Container } +@kotlin.jvm.internal.RepeatableContainer @java.lang.annotation.Retention(value=RUNTIME) @kotlin.Metadata public annotation class test/C$Container { diff --git a/compiler/testData/codegen/bytecodeListing/annotations/repeatable/retentionAndTarget.txt b/compiler/testData/codegen/bytecodeListing/annotations/repeatable/retentionAndTarget.txt index e39c4014fff..595893c40e3 100644 --- a/compiler/testData/codegen/bytecodeListing/annotations/repeatable/retentionAndTarget.txt +++ b/compiler/testData/codegen/bytecodeListing/annotations/repeatable/retentionAndTarget.txt @@ -1,4 +1,5 @@ @kotlin.annotation.Retention(value=BINARY) +@kotlin.jvm.internal.RepeatableContainer @java.lang.annotation.Retention(value=CLASS) @kotlin.Metadata public annotation class RetentionBinary$Container { @@ -17,6 +18,7 @@ public annotation class RetentionBinary { public inner class RetentionBinary$Container } +@kotlin.jvm.internal.RepeatableContainer @java.lang.annotation.Retention(value=RUNTIME) @kotlin.Metadata public annotation class RetentionRuntime$Container { @@ -35,6 +37,7 @@ public annotation class RetentionRuntime { } @kotlin.annotation.Retention(value=SOURCE) +@kotlin.jvm.internal.RepeatableContainer @java.lang.annotation.Retention(value=SOURCE) @kotlin.Metadata public annotation class RetentionSource$Container { @@ -54,6 +57,7 @@ public annotation class RetentionSource { } @kotlin.annotation.Target(allowedTargets=[ANNOTATION_CLASS, TYPE]) +@kotlin.jvm.internal.RepeatableContainer @java.lang.annotation.Retention(value=RUNTIME) @java.lang.annotation.Target(value=[ANNOTATION_TYPE, TYPE_USE]) @kotlin.Metadata @@ -75,6 +79,7 @@ public annotation class TargetAnnotationClassAndTypeOnly { } @kotlin.annotation.Target(allowedTargets=[CLASS]) +@kotlin.jvm.internal.RepeatableContainer @java.lang.annotation.Retention(value=RUNTIME) @java.lang.annotation.Target(value=[TYPE]) @kotlin.Metadata @@ -96,6 +101,7 @@ public annotation class TargetClassOnly { } @kotlin.annotation.Target(allowedTargets=[]) +@kotlin.jvm.internal.RepeatableContainer @java.lang.annotation.Retention(value=RUNTIME) @java.lang.annotation.Target(value=[]) @kotlin.Metadata diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java index c33321c6f8c..f25770f9bf8 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java @@ -34988,6 +34988,16 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/reflection/annotations/onTypes/differentPositions.kt"); } } + + @Nested + @TestMetadata("compiler/testData/codegen/box/reflection/annotations/repeatable") + @TestDataPath("$PROJECT_ROOT") + public class Repeatable { + @Test + public void testAllFilesPresentInRepeatable() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/annotations/repeatable"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); + } + } } @Nested diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java index 4a11e0d2369..13e147b2447 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java @@ -35120,6 +35120,40 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/reflection/annotations/onTypes/differentPositions.kt"); } } + + @Nested + @TestMetadata("compiler/testData/codegen/box/reflection/annotations/repeatable") + @TestDataPath("$PROJECT_ROOT") + public class Repeatable { + @Test + public void testAllFilesPresentInRepeatable() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/annotations/repeatable"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @Test + @TestMetadata("javaAnnotation.kt") + public void testJavaAnnotation() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/repeatable/javaAnnotation.kt"); + } + + @Test + @TestMetadata("jvmRepeatableKotlinAnnotation.kt") + public void testJvmRepeatableKotlinAnnotation() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/repeatable/jvmRepeatableKotlinAnnotation.kt"); + } + + @Test + @TestMetadata("kotlinAnnotation.kt") + public void testKotlinAnnotation() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/repeatable/kotlinAnnotation.kt"); + } + + @Test + @TestMetadata("nonRepeatedAnnotationWithItsContainer.kt") + public void testNonRepeatedAnnotationWithItsContainer() throws Exception { + runTest("compiler/testData/codegen/box/reflection/annotations/repeatable/nonRepeatedAnnotationWithItsContainer.kt"); + } + } } @Nested diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index c3b279cc274..7ca4f4c1baf 100644 --- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -27624,6 +27624,19 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/reflection/annotations/onTypes/differentPositions.kt"); } } + + @TestMetadata("compiler/testData/codegen/box/reflection/annotations/repeatable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Repeatable extends AbstractLightAnalysisModeTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath); + } + + public void testAllFilesPresentInRepeatable() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/annotations/repeatable"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); + } + } } @TestMetadata("compiler/testData/codegen/box/reflection/builtins") diff --git a/core/reflection.jvm/src/kotlin/reflect/full/KAnnotatedElements.kt b/core/reflection.jvm/src/kotlin/reflect/full/KAnnotatedElements.kt index 5d519e4410b..cb42abb4842 100644 --- a/core/reflection.jvm/src/kotlin/reflect/full/KAnnotatedElements.kt +++ b/core/reflection.jvm/src/kotlin/reflect/full/KAnnotatedElements.kt @@ -7,7 +7,9 @@ package kotlin.reflect.full -import kotlin.reflect.* +import java.lang.reflect.Method +import kotlin.reflect.KAnnotatedElement +import kotlin.reflect.KClass /** * Returns an annotation of the given type on this element. @@ -25,3 +27,75 @@ inline fun KAnnotatedElement.findAnnotation(): T? = @WasExperimental(ExperimentalStdlibApi::class) inline fun KAnnotatedElement.hasAnnotation(): Boolean = findAnnotation() != null + +/** + * Returns all annotations of the given type on this element, including individually applied annotations + * as well as repeated annotations. + * + * In case the annotation is repeated, instances are extracted from the container annotation class similarly to how it happens + * in Java reflection ([java.lang.reflect.AnnotatedElement.getAnnotationsByType]). This is supported both for Kotlin-repeatable + * ([kotlin.annotation.Repeatable]) and Java-repeatable ([java.lang.annotation.Repeatable]) annotation classes. + */ +@SinceKotlin("1.5") +@ExperimentalStdlibApi +inline fun KAnnotatedElement.findAnnotations(): List = + findAnnotations(T::class) + +/** + * Returns all annotations of the given type on this element, including individually applied annotations + * as well as repeated annotations. + * + * In case the annotation is repeated, instances are extracted from the container annotation class similarly to how it happens + * in Java reflection ([java.lang.reflect.AnnotatedElement.getAnnotationsByType]). This is supported both for Kotlin-repeatable + * ([kotlin.annotation.Repeatable]) and Java-repeatable ([java.lang.annotation.Repeatable]) annotation classes. + */ +@Suppress("UNCHECKED_CAST") +@SinceKotlin("1.5") +@ExperimentalStdlibApi +fun KAnnotatedElement.findAnnotations(klass: KClass): List { + val filtered = annotations.filterIsInstance(klass.java) + if (filtered.isNotEmpty()) return filtered + + val containerClass = Java8RepeatableContainerLoader.loadRepeatableContainer(klass.java) + if (containerClass != null) { + val container = annotations.firstOrNull { it.annotationClass.java == containerClass } + if (container != null) { + // A repeatable annotation container must have a method "value" returning the array of repeated annotations. + val valueMethod = container::class.java.getMethod("value") + return (valueMethod(container) as Array).asList() + } + } + + return emptyList() +} + +@Suppress("UNCHECKED_CAST") +private object Java8RepeatableContainerLoader { + class Cache(val repeatableClass: Class?, val valueMethod: Method?) + + var cache: Cache? = null + + private fun buildCache(): Cache { + val repeatableClass = try { + Class.forName("java.lang.annotation.Repeatable") as Class + } catch (e: ClassNotFoundException) { + return Cache(null, null) + } + + return Cache(repeatableClass, repeatableClass.getMethod("value")) + } + + fun loadRepeatableContainer(klass: Class): Class? { + var cache = cache + if (cache == null) { + cache = buildCache() + this.cache = cache + } + + val repeatableClass = cache.repeatableClass ?: return null + val repeatable = klass.getAnnotation(repeatableClass) ?: return null + val valueMethod = cache.valueMethod ?: return null + + return valueMethod(repeatable) as Class + } +} 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 ddbdcd9dc9c..f654b0a8f29 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.descriptors.runtime.components.tryLoadClass import org.jetbrains.kotlin.descriptors.runtime.structure.ReflectJavaAnnotation import org.jetbrains.kotlin.descriptors.runtime.structure.ReflectJavaClass import org.jetbrains.kotlin.descriptors.runtime.structure.safeClassLoader +import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement import org.jetbrains.kotlin.metadata.ProtoBuf import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion @@ -49,6 +50,7 @@ import org.jetbrains.kotlin.serialization.deserialization.MemberDeserializer import java.lang.reflect.Type import kotlin.jvm.internal.FunctionReference import kotlin.jvm.internal.PropertyReference +import kotlin.jvm.internal.RepeatableContainer import kotlin.reflect.KType import kotlin.reflect.KVisibility import kotlin.reflect.full.IllegalCallableAccessException @@ -122,7 +124,22 @@ internal fun Annotated.computeAnnotations(): List = is RuntimeSourceElementFactory.RuntimeSourceElement -> (source.javaElement as? ReflectJavaAnnotation)?.annotation else -> it.toAnnotationInstance() } - } + }.unwrapRepeatableAnnotations() + +private fun List.unwrapRepeatableAnnotations(): List = + if (any { it.annotationClass.java.simpleName == JvmAbi.REPEATABLE_ANNOTATION_CONTAINER_NAME }) + flatMap { + val klass = it.annotationClass.java + if (klass.simpleName == JvmAbi.REPEATABLE_ANNOTATION_CONTAINER_NAME && + klass.getAnnotation(RepeatableContainer::class.java) != null + ) + @Suppress("UNCHECKED_CAST") + (klass.getDeclaredMethod("value").invoke(it) as Array).asList() + else + listOf(it) + } + else + this private fun AnnotationDescriptor.toAnnotationInstance(): Annotation? { @Suppress("UNCHECKED_CAST") diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java index 6c993065d70..00a46b25c84 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java @@ -24439,6 +24439,19 @@ public class IrJsCodegenBoxES6TestGenerated extends AbstractIrJsCodegenBoxES6Tes KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/annotations/onTypes"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR_ES6, true); } } + + @TestMetadata("compiler/testData/codegen/box/reflection/annotations/repeatable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Repeatable extends AbstractIrJsCodegenBoxES6Test { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR_ES6, testDataFilePath); + } + + public void testAllFilesPresentInRepeatable() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/annotations/repeatable"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR_ES6, true); + } + } } @TestMetadata("compiler/testData/codegen/box/reflection/builtins") diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java index c05c5d18bab..cd9e9551027 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrJsCodegenBoxTestGenerated.java @@ -23845,6 +23845,19 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/annotations/onTypes"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true); } } + + @TestMetadata("compiler/testData/codegen/box/reflection/annotations/repeatable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Repeatable extends AbstractIrJsCodegenBoxTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath); + } + + public void testAllFilesPresentInRepeatable() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/annotations/repeatable"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true); + } + } } @TestMetadata("compiler/testData/codegen/box/reflection/builtins") diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index 29a30a5ab00..01e239e6d13 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -23805,6 +23805,19 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/annotations/onTypes"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS, true); } } + + @TestMetadata("compiler/testData/codegen/box/reflection/annotations/repeatable") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Repeatable extends AbstractJsCodegenBoxTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath); + } + + public void testAllFilesPresentInRepeatable() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/reflection/annotations/repeatable"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS, true); + } + } } @TestMetadata("compiler/testData/codegen/box/reflection/builtins") diff --git a/libraries/stdlib/jvm/runtime/kotlin/jvm/internal/RepeatableContainer.java b/libraries/stdlib/jvm/runtime/kotlin/jvm/internal/RepeatableContainer.java new file mode 100644 index 00000000000..57c3ce7fb54 --- /dev/null +++ b/libraries/stdlib/jvm/runtime/kotlin/jvm/internal/RepeatableContainer.java @@ -0,0 +1,17 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.jvm.internal; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.ANNOTATION_TYPE) +// @SinceKotlin(version = "1.6") +public @interface RepeatableContainer { +} diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-reflect.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-reflect.txt index 5d7fdba1d33..8f8704af09f 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-reflect.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-reflect.txt @@ -6,6 +6,10 @@ public final class kotlin/reflect/full/IllegalPropertyDelegateAccessException : public fun (Ljava/lang/IllegalAccessException;)V } +public final class kotlin/reflect/full/KAnnotatedElements { + public static final fun findAnnotations (Lkotlin/reflect/KAnnotatedElement;Lkotlin/reflect/KClass;)Ljava/util/List; +} + public final class kotlin/reflect/full/KCallables { public static final fun callSuspend (Lkotlin/reflect/KCallable;[Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun callSuspendBy (Lkotlin/reflect/KCallable;Ljava/util/Map;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 4b2d2c17403..99d008a2e7a 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 @@ -4037,6 +4037,9 @@ public class kotlin/jvm/internal/ReflectionFactory { public fun typeParameter (Ljava/lang/Object;Ljava/lang/String;Lkotlin/reflect/KVariance;Z)Lkotlin/reflect/KTypeParameter; } +public abstract interface annotation class kotlin/jvm/internal/RepeatableContainer : java/lang/annotation/Annotation { +} + public final class kotlin/jvm/internal/ShortCompanionObject { public static final field INSTANCE Lkotlin/jvm/internal/ShortCompanionObject; public static final field MAX_VALUE S