Instantiation of annotations for JVM IR with the corresponding feature flag
Seperate checker for platforms that do not support this language feature yet Synthetic implementations of annotations are generated on-demand with proper equals, hashCode, and annotationType methods #KT-47699 Fixed
This commit is contained in:
+53
@@ -0,0 +1,53 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
|
||||
// WITH_RUNTIME
|
||||
// !LANGUAGE: +InstantiationOfAnnotationClasses
|
||||
|
||||
// FILE: a.kt
|
||||
|
||||
package test
|
||||
|
||||
annotation class A1
|
||||
|
||||
annotation class A2
|
||||
|
||||
fun interface I {
|
||||
fun run(): A1
|
||||
}
|
||||
|
||||
// FILE: test.kt
|
||||
|
||||
package test
|
||||
|
||||
class E {
|
||||
fun insideClass(): A1 = A1()
|
||||
fun insideLammbda(): A1 = run { A1() }
|
||||
fun insideSAM(): I = I { A1() }
|
||||
}
|
||||
|
||||
class G {
|
||||
// test that we can reuse instance in different classes from same file
|
||||
fun insideClassAgain(): A1 = A1()
|
||||
}
|
||||
|
||||
fun outsideClass(): A2 = A2()
|
||||
|
||||
fun test(instance: Any, parent: String, fqa: String) {
|
||||
val clz = instance.javaClass
|
||||
assert(clz.getName().startsWith(parent))
|
||||
assert(clz.getName().contains(fqa))
|
||||
assert(clz.getEnclosingMethod() == null)
|
||||
assert(clz.getEnclosingClass().getName() == parent)
|
||||
// SAM treated as anonymous because of Origin or something else, see ClassCodegen#IrClass.isAnonymousInnerClass
|
||||
// assert(clz.getDeclaringClass() == null)
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
test(E().insideClass(), "test.E", "test_A1")
|
||||
test(E().insideLammbda(), "test.E", "test_A1")
|
||||
test(E().insideSAM().run(), "test.E", "test_A1")
|
||||
test(G().insideClassAgain(), "test.E", "test_A1")
|
||||
test(outsideClass(), "test.TestKt", "test_A2")
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
|
||||
// WITH_RUNTIME
|
||||
// !LANGUAGE: +InstantiationOfAnnotationClasses
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
annotation class Bar(val i:Int, val s: String, val f: Float)
|
||||
|
||||
annotation class Foo(
|
||||
val int: Int,
|
||||
val s: String,
|
||||
val arr: Array<String>,
|
||||
val arr2: IntArray,
|
||||
val kClass: KClass<*>,
|
||||
val bar: Bar
|
||||
)
|
||||
|
||||
data class BarLike(val i:Int, val s: String, val f: Float)
|
||||
|
||||
fun box(): String {
|
||||
val foo1 = Foo(42, "foo", arrayOf("a", "b"), intArrayOf(1,2), Bar::class, Bar(10, "bar", Float.NaN))
|
||||
val foo2 = Foo(42, "foo", arrayOf("a", "b"), intArrayOf(1,2), Bar::class, Bar(10, "bar", Float.NaN))
|
||||
if (foo1 != foo2) return "Failed equals"
|
||||
val barlike = BarLike(10, "bar", Float.NaN)
|
||||
if (barlike.hashCode() != foo1.bar.hashCode()) return "Failed HC1"
|
||||
if (barlike.hashCode() != foo2.bar.hashCode()) return "Failed HC2"
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
|
||||
// WITH_RUNTIME
|
||||
// !LANGUAGE: +InstantiationOfAnnotationClasses
|
||||
|
||||
// note: taken from ../parameters.kt and ../parametersWithPrimitiveValues.kt
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
enum class E { E0 }
|
||||
annotation class Empty
|
||||
|
||||
annotation class A(
|
||||
val b: Byte,
|
||||
val s: Short,
|
||||
val i: Int,
|
||||
val f: Float,
|
||||
val d: Double,
|
||||
val l: Long,
|
||||
val c: Char,
|
||||
val bool: Boolean
|
||||
)
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Anno(
|
||||
val s: String,
|
||||
val i: Int,
|
||||
val f: Double,
|
||||
val u: UInt,
|
||||
val e: E,
|
||||
val a: A,
|
||||
val k: KClass<*>,
|
||||
val arr: Array<String>,
|
||||
val intArr: IntArray,
|
||||
val arrOfE: Array<E>,
|
||||
val arrOfA: Array<Empty>,
|
||||
val arrOfK: Array<KClass<*>>
|
||||
)
|
||||
|
||||
|
||||
fun box(): String {
|
||||
val anno = Anno(
|
||||
"OK", 42, 2.718281828, 43u, E.E0,
|
||||
A(1, 1, 1, 1.0.toFloat(), 1.0, 1, 'c', true),
|
||||
A::class, emptyArray(), intArrayOf(1, 2), arrayOf(E.E0), arrayOf(Empty()), arrayOf(E::class, Empty::class)
|
||||
)
|
||||
assertEquals(anno.s, "OK")
|
||||
assertEquals(anno.i, 42)
|
||||
assert(anno.f > 2.0 && anno.f < 3.0)
|
||||
assertEquals(anno.u, 43u)
|
||||
assertEquals(anno.e, E.E0)
|
||||
assert(anno.a is A)
|
||||
assert(anno.k == A::class)
|
||||
assert(anno.arr.isEmpty())
|
||||
assert(anno.intArr.contentEquals(intArrayOf(1, 2)))
|
||||
assert(anno.arrOfE.contentEquals(arrayOf(E.E0)))
|
||||
assert(anno.arrOfA.size == 1)
|
||||
// assert(anno.arrOfK.size == 2) TODO(KT-47703): Array<KClass> to Array<j.l.Class> conversion
|
||||
val ann = anno.a
|
||||
assertEquals(ann.b, 1.toByte())
|
||||
assertEquals(ann.s, 1.toShort())
|
||||
assertEquals(ann.i, 1)
|
||||
assertEquals(ann.f, 1.toFloat())
|
||||
assertEquals(ann.d, 1.0)
|
||||
assertEquals(ann.l, 1.toLong())
|
||||
assertEquals(ann.c, 'c')
|
||||
assert(ann.bool)
|
||||
return "OK"
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
|
||||
// WITH_RUNTIME
|
||||
// !LANGUAGE: +InstantiationOfAnnotationClasses
|
||||
|
||||
package test
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
enum class E { A, B }
|
||||
|
||||
annotation class A()
|
||||
|
||||
annotation class B(val a: A = A())
|
||||
|
||||
annotation class C(
|
||||
val i: Int = 42,
|
||||
val b: B = B(),
|
||||
val kClass: KClass<*> = B::class,
|
||||
val e: E = E.B,
|
||||
val aS: Array<String> = arrayOf("a", "b"),
|
||||
val aI: IntArray = intArrayOf(1, 2)
|
||||
)
|
||||
|
||||
annotation class Partial(
|
||||
val i: Int = 42,
|
||||
val s: String = "foo",
|
||||
val e: E = E.A
|
||||
)
|
||||
|
||||
fun box(): String {
|
||||
val c = C()
|
||||
assert(c.toString() == "@test.C(i=42, b=@test.B(a=@test.A()), kClass=interface test.B (Kotlin reflection is not available), e=B, aS=[a, b], aI=[1, 2])")
|
||||
val p = Partial(e = E.B, s = "bar")
|
||||
assert(p.toString() == "@test.Partial(i=42, s=bar, e=B)")
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
|
||||
// WITH_RUNTIME
|
||||
// !LANGUAGE: +InstantiationOfAnnotationClasses
|
||||
|
||||
package test
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
enum class E { E0 }
|
||||
annotation class Empty
|
||||
|
||||
annotation class A(
|
||||
val b: Byte,
|
||||
val s: Short,
|
||||
val i: Int,
|
||||
val f: Float,
|
||||
val d: Double,
|
||||
val l: Long,
|
||||
val c: Char,
|
||||
val bool: Boolean
|
||||
)
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Anno(
|
||||
val s: String,
|
||||
val i: Int,
|
||||
val f: Double,
|
||||
val u: UInt,
|
||||
val e: E,
|
||||
val a: A,
|
||||
val k: KClass<*>,
|
||||
val arr: Array<String>,
|
||||
val intArr: IntArray,
|
||||
val arrOfE: Array<E>,
|
||||
val arrOfA: Array<Empty>,
|
||||
)
|
||||
|
||||
fun box(): String {
|
||||
val anno = Anno(
|
||||
"OK", 42, 2.718281828, 43u, E.E0,
|
||||
A(1, 1, 1, 1.0.toFloat(), 1.0, 1, 'c', true),
|
||||
A::class, emptyArray(), intArrayOf(1, 2), arrayOf(E.E0), arrayOf(Empty())
|
||||
)
|
||||
val s = anno.toString()
|
||||
val target = "@test.Anno(s=OK, i=42, f=2.718281828, u=43, e=E0, a=@test.A(b=1, s=1, i=1, f=1.0, d=1.0, l=1, c=c, bool=true), " +
|
||||
"k=interface test.A (Kotlin reflection is not available), arr=[], intArr=[1, 2], arrOfE=[E0], arrOfA=[@test.Empty()])"
|
||||
return if (s == target) "OK" else "FAILED, got string $s"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
|
||||
// WITH_RUNTIME
|
||||
// !LANGUAGE: +InstantiationOfAnnotationClasses
|
||||
|
||||
annotation class Foo(
|
||||
val int: Int,
|
||||
)
|
||||
|
||||
fun box(): String {
|
||||
val foo = Foo(42)
|
||||
val jClass = (foo as java.lang.annotation.Annotation).annotationType()
|
||||
val kClass = foo.annotationClass
|
||||
if (kClass != Foo::class) return "FAIL $kClass"
|
||||
if (jClass != Foo::class.java) return "FAIL $jClass"
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
|
||||
// WITH_RUNTIME
|
||||
// !LANGUAGE: +InstantiationOfAnnotationClasses
|
||||
|
||||
// FILE: A.java
|
||||
|
||||
public @interface A {}
|
||||
|
||||
// FILE: B.java
|
||||
|
||||
public @interface B {
|
||||
String value();
|
||||
}
|
||||
|
||||
// FILE: C.java
|
||||
|
||||
public @interface C {
|
||||
int[] v1();
|
||||
String v2();
|
||||
}
|
||||
|
||||
// FILE: D.java
|
||||
|
||||
public @interface D {
|
||||
String value() default "hello";
|
||||
}
|
||||
|
||||
// FILE: b.kt
|
||||
|
||||
fun box(): String {
|
||||
val a = A()
|
||||
val b = B("OK")
|
||||
assert(b.value == "OK")
|
||||
val c = C(v2 = "v2", v1 = intArrayOf(1))
|
||||
assert(c.v2 == "v2")
|
||||
// TODO(KT-47702): Looks like we have to force users either to pass default java parameters explicitly
|
||||
// or hack LazyJavaClassDescriptor/JavaPropertyDescriptor to load annotation param default value,
|
||||
// because it is not stored currently anywhere.
|
||||
// val d = D()
|
||||
val d = D("OK").value
|
||||
return d
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
|
||||
// WITH_RUNTIME
|
||||
// !LANGUAGE: +InstantiationOfAnnotationClasses
|
||||
|
||||
|
||||
// FILE: a.kt
|
||||
package a
|
||||
|
||||
annotation class A(val i: Int)
|
||||
|
||||
fun createInOtherFile(): A = A(10)
|
||||
|
||||
// FILE: b.kt
|
||||
|
||||
import a.*
|
||||
|
||||
fun here(): A = A(10)
|
||||
|
||||
fun box(): String {
|
||||
if (here() != createInOtherFile()) return "Fail equals"
|
||||
if (here().hashCode() != createInOtherFile().hashCode()) return "Fail hashCode"
|
||||
if (here().toString() != createInOtherFile().toString()) return "Fail toString"
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
// IGNORE_DEXING
|
||||
|
||||
// WITH_RUNTIME
|
||||
// !LANGUAGE: +InstantiationOfAnnotationClasses
|
||||
|
||||
// MODULE: lib
|
||||
// FILE: lib.kt
|
||||
|
||||
package a
|
||||
|
||||
annotation class A(val i: Int)
|
||||
|
||||
inline fun foo(i: Int): A = A(i)
|
||||
|
||||
inline fun bar(f: () -> Int): A = A(f())
|
||||
|
||||
// MODULE: app(lib)
|
||||
// FILE: app.kt
|
||||
|
||||
package test
|
||||
|
||||
import a.*
|
||||
|
||||
class C {
|
||||
fun one(): A = foo(1)
|
||||
fun two(): A = bar { 2 }
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
val one = C().one()
|
||||
val two = C().two()
|
||||
assert(one.i == 1)
|
||||
assert(two.i == 2)
|
||||
// Just like SAM wrappers, annotation implementation classes should be copied from inline functions
|
||||
// into current module to avoid compatibility problems when inline fun implementation in origin module
|
||||
// has changed (e.g. do not instantiate annotation anymore)
|
||||
assert(one.javaClass.getEnclosingClass().getName() == "test.C")
|
||||
assert(two.javaClass.getEnclosingClass().getName() == "test.C")
|
||||
return "OK"
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// IGNORE_BACKEND_FIR: JVM_IR
|
||||
// TARGET_BACKEND: JVM_IR
|
||||
|
||||
// WITH_RUNTIME
|
||||
// !LANGUAGE: +InstantiationOfAnnotationClasses +MultiPlatformProjects
|
||||
|
||||
// MODULE: lib
|
||||
// FILE: common.kt
|
||||
|
||||
expect annotation class A(val value: String)
|
||||
|
||||
fun createCommon(): A = A("OK")
|
||||
|
||||
// FILE: platform.kt
|
||||
|
||||
actual annotation class A(actual val value: String)
|
||||
|
||||
fun createPlatform(): A = A("OK")
|
||||
|
||||
// MODULE: main(lib)
|
||||
// FILE: main.kt
|
||||
|
||||
fun createApp(): A = A("OK")
|
||||
|
||||
fun box(): String {
|
||||
if (createApp().value != "OK") return "FAIL app"
|
||||
if (createCommon().value != "OK") return "FAIL common"
|
||||
if (createPlatform().value != "OK") return "FAIL platform"
|
||||
return "OK"
|
||||
}
|
||||
Reference in New Issue
Block a user