I’m wondering about reflection in Kotlin. Suppose that I have the following sample code:
class ReflectionQuestion {
private lateinit var name: String
override fun toString(): String = "Hello $name!"
}
fun main(args: Array<String>) {
val l = ReflectionQuestion()
val result = ReflectionQuestion::class.memberProperties.find { it.name == "name" }
if (result is KMutableProperty1) {
result.setter.isAccessible = true
result.set(l, "Kotlin") // error
}
}
The error is:
Error:(18, 16) Kotlin: Out-projected type ‘KMutableProperty1<LateInitViolation, out Any?>’ prohibits the use of ‘public abstract fun set(receiver: T, value: R): Unit defined in kotlin.reflect.KMutableProperty1’
The problem is that KProperty<out R> conflicts with the derived classes KMutableProperty1<T, R>. This seems like a bug to me.
If this happened in my own code, what would be the proper way to resolve such a conflict? Actually, I didn’t even manage to create a minimal sample, because the following compiles:
interface Base<T, out R> {
fun get(obj:T): R
}
interface Derived<T, R> : Base<T, R> {
fun set(obj: T, value: R)
}
fun demo(d: Base<StringBuilder, String>) {
if (d is Derived) {
d.set(StringBuilder(), "String")
}
}
One other thing that I noticed: why is reflection so slow in Kotlin? I translated that minimal code example to Java and that runs substantially faster (1239974329 nanoSec in Kotlin vs. 1288528 nanoSec in Java, which is a factor of 1000).
The error message means that the compiler has no reason to assume that you can safely pass a string value to set: this property could have been declared with any other return type, and there’s just no information for the compiler to believe that its set accepts strings. This happens because result’s type after the is-check is KMutableProperty1<ReflectionQuestion, *>. To be allowed to assign strings to that property, you have to perform an unchecked cast to KMutableProperty1<ReflectionQuestion, String>:
if (result is KMutableProperty1) {
result as KMutableProperty1<ReflectionQuestion, String>
result.setter.isAccessible = true
result.set(l, "Kotlin") // ok
}
Re performance problems: thanks for the report. We didn’t spend a lot of time optimizing the current implementation of reflection in Kotlin yet. I hope this will be addressed soon.
Ah, and that is the difference between the two examples. One is KMutableProperty1<ReflectionQuestion, *> and the other is Derived<StringBuilder, String>. Which is logical because memberProperties usually contains properties with a number of different return types. Reading goes the other way around because Any? always is a safe bet for a return value.