I have the following code:
import kotlin.reflect.KProperty1
infix fun <T, R> KProperty1<T, R>.eq(value: R) {
println(this.name + " = $value")
}
infix fun <T, R> KProperty1<T, R>.eq(value: KProperty1<T, R>) {
println(this.name + " = " + value.name)
}
data class Person(val age: Int, val name: String, val surname: String?)
fun main() {
Person::age eq 1 // Valid. First function
Person::name eq "Johan" // Valid. First function
Person::name eq Person::name // Valid. Second function
Person::age eq 1.2f // Valid, but it shouldn't be. First function
Person::age eq Person::name // Valid, but it shouldn't be. First function
Person::surname eq null // Invalid, but it should be. Second function, but it should be first
}
I am trying create extension functions for the KProperty1
class with generics, but my generics are not matching as I'd expect. main()
lists some uses of the two functions, with 3 unexpected results at the end and a description of what I should expect.
First, note that
KProperty1<T, out R>
has anout
-projected type parameterR
, so an instance ofKProperty1<Foo, Bar>
can also be used where aKProperty1<Foo, Baz>
is expected, whereBaz
is a supertype ofBar
, such asAny
.This is exactly what happens when you call
eq
with types that don't quite match: a more common supertype is used so that the call resolves correctly.For example, this call:
is effectively equivalent to:
You can even transform it to this form automatically be applying Replace infix call with ordinary call and then Add explicit type arguments.
So whenever the types don't match exactly, a common supertype is chosen for
R
. Currently, there's no proper way to tell the compiler it should not fall back to the supertype (see this Q&A).The last call is a side effect of how type inference currently works: it seems like the compiler has to choose one of the overloads before it decides on whether it is compatible for nullability. If you replace
null
with(null as String?)
it works. Please file an issue for this at kotl.in/issue.In general, it is preferable not to mix the generic signatures with non-generic ones because of possible generic substitutions that lead to ambiguity. In your case, a substitution
R := KProperty1<T, Foo>
(i.e. usingeq
on a property of this type) would lead to ambiguity.Also relevant:
Kotlin - Generic Type Parameters Not Being Respected
(basically the same case, but with a more detailed explanation of how type inference works here)