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))