Issue
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.
Answered By - broot
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.