Allow us to safely extract KFunction's receiver or just it's type

Hi, for my project’s DSL, I want to implement a method that accepts a KFunction<T>, converts it to MethodHandle and creates a dispatcher object, that supplies all parameters and invokes the MethodHandle when needed.
Why a MethodHandle? Because I want to invoke the method with dynamic arguments and this should be much more performant then KFunction<T>.call(...) which (to my knowlege) utilizes reflection every time.

But when I create a method reference (KFunction<T>), the instance receiver and it’s type are hidden from me. This is not conveniet for my purposes. And that’s what DSL is about - convenience.

I found two possible ways to retrieve the receiver instance or just it’s class, but both ways are clunky and probably not very safe

open class MyInterface {
    fun exec() {
        myMethod("param")
    }

    open fun myMethod(param: String) {}
}

class MyClass() : MyInterface() {
    override fun myMethod(param: String) {
        println(param)
    }
}

fun <T> createDispatcher(function: KFunction<T>) {
    // refers to MyInterface, but I need MyClass. This makes sense, of course - I'm just addressing this option as not viable
    val receiverClass = function.javaMethod!!.declaringClass


    // Option 1 - only getting the receiver class name
    run {
        // KFunction<T>.toString()  this returns  `fun org.budgetbakers.api.handlers.MyClass.exec(): kotlin.Unit`,
        // which can be used to identify the receiver class at least. But having it as this solution is rather cumbersome.
        // Matching a class based on string and relying on the `toString` method is just bad..
        val methodfullName = function.toString()
        val receiverClassName = methodfullName.removePrefix("fun ")
            .split('(')
            .first().split('.')
            .dropLast(1)
            .joinToString(".")

        println("Receiver class name: $receiverClassName")

    }


    // Option 2 - obtaining the receiver via reflection and accessing internal class
    run {
        val getBoundReceiverInternalMethod =
            Class.forName("kotlin.jvm.internal.CallableReference").declaredMethods.first { it.name == "getBoundReceiver" }
        val receiver = getBoundReceiverInternalMethod.invoke(function)
        println("Receiver class name: ${receiver.javaClass.name}")
    } 
}

So I’m proposing, that KFunction is extended with a member that lets us retrieve the receiver or just it’s class


EDIT: I just discovered a third and probably the safest solution (though far from ideal)

package kotlin.jvm.internal
import kotlin.reflect.KFunction

object KotlinJvmUtils {
    fun getBoundReceiver(function: KFunction<*>) = run {
        function as? CallableReference ?: throw InternalError("")
        function.boundReceiver.takeIf { it != CallableReference.NO_RECEIVER }
    }
}

EDIT II: the third option won’t compile (the error is a bit vague, but it is clear that the package name is not allowed) , so it is not an option at all :slight_smile:

Why do you consider it far from ideal? I think this is exactly what we need to do. There are many different kinds of KFunction and only some of them have a concept of a receiver. So it feels reasonable to down cast.

1 Like

It is not because of the casting.
It is because I have to create the package kotlin.jvm.internal in my project, just so I can use CallableReference . It is not accesible from other packages.

The kotlin.jvm.internal is a package from the standard library. Ideally, it should be left untouched by modules, that do not come from the Kotlin language designers.

Ahh, yes, current reflection API is indeed sometimes pretty limited and it doesn’t expose some functionality. I think it would be nice if they expose things like CallableReference.

BTW, it would be even better if they generate a call(vararg args: Any?) function in this case. Please note if we have foo(String, Int) and we do: obj::foo then we get a KFunction implementation with functions: invoke(String, Int) and invoke(Any, Any). These functions are statically linked to the foo function, so they should be much faster than reflection or method handles. Unfortunately, compiler doesn’t provide a similar function accepting an array of arguments.

2 Likes

I’m completely lost on what you’re trying to achieve. Can you include an example of what you want to do, and what your desired outcome is?