Search code examples
kotlinreflectionjvmkotlin-reflect

Kotlin - obtaining an instance of both KProperty1 and CallableReference through reflection


I'm working with a third-party library that has a function which expects an argument of type KProperty1<T, V>, and casts it to an instance of CallableReference internally. I need to acquire a reference to a class member through reflection that is an instance of both KProperty1 and CallableReference.

The function in the third-party library looks something like this:

fun <T : Any, V> thirdPartyFunction(property: KProperty1<T, V>)  {
    val callableReference = (property as CallableReference)
}

Everything works when I call the function with a member reference obtained with the :: operator:

class ExampleClass(
    var exampleProperty: String,
)

// this works
thirdPartyFunction(ExampleClass::exampleProperty)

When I call the function with a value obtained through reflection, I get a ClassCastException:

val reflectedProperty = ExampleClass::class.memberProperties
    .find { it.name == "exampleProperty" } as KProperty1<ExampleClass, String>

// class kotlin.reflect.jvm.internal.KMutableProperty1Impl cannot be cast to class kotlin.jvm.internal.CallableReference
thirdPartyFunction(reflectedProperty)

I'm using Kotlin 1.9.21 on the JVM.


Solution

  • You can basically do what the compiler does when it encounters a property reference like this - create an instance of PropertyReference1Impl.

    The constructor takes 3 parameters

    • the class in which the property is declared
    • the property name
    • the JVM method signature of the getter

    Optionally, you can give it an additional flags parameter indicating whether it is synthetic in the Java world, and/or whether it is declared at the top level (not in a class). You can also give it a receiver if it is an extension property.

    Let's consider the simple case of:

    class Foo {
        val x = 1
    }
    

    For Foo::x, you would write:

    PropertyReference1Impl(Foo::class, "x", "getX()I")
    

    (The Kotlin compiler would actually create a new class inheriting from PropertyReference1Impl and override its get method to return the property value directly, instead of using reflection as the default implementation does.)

    Similar such classes exist for KProperty0, KProperty2, mutable properties, and functions. For an example of doing this with functions, see my answer here


    You can write a method like this to handle this more generally:

    inline fun <reified T, R> KProperty1<T, *>.asCallableReference(): KProperty1<T, R> {
        val getter = javaGetter ?: throw Exception("Must have a JVM getter for this to work!")
        return PropertyReference1Impl(
            T::class, name,
            getter.name + MethodType.methodType(getter.returnType)
                .toMethodDescriptorString()
        ) as KProperty1<T, R>
    }
    

    This assumes that the receiver came from the memberProperties collection of some KClass, and that it is not an extension property, and that it has a Java getter (not @JvmField) that takes no parameters. There are probably other assumptions being made here that aren't always true, but this should work for most properties.