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:
Alexander Udalov
2021-07-24 03:33:42 +02:00
parent 67128c022a
commit 0a6d010d1c
23 changed files with 448 additions and 3 deletions
@@ -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
@@ -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
}
@@ -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 {
@@ -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<Yes>())
assertNull(Bar::class.findAnnotation<No>())
assertEquals("OK", Foo::class.findAnnotations<Yes>().single().value)
return Foo::class.findAnnotation<Yes>()?.value ?: "Fail: no annotation"
}
@@ -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();
}
}
@@ -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 }
}
@@ -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"
}
@@ -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,3 +1,4 @@
@kotlin.jvm.internal.RepeatableContainer
@java.lang.annotation.Retention(value=RUNTIME)
@kotlin.Metadata
public annotation class test/A$Container {
@@ -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
@@ -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 {
@@ -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
@@ -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
@@ -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
@@ -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")