Support Kotlin annotation constructors in reflection

#KT-13106 In Progress
This commit is contained in:
Alexander Udalov
2016-09-14 19:52:57 +03:00
parent fb18e4bf4c
commit aeba124bdb
12 changed files with 344 additions and 14 deletions
@@ -0,0 +1,21 @@
// WITH_REFLECT
import kotlin.reflect.*
import kotlin.test.assertEquals
annotation class A1
annotation class A2(val k: KClass<*>, val s: A1)
fun box(): String {
assertEquals(1, A1::class.constructors.size)
assertEquals(A1::class.primaryConstructor, A1::class.constructors.single())
val cs = A2::class.constructors
assertEquals(1, cs.size)
assertEquals(A2::class.primaryConstructor, cs.single())
val params = cs.single().parameters
assertEquals(listOf("k", "s"), params.map { it.name })
return "OK"
}
@@ -1,10 +1,8 @@
// WITH_REFLECT
import kotlin.reflect.*
import kotlin.test.assertTrue
interface Interface
annotation class Anno(val x: Int)
object Obj
class C {
@@ -13,7 +11,6 @@ class C {
fun box(): String {
assertTrue(Interface::class.constructors.isEmpty())
assertTrue(Anno::class.constructors.isEmpty())
assertTrue(Obj::class.constructors.isEmpty())
assertTrue(C.Companion::class.constructors.isEmpty())
@@ -0,0 +1,12 @@
// WITH_REFLECT
import kotlin.reflect.KClass
import kotlin.test.assertEquals
annotation class Anno(val klasses: Array<KClass<*>> = arrayOf(String::class, Int::class))
fun box(): String {
val anno = Anno::class.constructors.single().callBy(emptyMap())
assertEquals(listOf(String::class, Int::class), (anno.klasses as Array<KClass<*>>).toList() /* TODO: KT-9453 */)
return "OK"
}
@@ -0,0 +1,50 @@
// WITH_REFLECT
import kotlin.reflect.KClass
import kotlin.reflect.primaryConstructor
import kotlin.test.assertEquals
import kotlin.test.assertFails
annotation class NoParams
annotation class OneDefault(val s: String = "OK")
annotation class OneNonDefault(val s: String)
annotation class TwoParamsOneDefault(val s: String, val x: Int = 42)
annotation class TwoParamsOneDefaultKClass(val string: String, val klass: KClass<*> = Number::class)
annotation class TwoNonDefaults(val string: String, val klass: KClass<*>)
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")) }
val t5 = create<TwoParamsOneDefaultKClass>(mapOf("string" to "OK"))
assertEquals(Number::class, t5.klass as KClass<*> /* TODO: KT-9453 */)
assertFails("KClass (not Class) instances should be passed as arguments") {
create<TwoNonDefaults>(mapOf("klass" to String::class.java, "string" to "Fail"))
}
val t6 = create<TwoNonDefaults>(mapOf("klass" to String::class, "string" to "OK"))
return t6.string
}
@@ -0,0 +1,35 @@
// WITH_REFLECT
import kotlin.reflect.KClass
import kotlin.reflect.primaryConstructor
import kotlin.test.assertEquals
import kotlin.test.assertFails
annotation class NoParams
annotation class OneDefault(val s: String = "Fail")
annotation class TwoNonDefaults(val string: String, val klass: KClass<*>)
inline fun <reified T : Annotation> create(vararg args: Any?): T =
T::class.constructors.single().call(*args)
fun box(): String {
create<NoParams>()
assertFails { create<NoParams>("Fail") }
assertFails { create<OneDefault>() }
assertFails { create<OneDefault>(42) }
val o = create<OneDefault>("OK")
assertEquals("OK", o.s)
assertFails("call() should fail because arguments were passed in an incorrect order") {
create<TwoNonDefaults>(Any::class, "Fail")
}
assertFails("call() should fail because KClass (not Class) instances should be passed as arguments") {
create<TwoNonDefaults>("Fail", Any::class.java)
}
val k = create<TwoNonDefaults>("OK", Int::class)
assertEquals(Int::class, k.klass as KClass<*> /* TODO: KT-9453 */)
return k.string
}
@@ -0,0 +1,48 @@
// WITH_REFLECT
// FULL_JDK
import java.lang.reflect.GenericArrayType
import java.lang.reflect.ParameterizedType
import kotlin.reflect.KClass
import kotlin.reflect.jvm.javaType
import kotlin.test.assertEquals
import kotlin.test.assertTrue
annotation class Z
enum class E
annotation class Anno(
val b: Byte,
val s: String,
val ss: Array<String>,
val z: Z,
val zs: Array<Z>,
val e: E,
val es: Array<E>,
val k: KClass<*>,
val ka: Array<KClass<*>>
)
fun tmp(): Array<Class<*>> = null!!
fun box(): String {
val t = Anno::class.constructors.single().parameters.map { it.type.javaType }
assertEquals(Byte::class.java, t[0])
assertEquals(String::class.java, t[1])
assertEquals(Array<String>::class.java, t[2])
assertEquals(Z::class.java, t[3])
assertEquals(Array<Z>::class.java, t[4])
assertEquals(E::class.java, t[5])
assertEquals(Array<E>::class.java, t[6])
assertTrue(t[7] is ParameterizedType)
assertEquals(Class::class.java, (t[7] as ParameterizedType).rawType)
assertTrue(t[8] is GenericArrayType)
val e = (t[8] as GenericArrayType).genericComponentType
assertTrue(e is ParameterizedType)
assertEquals(Class::class.java, (e as ParameterizedType).rawType)
return "OK"
}
@@ -12111,6 +12111,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/constructors"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("annotationClass.kt")
public void testAnnotationClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/constructors/annotationClass.kt");
doTest(fileName);
}
@TestMetadata("classesWithoutConstructors.kt")
public void testClassesWithoutConstructors() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/constructors/classesWithoutConstructors.kt");
@@ -12136,6 +12142,33 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
}
}
@TestMetadata("compiler/testData/codegen/box/reflection/createAnnotation")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class CreateAnnotation extends AbstractBlackBoxCodegenTest {
public void testAllFilesPresentInCreateAnnotation() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/createAnnotation"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("arrayOfKClasses.kt")
public void testArrayOfKClasses() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/arrayOfKClasses.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("callKotlin.kt")
public void testCallKotlin() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/callKotlin.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/box/reflection/enclosing")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -12614,6 +12647,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/reflection/mapping/types"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("annotationConstructorParameters.kt")
public void testAnnotationConstructorParameters() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/mapping/types/annotationConstructorParameters.kt");
doTest(fileName);
}
@TestMetadata("array.kt")
public void testArray() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/mapping/types/array.kt");
@@ -0,0 +1,95 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlin.reflect.jvm.internal
import org.jetbrains.kotlin.load.java.structure.reflect.wrapperByPrimitive
import java.lang.reflect.Proxy
import kotlin.reflect.KClass
import kotlin.reflect.KotlinReflectionInternalError
import java.lang.reflect.Method as ReflectMethod
internal class AnnotationConstructorCaller(
private val jClass: Class<*>,
private val parameterNames: List<String>,
private val callMode: CallMode,
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 }
// 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 }
override fun call(args: Array<*>): Any? {
checkArguments(args)
val values = args.mapIndexed { index, arg ->
val value =
if (arg == null && callMode == CallMode.CALL_BY_NAME) defaultValues[index]
else arg.transformKotlinToJvm(erasedParameterTypes[index])
value ?: throwIllegalArgumentType(index, parameterNames[index], erasedParameterTypes[index])
}
return createAnnotationInstance(jClass, parameterNames.zip(values).toMap())
}
}
/**
* Transforms a Kotlin value to the one required by the JVM, e.g. KClass<*> -> Class<*> or Array<KClass<*>> -> Array<Class<*>>.
* Returns `null` in case when no transformation is possible (an argument of an incorrect type was passed).
*/
private fun Any?.transformKotlinToJvm(expectedType: Class<*>): Any? {
@Suppress("UNCHECKED_CAST")
val result = when (this) {
is Class<*> -> return null
is KClass<*> -> this.java
is Array<*> -> when {
this.isArrayOf<Class<*>>() -> return null
this.isArrayOf<KClass<*>>() -> (this as Array<KClass<*>>).map(KClass<*>::java).toTypedArray()
else -> this
}
else -> this
}
return if (expectedType.isInstance(result)) result else null
}
private fun throwIllegalArgumentType(index: Int, name: String, expectedJvmType: Class<*>): Nothing {
val kotlinClass = when {
expectedJvmType == Class::class.java -> KClass::class
expectedJvmType.isArray && expectedJvmType.componentType == Class::class.java ->
@Suppress("CLASS_LITERAL_LHS_NOT_A_CLASS") Array<KClass<*>>::class // Workaround KT-13924
else -> expectedJvmType.kotlin
}
// For arrays, also render the type argument in the message, e.g. "... not of the required type kotlin.Array<kotlin.reflect.KClass>"
val typeString = when {
kotlinClass.qualifiedName == Array<Any>::class.qualifiedName ->
"${kotlinClass.qualifiedName}<${kotlinClass.java.componentType.kotlin.qualifiedName}>"
else -> kotlinClass.qualifiedName
}
throw IllegalArgumentException("Argument #$index $name is not of the required type $typeString")
}
private fun createAnnotationInstance(annotationClass: Class<*>, values: Map<String, Any>): Any {
return Proxy.newProxyInstance(annotationClass.classLoader, arrayOf(annotationClass)) { proxy, method, args ->
// TODO: support equals, hashCode, toString, annotationType
values[method.name] ?: throw KotlinReflectionInternalError("Method is not supported: $method (args: ${args.orEmpty().toList()})")
}
}
@@ -23,7 +23,7 @@ import java.lang.reflect.Constructor as ReflectConstructor
import java.lang.reflect.Field as ReflectField
import java.lang.reflect.Method as ReflectMethod
internal abstract class FunctionCaller<out M : Member>(
internal abstract class FunctionCaller<out M : Member?>(
internal val member: M,
internal val returnType: Type,
internal val instanceClass: Class<*>?,
@@ -45,7 +45,7 @@ internal abstract class FunctionCaller<out M : Member>(
}
protected fun checkObjectInstance(obj: Any?) {
if (obj == null || !member.declaringClass.isInstance(obj)) {
if (obj == null || !member!!.declaringClass.isInstance(obj)) {
throw IllegalArgumentException("An object member requires the object instance passed as the first argument.")
}
}
@@ -27,8 +27,10 @@ import kotlin.reflect.jvm.javaType
internal abstract class KCallableImpl<out R> : KCallable<R> {
abstract val descriptor: CallableMemberDescriptor
// The instance which is used to perform a positional call, i.e. `call`
abstract val caller: FunctionCaller<*>
// The instance which is used to perform a call "by name", i.e. `callBy`
abstract val defaultCaller: FunctionCaller<*>?
abstract val container: KDeclarationContainerImpl
@@ -94,7 +96,7 @@ internal abstract class KCallableImpl<out R> : KCallable<R> {
override val isAbstract: Boolean
get() = descriptor.modality == Modality.ABSTRACT
private val isAnnotationConstructor: Boolean
protected val isAnnotationConstructor: Boolean
get() = name == "<init>" && container.jClass.isAnnotation
@Suppress("UNCHECKED_CAST")
@@ -102,8 +104,12 @@ internal abstract class KCallableImpl<out R> : KCallable<R> {
return caller.call(args) as R
}
// See ArgumentGenerator#generate
override fun callBy(args: Map<KParameter, Any?>): R {
return if (isAnnotationConstructor) callAnnotationConstructor(args) else callDefaultMethod(args)
}
// See ArgumentGenerator#generate
private fun callDefaultMethod(args: Map<KParameter, Any?>): R {
val parameters = parameters
val arguments = ArrayList<Any?>(parameters.size)
var mask = 0
@@ -153,6 +159,25 @@ internal abstract class KCallableImpl<out R> : KCallable<R> {
}
}
private fun callAnnotationConstructor(args: Map<KParameter, Any?>): R {
val arguments = parameters.map { parameter ->
when {
args.containsKey(parameter) -> {
args[parameter] ?: throw IllegalArgumentException("Annotation argument value cannot be null ($parameter)")
}
parameter.isOptional -> null
else -> throw IllegalArgumentException("No argument provided for a required parameter: $parameter")
}
}
val caller = defaultCaller ?: throw KotlinReflectionInternalError("This callable does not support a default call: $descriptor")
@Suppress("UNCHECKED_CAST")
return reflectionCall {
caller.call(arguments.toTypedArray()) as R
}
}
private fun defaultPrimitiveValue(type: Type): Any? =
if (type is Class<*> && type.isPrimitive) {
when (type) {
@@ -170,10 +170,10 @@ internal class KClassImpl<T : Any>(override val jClass: Class<T>) : KDeclaration
override val constructorDescriptors: Collection<ConstructorDescriptor>
get() {
val descriptor = descriptor
if (descriptor.kind == ClassKind.CLASS || descriptor.kind == ClassKind.ENUM_CLASS) {
return descriptor.constructors
if (descriptor.kind == ClassKind.INTERFACE || descriptor.kind == ClassKind.OBJECT) {
return emptyList()
}
return emptyList()
return descriptor.constructors
}
override fun getProperties(name: Name): Collection<PropertyDescriptor> =
@@ -26,6 +26,8 @@ import java.lang.reflect.Modifier
import kotlin.jvm.internal.FunctionImpl
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.JvmFunctionSignature.*
internal class KFunctionImpl private constructor(
@@ -48,10 +50,14 @@ internal class KFunctionImpl private constructor(
private fun isDeclared(): Boolean = Visibilities.isPrivate(descriptor.visibility)
override val caller: FunctionCaller<*> by ReflectProperties.lazySoft {
override val caller: FunctionCaller<*> by ReflectProperties.lazySoft caller@ {
val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor)
val member: Member? = when (jvmSignature) {
is KotlinConstructor -> container.findConstructorBySignature(jvmSignature.constructorDesc, isDeclared())
is KotlinConstructor -> {
if (isAnnotationConstructor)
return@caller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, POSITIONAL_CALL)
container.findConstructorBySignature(jvmSignature.constructorDesc, isDeclared())
}
is KotlinFunction -> container.findMethodBySignature(jvmSignature.methodName, jvmSignature.methodDesc, isDeclared())
is JavaMethod -> jvmSignature.method
is JavaConstructor -> jvmSignature.constructor
@@ -71,14 +77,16 @@ internal class KFunctionImpl private constructor(
}
}
override val defaultCaller: FunctionCaller<*>? by ReflectProperties.lazySoft {
override val defaultCaller: FunctionCaller<*>? by ReflectProperties.lazySoft defaultCaller@ {
val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor)
val member: Member? = when (jvmSignature) {
is KotlinFunction -> {
container.findDefaultMethod(jvmSignature.methodName, jvmSignature.methodDesc,
!Modifier.isStatic(caller.member.modifiers), isDeclared())
!Modifier.isStatic(caller.member!!.modifiers), isDeclared())
}
is KotlinConstructor -> {
if (isAnnotationConstructor)
return@defaultCaller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, CALL_BY_NAME)
container.findDefaultConstructor(jvmSignature.constructorDesc, isDeclared())
}
else -> {