Home > Enterprise >  Generic parameter resolution without looking for super type in Kotlin
Generic parameter resolution without looking for super type in Kotlin

Time:08-02

I'm creating a property mapper. I'd like to be able to specify an associative relationship between KProperty and it's value. I'd like to have something like that:

infix fun <TProperty> KProperty1<out Changeable, TProperty>.to(that: TProperty) = Pair(this, that)

but for the following class:

interface Changeable
data class Person(val name: String, val age: Number, val salary: Double) : Changeable

when I declare potentially faulty map:

val entry = (Person::age to "9999") //String instead of Number

this type is resolved to:

Pair<KProperty1<out Changeable, Serializable>, Serializable>

While it looks all nice and compiles, this is dangerous in runtime. How can I restrict TProperty to be exactly the same type as KProperty1<out Changeable, TProperty> without looking for super type (Serializable instead of Int)

CodePudding user response:

There is an internal annotation @InputTypesOnly that solves exactly this problem. It causes type inference to fail if the inferred type (like Serializable) is not any of the type parameters. However, you cannot use it for obvious reasons. Follow KT-13198, which is about making it public.

However, there seems to be a trick that you can do (I learned this from here) - if you put the type you want "correctly" inferred (TProperty) into both a lambda parameter and lambda return position, then it does get "corretly" inferred.

infix fun <TProperty> KProperty1<out Changeable, TProperty>.to(
    that: (TProperty) -> TProperty) =
    Pair(this, that(TODO()))

val entry = Person::age to { "" } // error
val entry = Person::age to { 1 } // ok

I guess by having TProperty in both positions, the compiler adds enough constraints for it to stay "fixed".

Of course, we have no instances of TProperty to pass to the that lambda, so a better idea would be to use the receiver's type KProperty1<out Changeable, TProperty>:

infix fun <TProperty> KProperty1<out Changeable, TProperty>.to(
    that: (KProperty1<out Changeable, TProperty>) -> TProperty) = 
    Pair(this, that(this))

Note that with this signature, entry would have a type of:

Pair<KProperty1<out Changeable, Number>, Number>

If you want

Pair<KProperty1<Person, Number>, Number>

instead, you should add another type parameter for the first type argument of KProperty1 rather than using out Changeable:

infix fun <TReceiver: Changeable, TProperty> KProperty1<TReceiver, TProperty>.to(
    that: (KProperty1<TReceiver, TProperty>) -> TProperty) = 
    Pair(this, that(this))
  • Related