Support Java annotation constructors in reflection
#KT-13106 In Progress
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
// WITH_REFLECT
|
||||
// FILE: J.java
|
||||
|
||||
public interface J {
|
||||
@interface NoParams {}
|
||||
|
||||
@interface OneDefault {
|
||||
String s() default "OK";
|
||||
}
|
||||
|
||||
@interface OneNonDefault {
|
||||
String s();
|
||||
}
|
||||
|
||||
@interface TwoParamsOneDefault {
|
||||
String s();
|
||||
int x() default 42;
|
||||
}
|
||||
|
||||
@interface TwoNonDefaults {
|
||||
String string();
|
||||
Class<?> clazz();
|
||||
}
|
||||
|
||||
@interface ManyDefaultParams {
|
||||
int i() default 0;
|
||||
String s() default "";
|
||||
double d() default 3.14;
|
||||
}
|
||||
}
|
||||
|
||||
// FILE: K.kt
|
||||
|
||||
import J.*
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.primaryConstructor
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFails
|
||||
|
||||
inline fun <reified T : Annotation> create(args: Map<String, Any?>): T {
|
||||
val ctor = T::class.constructors.single()
|
||||
return ctor.callBy(args.mapKeys { entry -> ctor.parameters.single { it.name == entry.key } })
|
||||
}
|
||||
|
||||
inline fun <reified T : Annotation> create(): T = create(emptyMap())
|
||||
|
||||
fun box(): String {
|
||||
create<NoParams>()
|
||||
|
||||
val t1 = create<OneDefault>()
|
||||
assertEquals("OK", t1.s)
|
||||
assertFails { create<OneDefault>(mapOf("s" to 42)) }
|
||||
|
||||
val t2 = create<OneNonDefault>(mapOf("s" to "OK"))
|
||||
assertEquals("OK", t2.s)
|
||||
assertFails { create<OneNonDefault>() }
|
||||
|
||||
val t3 = create<TwoParamsOneDefault>(mapOf("s" to "OK"))
|
||||
assertEquals("OK", t3.s)
|
||||
assertEquals(42, t3.x)
|
||||
val t4 = create<TwoParamsOneDefault>(mapOf("s" to "OK", "x" to 239))
|
||||
assertEquals(239, t4.x)
|
||||
assertFails { create<TwoParamsOneDefault>(mapOf("s" to "Fail", "x" to "Fail")) }
|
||||
|
||||
assertFails("KClass (not Class) instances should be passed as arguments") {
|
||||
create<TwoNonDefaults>(mapOf("clazz" to String::class.java, "string" to "Fail"))
|
||||
}
|
||||
|
||||
val t5 = create<TwoNonDefaults>(mapOf("clazz" to String::class, "string" to "OK"))
|
||||
assertEquals("OK", t5.string)
|
||||
|
||||
val t6 = create<ManyDefaultParams>()
|
||||
assertEquals(0, t6.i)
|
||||
assertEquals("", t6.s)
|
||||
assertEquals(3.14, t6.d)
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// WITH_REFLECT
|
||||
// FILE: J.java
|
||||
|
||||
public interface J {
|
||||
@interface NoParams {}
|
||||
|
||||
@interface OneDefault {
|
||||
String foo() default "foo";
|
||||
}
|
||||
|
||||
@interface OneDefaultValue {
|
||||
String value() default "value";
|
||||
}
|
||||
|
||||
@interface OneNonDefault {
|
||||
String foo();
|
||||
}
|
||||
|
||||
@interface OneNonDefaultValue {
|
||||
String value();
|
||||
}
|
||||
|
||||
@interface TwoParamsOneDefault {
|
||||
String string();
|
||||
Class<?> clazz() default Object.class;
|
||||
}
|
||||
|
||||
@interface TwoParamsOneValueOneDefault {
|
||||
String value();
|
||||
Class<?> clazz() default Object.class;
|
||||
}
|
||||
|
||||
@interface TwoNonDefaults {
|
||||
String string();
|
||||
Class<?> clazz();
|
||||
}
|
||||
|
||||
@interface ManyDefaults {
|
||||
int i() default 0;
|
||||
String s() default "";
|
||||
double d() default 3.14;
|
||||
}
|
||||
}
|
||||
|
||||
// FILE: K.kt
|
||||
|
||||
import J.*
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.primaryConstructor
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFails
|
||||
|
||||
inline fun <reified T : Annotation> create(vararg args: Any?): T =
|
||||
T::class.constructors.single().call(*args)
|
||||
|
||||
fun box(): String {
|
||||
create<NoParams>()
|
||||
|
||||
assertFails { create<OneDefault>() }
|
||||
assertFails { create<OneDefault>("") }
|
||||
assertFails { create<OneDefault>("", "") }
|
||||
|
||||
assertFails { create<OneDefaultValue>() }
|
||||
create<OneDefaultValue>("")
|
||||
assertFails { create<OneDefaultValue>("", "") }
|
||||
|
||||
assertFails { create<OneNonDefault>() }
|
||||
assertFails { create<OneNonDefault>("") }
|
||||
|
||||
assertFails { create<OneNonDefaultValue>() }
|
||||
create<OneNonDefaultValue>("")
|
||||
|
||||
assertFails { create<TwoParamsOneDefault>() }
|
||||
assertFails { create<TwoParamsOneDefault>("") }
|
||||
assertFails { create<TwoParamsOneDefault>("", Any::class) }
|
||||
assertFails { create<TwoParamsOneDefault>(Any::class, "") }
|
||||
|
||||
assertFails { create<TwoParamsOneValueOneDefault>() }
|
||||
assertFails { create<TwoParamsOneValueOneDefault>("") }
|
||||
assertFails { create<TwoParamsOneValueOneDefault>("", Any::class) }
|
||||
assertFails { create<TwoParamsOneValueOneDefault>(Any::class, "") }
|
||||
|
||||
assertFails { create<TwoNonDefaults>("", Any::class) }
|
||||
assertFails { create<TwoNonDefaults>(Any::class, "") }
|
||||
|
||||
assertFails { create<ManyDefaults>() }
|
||||
assertFails { create<ManyDefaults>(42, "Fail", 2.72) }
|
||||
|
||||
return "OK"
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// WITH_REFLECT
|
||||
|
||||
import java.lang.annotation.Retention
|
||||
import java.lang.annotation.RetentionPolicy
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
fun box(): String {
|
||||
val ctor = Retention::class.constructors.single()
|
||||
val r = ctor.callBy(mapOf(
|
||||
ctor.parameters.single { it.name == "value" } to RetentionPolicy.RUNTIME
|
||||
))
|
||||
assertEquals(RetentionPolicy.RUNTIME, r.value as RetentionPolicy)
|
||||
assertEquals(Retention::class.java.classLoader, r.javaClass.classLoader)
|
||||
return "OK"
|
||||
}
|
||||
@@ -12156,17 +12156,35 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("callByJava.kt")
|
||||
public void testCallByJava() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/callByJava.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("callByKotlin.kt")
|
||||
public void testCallByKotlin() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/callByKotlin.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("callJava.kt")
|
||||
public void testCallJava() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/callJava.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("callKotlin.kt")
|
||||
public void testCallKotlin() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/callKotlin.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("createJdkAnnotationInstance.kt")
|
||||
public void testCreateJdkAnnotationInstance() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/createJdkAnnotationInstance.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/box/reflection/enclosing")
|
||||
|
||||
@@ -26,17 +26,31 @@ internal class AnnotationConstructorCaller(
|
||||
private val jClass: Class<*>,
|
||||
private val parameterNames: List<String>,
|
||||
private val callMode: CallMode,
|
||||
origin: Origin,
|
||||
methods: List<ReflectMethod> = parameterNames.map { name -> jClass.getDeclaredMethod(name) }
|
||||
) : FunctionCaller<Nothing?>(
|
||||
null, jClass, null, methods.map { it.genericReturnType }.toTypedArray()
|
||||
) {
|
||||
enum class CallMode { CALL_BY_NAME, POSITIONAL_CALL }
|
||||
|
||||
enum class Origin { JAVA, KOTLIN }
|
||||
|
||||
// Transform primitive int to java.lang.Integer because actual arguments passed here will be boxed and Class#isInstance should succeed
|
||||
private val erasedParameterTypes: List<Class<*>> = methods.map { method -> method.returnType.let { it.wrapperByPrimitive ?: it } }
|
||||
|
||||
private val defaultValues: List<Any?> = methods.map { method -> method.defaultValue }
|
||||
|
||||
init {
|
||||
// TODO: consider lifting this restriction once KT-8957 is implemented
|
||||
if (callMode == CallMode.POSITIONAL_CALL && origin == Origin.JAVA && (parameterNames - "value").isNotEmpty()) {
|
||||
throw UnsupportedOperationException(
|
||||
"Positional call of a Java annotation constructor is allowed only if there are no parameters " +
|
||||
"or one parameter named \"value\". This restriction exists because Java annotations (in contrast to Kotlin)" +
|
||||
"do not impose any order on their arguments. Use KCallable#callBy instead."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun call(args: Array<*>): Any? {
|
||||
checkArguments(args)
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KotlinReflectionInternalError
|
||||
import kotlin.reflect.jvm.internal.AnnotationConstructorCaller.CallMode.CALL_BY_NAME
|
||||
import kotlin.reflect.jvm.internal.AnnotationConstructorCaller.CallMode.POSITIONAL_CALL
|
||||
import kotlin.reflect.jvm.internal.AnnotationConstructorCaller.Origin.JAVA
|
||||
import kotlin.reflect.jvm.internal.AnnotationConstructorCaller.Origin.KOTLIN
|
||||
import kotlin.reflect.jvm.internal.JvmFunctionSignature.*
|
||||
|
||||
internal class KFunctionImpl private constructor(
|
||||
@@ -55,12 +57,16 @@ internal class KFunctionImpl private constructor(
|
||||
val member: Member? = when (jvmSignature) {
|
||||
is KotlinConstructor -> {
|
||||
if (isAnnotationConstructor)
|
||||
return@caller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, POSITIONAL_CALL)
|
||||
return@caller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, POSITIONAL_CALL, KOTLIN)
|
||||
container.findConstructorBySignature(jvmSignature.constructorDesc, isDeclared())
|
||||
}
|
||||
is KotlinFunction -> container.findMethodBySignature(jvmSignature.methodName, jvmSignature.methodDesc, isDeclared())
|
||||
is JavaMethod -> jvmSignature.method
|
||||
is JavaConstructor -> jvmSignature.constructor
|
||||
is FakeJavaAnnotationConstructor -> {
|
||||
val methods = jvmSignature.methods
|
||||
return@caller AnnotationConstructorCaller(container.jClass, methods.map { it.name }, POSITIONAL_CALL, JAVA, methods)
|
||||
}
|
||||
is BuiltInFunction -> jvmSignature.getMember(container)
|
||||
}
|
||||
|
||||
@@ -86,9 +92,13 @@ internal class KFunctionImpl private constructor(
|
||||
}
|
||||
is KotlinConstructor -> {
|
||||
if (isAnnotationConstructor)
|
||||
return@defaultCaller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, CALL_BY_NAME)
|
||||
return@defaultCaller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, CALL_BY_NAME, KOTLIN)
|
||||
container.findDefaultConstructor(jvmSignature.constructorDesc, isDeclared())
|
||||
}
|
||||
is FakeJavaAnnotationConstructor -> {
|
||||
val methods = jvmSignature.methods
|
||||
return@defaultCaller AnnotationConstructorCaller(container.jClass, methods.map { it.name }, CALL_BY_NAME, JAVA, methods)
|
||||
}
|
||||
else -> {
|
||||
// Java methods, Java constructors and built-ins don't have $default methods
|
||||
null
|
||||
|
||||
@@ -71,9 +71,15 @@ internal sealed class JvmFunctionSignature {
|
||||
|
||||
class JavaConstructor(val constructor: Constructor<*>) : JvmFunctionSignature() {
|
||||
override fun asString(): String =
|
||||
"<init>" +
|
||||
constructor.parameterTypes.joinToString(separator = "", prefix = "(", postfix = ")") { it.desc } +
|
||||
"V"
|
||||
constructor.parameterTypes.joinToString(separator = "", prefix = "<init>(", postfix = ")V") { it.desc }
|
||||
}
|
||||
|
||||
class FakeJavaAnnotationConstructor(val jClass: Class<*>) : JvmFunctionSignature() {
|
||||
// Java annotations do not impose any order of methods inside them, so we consider them lexicographic here for stability
|
||||
val methods = jClass.declaredMethods.sortedBy { it.name }
|
||||
|
||||
override fun asString(): String =
|
||||
methods.joinToString(separator = "", prefix = "<init>(", postfix = ")V") { it.returnType.desc }
|
||||
}
|
||||
|
||||
open class BuiltInFunction(private val signature: String) : JvmFunctionSignature() {
|
||||
@@ -189,10 +195,14 @@ internal object RuntimeTypeMapper {
|
||||
return JvmFunctionSignature.JavaMethod(method)
|
||||
}
|
||||
is JavaClassConstructorDescriptor -> {
|
||||
val constructor = ((function.source as? JavaSourceElement)?.javaElement as? ReflectJavaConstructor)?.member ?:
|
||||
throw KotlinReflectionInternalError("Incorrect resolution sequence for Java constructor $function")
|
||||
|
||||
return JvmFunctionSignature.JavaConstructor(constructor)
|
||||
val element = (function.source as? JavaSourceElement)?.javaElement
|
||||
when {
|
||||
element is ReflectJavaConstructor ->
|
||||
return JvmFunctionSignature.JavaConstructor(element.member)
|
||||
element is ReflectJavaClass && element.isAnnotationType ->
|
||||
return JvmFunctionSignature.FakeJavaAnnotationConstructor(element.element)
|
||||
else -> throw KotlinReflectionInternalError("Incorrect resolution sequence for Java constructor $function ($element)")
|
||||
}
|
||||
}
|
||||
else -> throw KotlinReflectionInternalError("Unknown origin of $function (${function.javaClass})")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user