kotlinreflection

"Could not compute caller for function": How can I call "plus" reflectively?


In function f I want to sum two values a.x and b.x:

class C<T>(var x: T)

inline fun <reified T> f(a: C<T>, b: C<T>): T? {
    val c = T::class
    val members = c.members.groupBy { it.name }
    for (m in members["plus"]!!) {
        if (m.parameters[1].type == Int::class.createType()) {
            return m.call(a.x, b.x) as T?
        }
    }

    return null
}

fun main(args: Array<String>) {
    val a = C(1)
    val b = C(2)
    println(f(a, b))
}

Error:

Exception in thread "main" kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Could not compute caller for function: @kotlin.internal.IntrinsicConstEvaluation public final operator fun plus(other: kotlin.Int): kotlin.Int defined in kotlin.Int[DeserializedSimpleFunctionDescriptor@2f4205be] (member = null)
    at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:98)
    at kotlin.reflect.jvm.internal.KFunctionImpl$caller$2.invoke(KFunctionImpl.kt:64)
    at kotlin.SafePublicationLazyImpl.getValue(LazyJVM.kt:107)
    at kotlin.reflect.jvm.internal.KFunctionImpl.getCaller(KFunctionImpl.kt:64)
    at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:108)
    at MainKt.main(Main.kt:66)

Solution

  • This is currently not possible. Int::plus doesn't actually exist, in the sense that there is no JVM method corresponding to it. You cannot call Int::plus.

    When something like someInt + anotherInt is compiled, it doesn't get translated to a call to Int::plus. It's just translated to an iadd JVM instruction directly.

    At the end of the day, Kotlin reflection tries to find a corresponding JVM method, so when it cannot find one, it throws a KotlinReflectionInternalError. See the implementation in KFunctionImpl

    val member: Member? = when (val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor)) {
        // ...
        is KotlinFunction -> {
            // ...
            container.findMethodBySignature(jvmSignature.methodName, jvmSignature.methodDesc) as Member?
        }
        // ...
    }
    
    when (member) {
        is Constructor<*> -> // ...
        is Method -> // ...
        else -> throw KotlinReflectionInternalError("Could not compute caller for function: $descriptor (member = $member)")
    }.createValueClassAwareCallerIfNeeded(descriptor)
    

    You can see that the Kotlin function is first mapped to its JVM signature, and then container.findMethodBySignature is used to find a JVM method with that signature in Int. Such a method doesn't exist, so container.findMethodBySignature returns null, and the exception is thrown in the second when.

    There are already bug reports reporting this issue. See KT-13077 (and its related issues) for example. The specific case mentioned in KT-13077 (Int::toByte) does have something sort of like a JVM counterpart (java.lang.Integer::byteValue), and also they are using a callable reference expression, which makes the exception message a bit different from yours.

    Either way, this is something Kotlin reflection cannot do yet.