kotlinreflectionkotlin-reflect

Access Implementation's property on variable of type Interface


I'm trying to access the delegate of the property (id) of a class (FooImpl). The problem is, this class implements an interface (Foo), and the property in question overrides a property of this interface. The delegate only exists in the class (not that it could exist in the interface).

The problem is that using the :: operator on a variable of type Foo always returns the property of Foo, not that of the actual instance. The problem in code:

import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
import kotlin.reflect.jvm.isAccessible

interface Foo {
    val id: Int
}

class FooImpl(
    id: Int,
) : Foo {
    override val id: Int by lazy { id }
}

val <T> KProperty<T>.hasDelegate: Boolean
    get() = apply { isAccessible = true }.let { (it as KProperty0<T>).getDelegate() != null }

fun main() {
    val foo: Foo = FooImpl(1)
    println("foo::id.hasDelegate = ${foo::id.hasDelegate}")
    println("(foo as FooImpl)::id.hasDelegate = ${(foo as FooImpl)::id.hasDelegate}")
}

This prints:

foo::id.hasDelegate = false
(foo as FooImpl)::id.hasDelegate = true

But this requires compile-time knowledge of the correct implementation. What I'm looking for is accessing the correct propert without having to specify FooImpl there.

The information is present at runtime because the least (!) intrusive workaround I have found so far is adding fun idProp(): KProperty0<*> to Foo and override fun idProp() = ::id to FooImpl and accessing the property using that.

Is there any better way than that?


Solution

  • The problem here is that the owner of the property is resolved on compile time, not on runtime. When you do foo::id then foo (so FooImpl) become its bound receiver, but owner is still resolved to Foo. To fix this we wound need to "cast" property to another owner. Unfortunately, I didn't find a straightforward way to do this.

    One solution I found is to use foo::class instead of foo::id as it resolves KClass on runtime, not on compile time. Then I came up with almost exactly the same code as @Tenfour04.

    But if you don't mind using Kotlin internals that are public and not protected with any annotation, you can use much cleaner solution:

    val KProperty0<*>.hasDelegate: Boolean
        get() = apply { isAccessible = true }.getDelegate() != null
    
    fun KProperty0<*>.castToRuntimeType(): KProperty0<*> {
        require(this is PropertyReference0)
        return PropertyReference0Impl(boundReceiver, boundReceiver::class.java, name, signature, 0)
    }
    
    fun main() {
        val foo: Foo = FooImpl(1)
        println(foo::id.castToRuntimeType().hasDelegate) // true
    }
    

    We basically create a new instance of KProperty, copying all its data, but changing the owner to the same type as its bound receiver. As a result, we "cast" it to the runtime type. This is much simpler and it is also cleaner because we separated property casting and checking for a delegate.

    Unfortunately, I think Kotlin reflection API is still missing a lot of features. There should be hasDelegate() function, so we don't have to provide receivers, which is not really needed to check if property is delegated. It should be possible to cast KProperty to another type. It should be possible to create bound properties with some API call. But first of all, it should be possible to do something like: Foo::id(foo), so create KProperty of the runtime type of foo. And so on.