Generic extensions of KProperty1 in Kotlin

青春壹個敷衍的年華 提交于 2019-12-12 11:23:51

问题


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.


回答1:


First, note that KProperty1<T, out R> has an out-projected type parameter R, so an instance of KProperty1<Foo, Bar> can also be used where a KProperty1<Foo, Baz> is expected, where Baz is a supertype of Bar, such as Any.

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:

Person::age eq 1.2f

is effectively equivalent to:

Person::age.eq<Person, Any>(1.2f)

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. using eq 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)



来源:https://stackoverflow.com/questions/52717977/generic-extensions-of-kproperty1-in-kotlin

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!