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);
|
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")
|
@TestMetadata("callByKotlin.kt")
|
||||||
public void testCallByKotlin() throws Exception {
|
public void testCallByKotlin() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/callByKotlin.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/callByKotlin.kt");
|
||||||
doTest(fileName);
|
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")
|
@TestMetadata("callKotlin.kt")
|
||||||
public void testCallKotlin() throws Exception {
|
public void testCallKotlin() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/callKotlin.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/callKotlin.kt");
|
||||||
doTest(fileName);
|
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")
|
@TestMetadata("compiler/testData/codegen/box/reflection/enclosing")
|
||||||
|
|||||||
@@ -26,17 +26,31 @@ internal class AnnotationConstructorCaller(
|
|||||||
private val jClass: Class<*>,
|
private val jClass: Class<*>,
|
||||||
private val parameterNames: List<String>,
|
private val parameterNames: List<String>,
|
||||||
private val callMode: CallMode,
|
private val callMode: CallMode,
|
||||||
|
origin: Origin,
|
||||||
methods: List<ReflectMethod> = parameterNames.map { name -> jClass.getDeclaredMethod(name) }
|
methods: List<ReflectMethod> = parameterNames.map { name -> jClass.getDeclaredMethod(name) }
|
||||||
) : FunctionCaller<Nothing?>(
|
) : FunctionCaller<Nothing?>(
|
||||||
null, jClass, null, methods.map { it.genericReturnType }.toTypedArray()
|
null, jClass, null, methods.map { it.genericReturnType }.toTypedArray()
|
||||||
) {
|
) {
|
||||||
enum class CallMode { CALL_BY_NAME, POSITIONAL_CALL }
|
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
|
// 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 erasedParameterTypes: List<Class<*>> = methods.map { method -> method.returnType.let { it.wrapperByPrimitive ?: it } }
|
||||||
|
|
||||||
private val defaultValues: List<Any?> = methods.map { method -> method.defaultValue }
|
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? {
|
override fun call(args: Array<*>): Any? {
|
||||||
checkArguments(args)
|
checkArguments(args)
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import kotlin.reflect.KFunction
|
|||||||
import kotlin.reflect.KotlinReflectionInternalError
|
import kotlin.reflect.KotlinReflectionInternalError
|
||||||
import kotlin.reflect.jvm.internal.AnnotationConstructorCaller.CallMode.CALL_BY_NAME
|
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.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.*
|
import kotlin.reflect.jvm.internal.JvmFunctionSignature.*
|
||||||
|
|
||||||
internal class KFunctionImpl private constructor(
|
internal class KFunctionImpl private constructor(
|
||||||
@@ -55,12 +57,16 @@ internal class KFunctionImpl private constructor(
|
|||||||
val member: Member? = when (jvmSignature) {
|
val member: Member? = when (jvmSignature) {
|
||||||
is KotlinConstructor -> {
|
is KotlinConstructor -> {
|
||||||
if (isAnnotationConstructor)
|
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())
|
container.findConstructorBySignature(jvmSignature.constructorDesc, isDeclared())
|
||||||
}
|
}
|
||||||
is KotlinFunction -> container.findMethodBySignature(jvmSignature.methodName, jvmSignature.methodDesc, isDeclared())
|
is KotlinFunction -> container.findMethodBySignature(jvmSignature.methodName, jvmSignature.methodDesc, isDeclared())
|
||||||
is JavaMethod -> jvmSignature.method
|
is JavaMethod -> jvmSignature.method
|
||||||
is JavaConstructor -> jvmSignature.constructor
|
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)
|
is BuiltInFunction -> jvmSignature.getMember(container)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,9 +92,13 @@ internal class KFunctionImpl private constructor(
|
|||||||
}
|
}
|
||||||
is KotlinConstructor -> {
|
is KotlinConstructor -> {
|
||||||
if (isAnnotationConstructor)
|
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())
|
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 -> {
|
else -> {
|
||||||
// Java methods, Java constructors and built-ins don't have $default methods
|
// Java methods, Java constructors and built-ins don't have $default methods
|
||||||
null
|
null
|
||||||
|
|||||||
@@ -71,9 +71,15 @@ internal sealed class JvmFunctionSignature {
|
|||||||
|
|
||||||
class JavaConstructor(val constructor: Constructor<*>) : JvmFunctionSignature() {
|
class JavaConstructor(val constructor: Constructor<*>) : JvmFunctionSignature() {
|
||||||
override fun asString(): String =
|
override fun asString(): String =
|
||||||
"<init>" +
|
constructor.parameterTypes.joinToString(separator = "", prefix = "<init>(", postfix = ")V") { it.desc }
|
||||||
constructor.parameterTypes.joinToString(separator = "", prefix = "(", postfix = ")") { it.desc } +
|
}
|
||||||
"V"
|
|
||||||
|
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() {
|
open class BuiltInFunction(private val signature: String) : JvmFunctionSignature() {
|
||||||
@@ -189,10 +195,14 @@ internal object RuntimeTypeMapper {
|
|||||||
return JvmFunctionSignature.JavaMethod(method)
|
return JvmFunctionSignature.JavaMethod(method)
|
||||||
}
|
}
|
||||||
is JavaClassConstructorDescriptor -> {
|
is JavaClassConstructorDescriptor -> {
|
||||||
val constructor = ((function.source as? JavaSourceElement)?.javaElement as? ReflectJavaConstructor)?.member ?:
|
val element = (function.source as? JavaSourceElement)?.javaElement
|
||||||
throw KotlinReflectionInternalError("Incorrect resolution sequence for Java constructor $function")
|
when {
|
||||||
|
element is ReflectJavaConstructor ->
|
||||||
return JvmFunctionSignature.JavaConstructor(constructor)
|
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})")
|
else -> throw KotlinReflectionInternalError("Unknown origin of $function (${function.javaClass})")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user