I am working on a Kotlin project where I have a function createPropertyReader that uses a single type parameter T to enforce type safety between a property and a read function. Here is the relevant code:
inline fun <R, reified T> createPropertyReader(
property: KProperty1<R, T>,
noinline readFunction: (R) -> T
): PropertyReader<R, T> {
return PropertyReader(property, readFunction)
}
class PropertyReader<R, T>(
val property: KProperty1<R, T>,
val readFunction: (R) -> T
)
The function is supposed to enforce that the type of property matches the type returned by readFunction. However, when I use it like this:
val x = createPropertyReader(User::id) { readString() }
It does not give a compile-time error, even though User::id
is of type Uuid
, and readString()
returns String
. Instead, Intellij shows the inferred type of x as PropertyReader<User, {Comparable<{String & UUID}> & Serializable}!>
.
I tried to write a generic function which takes a class property and a lambda function which returns a value that is assignable to the same property. However, I expected a compile-time safety which was not the case.
I expected a compile-time error because the types do not match. Adding reified to the type parameter T did not make any difference. Why does Kotlin allow this mismatch without a compile-time error? Is there a way to enforce stricter type checking in this scenario?
This is similar to the situation here.
{Comparable<{String & UUID}> & Serializable}!
is indeed a valid type for T
such that the call typechecks. It's just that you don't want it to find a type like this.
As I said in the linked answer, you just need the T
to appear in a parameter type of readFunction
. This adds additional constraints to what T
can be.
So you can just add a T?
parameter and pass null.
fun <R, T> createPropertyReader(
property: KProperty1<R, T>,
readFunction: (R, T?) -> T
): PropertyReader<R, T> {
return PropertyReader(property, { readFunction(it, null) })
}
Because readFunction
now has 2 parameters, this means that you can't use it
at the call site anymore. Consider using (Pair<R, T?>) -> T
if that's undesirable.