Support new repeatable annotations in kotlin-reflect
- Unwrap Kotlin-repeatable annotations (with implicit container) - Introduce `KAnnotatedElement.findAnnotations` to find instances of repeated annotations #KT-12794
This commit is contained in:
+34
@@ -35120,6 +35120,40 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
|
|||||||
runTest("compiler/testData/codegen/box/reflection/annotations/onTypes/differentPositions.kt");
|
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
|
@Nested
|
||||||
|
|||||||
+2
-1
@@ -314,7 +314,8 @@ class JvmCachedDeclarations(
|
|||||||
it.isAnnotationWithEqualFqName(StandardNames.FqNames.retention) ||
|
it.isAnnotationWithEqualFqName(StandardNames.FqNames.retention) ||
|
||||||
it.isAnnotationWithEqualFqName(StandardNames.FqNames.target)
|
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
|
containerClass
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -947,6 +947,11 @@ class JvmSymbols(
|
|||||||
val runSuspendFunction: IrSimpleFunctionSymbol =
|
val runSuspendFunction: IrSimpleFunctionSymbol =
|
||||||
kotlinCoroutinesJvmInternalRunSuspendKt.functionByName("runSuspend")
|
kotlinCoroutinesJvmInternalRunSuspendKt.functionByName("runSuspend")
|
||||||
|
|
||||||
|
val repeatableContainer: IrClassSymbol =
|
||||||
|
createClass(FqName("kotlin.jvm.internal.RepeatableContainer"), ClassKind.ANNOTATION_CLASS).apply {
|
||||||
|
owner.addConstructor { isPrimary = true }
|
||||||
|
}
|
||||||
|
|
||||||
val javaAnnotations = JavaAnnotations()
|
val javaAnnotations = JavaAnnotations()
|
||||||
|
|
||||||
inner class JavaAnnotations {
|
inner class JavaAnnotations {
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
// IGNORE_BACKEND: JS_IR
|
// IGNORE_BACKEND: JS_IR
|
||||||
// IGNORE_BACKEND: JS_IR_ES6
|
// IGNORE_BACKEND: JS_IR_ES6
|
||||||
// IGNORE_BACKEND: JS, NATIVE
|
// IGNORE_BACKEND: JS, NATIVE
|
||||||
|
// !USE_EXPERIMENTAL: kotlin.ExperimentalStdlibApi
|
||||||
// WITH_REFLECT
|
// WITH_REFLECT
|
||||||
|
|
||||||
import kotlin.reflect.full.findAnnotation
|
import kotlin.reflect.full.findAnnotation
|
||||||
|
import kotlin.reflect.full.findAnnotations
|
||||||
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertNull
|
import kotlin.test.assertNull
|
||||||
|
|
||||||
annotation class Yes(val value: String)
|
annotation class Yes(val value: String)
|
||||||
@@ -19,5 +22,7 @@ fun box(): String {
|
|||||||
assertNull(Bar::class.findAnnotation<Yes>())
|
assertNull(Bar::class.findAnnotation<Yes>())
|
||||||
assertNull(Bar::class.findAnnotation<No>())
|
assertNull(Bar::class.findAnnotation<No>())
|
||||||
|
|
||||||
|
assertEquals("OK", Foo::class.findAnnotations<Yes>().single().value)
|
||||||
|
|
||||||
return Foo::class.findAnnotation<Yes>()?.value ?: "Fail: no annotation"
|
return Foo::class.findAnnotation<Yes>()?.value ?: "Fail: no annotation"
|
||||||
}
|
}
|
||||||
|
|||||||
+44
@@ -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<A>()) return "Fail hasAnnotation $element"
|
||||||
|
val find = element.findAnnotation<A>()
|
||||||
|
if (find != null) return "Fail findAnnotation $element: $find"
|
||||||
|
|
||||||
|
val all = (element.annotations.single() as A.Container).value.asList()
|
||||||
|
val findAll = element.findAnnotations<A>()
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+36
@@ -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>)
|
||||||
|
}
|
||||||
|
|
||||||
|
@A("O")
|
||||||
|
@A("")
|
||||||
|
@A("K")
|
||||||
|
fun f() {}
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val element = ::f
|
||||||
|
if (element.hasAnnotation<A>()) return "Fail hasAnnotation $element"
|
||||||
|
val find = element.findAnnotation<A>()
|
||||||
|
if (find != null) return "Fail findAnnotation $element: $find"
|
||||||
|
|
||||||
|
val all = (element.annotations.single() as A.Container).value.asList()
|
||||||
|
val findAll = element.findAnnotations<A>()
|
||||||
|
if (all != findAll) throw AssertionError("Fail findAnnotations $element: $all != $findAll")
|
||||||
|
|
||||||
|
return all.fold("") { acc, it -> acc + it.value }
|
||||||
|
}
|
||||||
+54
@@ -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<A>()) throw AssertionError("Fail hasAnnotation $element")
|
||||||
|
|
||||||
|
val find = element.findAnnotation<A>()
|
||||||
|
if (find == null || find.value != "O") throw AssertionError("Fail findAnnotation $element: $find")
|
||||||
|
|
||||||
|
val all = element.annotations
|
||||||
|
val findAll = element.findAnnotations<A>()
|
||||||
|
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"
|
||||||
|
}
|
||||||
+44
@@ -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>)
|
||||||
|
|
||||||
|
@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<A>().render())
|
||||||
|
assertEquals("@test.A(1)", Z::class.findAnnotation<A>().render())
|
||||||
|
|
||||||
|
assertEquals("[@test.As([@test.A(1), @test.A(2)]), @test.A(3)]", ZZ::class.annotations.render())
|
||||||
|
assertEquals("[@test.A(3)]", ZZ::class.findAnnotations<A>().render())
|
||||||
|
assertEquals("@test.A(3)", ZZ::class.findAnnotation<A>().render())
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
+1
@@ -1,3 +1,4 @@
|
|||||||
|
@kotlin.jvm.internal.RepeatableContainer
|
||||||
@java.lang.annotation.Retention(value=RUNTIME)
|
@java.lang.annotation.Retention(value=RUNTIME)
|
||||||
@kotlin.Metadata
|
@kotlin.Metadata
|
||||||
public annotation class test/A$Container {
|
public annotation class test/A$Container {
|
||||||
|
|||||||
+1
@@ -1,4 +1,5 @@
|
|||||||
@kotlin.annotation.Target(allowedTargets=[FILE, TYPEALIAS])
|
@kotlin.annotation.Target(allowedTargets=[FILE, TYPEALIAS])
|
||||||
|
@kotlin.jvm.internal.RepeatableContainer
|
||||||
@java.lang.annotation.Retention(value=RUNTIME)
|
@java.lang.annotation.Retention(value=RUNTIME)
|
||||||
@java.lang.annotation.Target(value=[])
|
@java.lang.annotation.Target(value=[])
|
||||||
@kotlin.Metadata
|
@kotlin.Metadata
|
||||||
|
|||||||
Vendored
+3
@@ -1,3 +1,4 @@
|
|||||||
|
@kotlin.jvm.internal.RepeatableContainer
|
||||||
@java.lang.annotation.Retention(value=RUNTIME)
|
@java.lang.annotation.Retention(value=RUNTIME)
|
||||||
@kotlin.Metadata
|
@kotlin.Metadata
|
||||||
public annotation class test/A$Container {
|
public annotation class test/A$Container {
|
||||||
@@ -16,6 +17,7 @@ public annotation class test/A {
|
|||||||
public inner class test/A$Container
|
public inner class test/A$Container
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@kotlin.jvm.internal.RepeatableContainer
|
||||||
@java.lang.annotation.Retention(value=RUNTIME)
|
@java.lang.annotation.Retention(value=RUNTIME)
|
||||||
@kotlin.Metadata
|
@kotlin.Metadata
|
||||||
public annotation class test/B$Container {
|
public annotation class test/B$Container {
|
||||||
@@ -34,6 +36,7 @@ public annotation class test/B {
|
|||||||
public inner class test/B$Container
|
public inner class test/B$Container
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@kotlin.jvm.internal.RepeatableContainer
|
||||||
@java.lang.annotation.Retention(value=RUNTIME)
|
@java.lang.annotation.Retention(value=RUNTIME)
|
||||||
@kotlin.Metadata
|
@kotlin.Metadata
|
||||||
public annotation class test/C$Container {
|
public annotation class test/C$Container {
|
||||||
|
|||||||
+6
@@ -1,4 +1,5 @@
|
|||||||
@kotlin.annotation.Retention(value=BINARY)
|
@kotlin.annotation.Retention(value=BINARY)
|
||||||
|
@kotlin.jvm.internal.RepeatableContainer
|
||||||
@java.lang.annotation.Retention(value=CLASS)
|
@java.lang.annotation.Retention(value=CLASS)
|
||||||
@kotlin.Metadata
|
@kotlin.Metadata
|
||||||
public annotation class RetentionBinary$Container {
|
public annotation class RetentionBinary$Container {
|
||||||
@@ -17,6 +18,7 @@ public annotation class RetentionBinary {
|
|||||||
public inner class RetentionBinary$Container
|
public inner class RetentionBinary$Container
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@kotlin.jvm.internal.RepeatableContainer
|
||||||
@java.lang.annotation.Retention(value=RUNTIME)
|
@java.lang.annotation.Retention(value=RUNTIME)
|
||||||
@kotlin.Metadata
|
@kotlin.Metadata
|
||||||
public annotation class RetentionRuntime$Container {
|
public annotation class RetentionRuntime$Container {
|
||||||
@@ -35,6 +37,7 @@ public annotation class RetentionRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@kotlin.annotation.Retention(value=SOURCE)
|
@kotlin.annotation.Retention(value=SOURCE)
|
||||||
|
@kotlin.jvm.internal.RepeatableContainer
|
||||||
@java.lang.annotation.Retention(value=SOURCE)
|
@java.lang.annotation.Retention(value=SOURCE)
|
||||||
@kotlin.Metadata
|
@kotlin.Metadata
|
||||||
public annotation class RetentionSource$Container {
|
public annotation class RetentionSource$Container {
|
||||||
@@ -54,6 +57,7 @@ public annotation class RetentionSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@kotlin.annotation.Target(allowedTargets=[ANNOTATION_CLASS, TYPE])
|
@kotlin.annotation.Target(allowedTargets=[ANNOTATION_CLASS, TYPE])
|
||||||
|
@kotlin.jvm.internal.RepeatableContainer
|
||||||
@java.lang.annotation.Retention(value=RUNTIME)
|
@java.lang.annotation.Retention(value=RUNTIME)
|
||||||
@java.lang.annotation.Target(value=[ANNOTATION_TYPE, TYPE_USE])
|
@java.lang.annotation.Target(value=[ANNOTATION_TYPE, TYPE_USE])
|
||||||
@kotlin.Metadata
|
@kotlin.Metadata
|
||||||
@@ -75,6 +79,7 @@ public annotation class TargetAnnotationClassAndTypeOnly {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@kotlin.annotation.Target(allowedTargets=[CLASS])
|
@kotlin.annotation.Target(allowedTargets=[CLASS])
|
||||||
|
@kotlin.jvm.internal.RepeatableContainer
|
||||||
@java.lang.annotation.Retention(value=RUNTIME)
|
@java.lang.annotation.Retention(value=RUNTIME)
|
||||||
@java.lang.annotation.Target(value=[TYPE])
|
@java.lang.annotation.Target(value=[TYPE])
|
||||||
@kotlin.Metadata
|
@kotlin.Metadata
|
||||||
@@ -96,6 +101,7 @@ public annotation class TargetClassOnly {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@kotlin.annotation.Target(allowedTargets=[])
|
@kotlin.annotation.Target(allowedTargets=[])
|
||||||
|
@kotlin.jvm.internal.RepeatableContainer
|
||||||
@java.lang.annotation.Retention(value=RUNTIME)
|
@java.lang.annotation.Retention(value=RUNTIME)
|
||||||
@java.lang.annotation.Target(value=[])
|
@java.lang.annotation.Target(value=[])
|
||||||
@kotlin.Metadata
|
@kotlin.Metadata
|
||||||
|
|||||||
+10
@@ -34988,6 +34988,16 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
runTest("compiler/testData/codegen/box/reflection/annotations/onTypes/differentPositions.kt");
|
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
|
@Nested
|
||||||
|
|||||||
+34
@@ -35120,6 +35120,40 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
|||||||
runTest("compiler/testData/codegen/box/reflection/annotations/onTypes/differentPositions.kt");
|
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
|
@Nested
|
||||||
|
|||||||
+13
@@ -27624,6 +27624,19 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
|||||||
runTest("compiler/testData/codegen/box/reflection/annotations/onTypes/differentPositions.kt");
|
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")
|
@TestMetadata("compiler/testData/codegen/box/reflection/builtins")
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
|
|
||||||
package kotlin.reflect.full
|
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.
|
* Returns an annotation of the given type on this element.
|
||||||
@@ -25,3 +27,75 @@ inline fun <reified T : Annotation> KAnnotatedElement.findAnnotation(): T? =
|
|||||||
@WasExperimental(ExperimentalStdlibApi::class)
|
@WasExperimental(ExperimentalStdlibApi::class)
|
||||||
inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =
|
inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =
|
||||||
findAnnotation<T>() != null
|
findAnnotation<T>() != 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 <reified T : Annotation> KAnnotatedElement.findAnnotations(): List<T> =
|
||||||
|
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 <T : Annotation> KAnnotatedElement.findAnnotations(klass: KClass<T>): List<T> {
|
||||||
|
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<T>).asList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
private object Java8RepeatableContainerLoader {
|
||||||
|
class Cache(val repeatableClass: Class<out Annotation>?, val valueMethod: Method?)
|
||||||
|
|
||||||
|
var cache: Cache? = null
|
||||||
|
|
||||||
|
private fun buildCache(): Cache {
|
||||||
|
val repeatableClass = try {
|
||||||
|
Class.forName("java.lang.annotation.Repeatable") as Class<out Annotation>
|
||||||
|
} catch (e: ClassNotFoundException) {
|
||||||
|
return Cache(null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cache(repeatableClass, repeatableClass.getMethod("value"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadRepeatableContainer(klass: Class<out Annotation>): Class<out Annotation>? {
|
||||||
|
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<out Annotation>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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.ReflectJavaAnnotation
|
||||||
import org.jetbrains.kotlin.descriptors.runtime.structure.ReflectJavaClass
|
import org.jetbrains.kotlin.descriptors.runtime.structure.ReflectJavaClass
|
||||||
import org.jetbrains.kotlin.descriptors.runtime.structure.safeClassLoader
|
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.load.kotlin.KotlinJvmBinarySourceElement
|
||||||
import org.jetbrains.kotlin.metadata.ProtoBuf
|
import org.jetbrains.kotlin.metadata.ProtoBuf
|
||||||
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
|
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
|
||||||
@@ -49,6 +50,7 @@ import org.jetbrains.kotlin.serialization.deserialization.MemberDeserializer
|
|||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import kotlin.jvm.internal.FunctionReference
|
import kotlin.jvm.internal.FunctionReference
|
||||||
import kotlin.jvm.internal.PropertyReference
|
import kotlin.jvm.internal.PropertyReference
|
||||||
|
import kotlin.jvm.internal.RepeatableContainer
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
import kotlin.reflect.KVisibility
|
import kotlin.reflect.KVisibility
|
||||||
import kotlin.reflect.full.IllegalCallableAccessException
|
import kotlin.reflect.full.IllegalCallableAccessException
|
||||||
@@ -122,7 +124,22 @@ internal fun Annotated.computeAnnotations(): List<Annotation> =
|
|||||||
is RuntimeSourceElementFactory.RuntimeSourceElement -> (source.javaElement as? ReflectJavaAnnotation)?.annotation
|
is RuntimeSourceElementFactory.RuntimeSourceElement -> (source.javaElement as? ReflectJavaAnnotation)?.annotation
|
||||||
else -> it.toAnnotationInstance()
|
else -> it.toAnnotationInstance()
|
||||||
}
|
}
|
||||||
}
|
}.unwrapRepeatableAnnotations()
|
||||||
|
|
||||||
|
private fun List<Annotation>.unwrapRepeatableAnnotations(): List<Annotation> =
|
||||||
|
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<out Annotation>).asList()
|
||||||
|
else
|
||||||
|
listOf(it)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this
|
||||||
|
|
||||||
private fun AnnotationDescriptor.toAnnotationInstance(): Annotation? {
|
private fun AnnotationDescriptor.toAnnotationInstance(): Annotation? {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
|||||||
js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrJsCodegenBoxES6TestGenerated.java
Generated
+13
@@ -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);
|
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")
|
@TestMetadata("compiler/testData/codegen/box/reflection/builtins")
|
||||||
|
|||||||
Generated
+13
@@ -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);
|
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")
|
@TestMetadata("compiler/testData/codegen/box/reflection/builtins")
|
||||||
|
|||||||
Generated
+13
@@ -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);
|
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")
|
@TestMetadata("compiler/testData/codegen/box/reflection/builtins")
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
}
|
||||||
@@ -6,6 +6,10 @@ public final class kotlin/reflect/full/IllegalPropertyDelegateAccessException :
|
|||||||
public fun <init> (Ljava/lang/IllegalAccessException;)V
|
public fun <init> (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 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 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;
|
public static final fun callSuspendBy (Lkotlin/reflect/KCallable;Ljava/util/Map;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
|||||||
+3
@@ -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 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 final class kotlin/jvm/internal/ShortCompanionObject {
|
||||||
public static final field INSTANCE Lkotlin/jvm/internal/ShortCompanionObject;
|
public static final field INSTANCE Lkotlin/jvm/internal/ShortCompanionObject;
|
||||||
public static final field MAX_VALUE S
|
public static final field MAX_VALUE S
|
||||||
|
|||||||
Reference in New Issue
Block a user