Kotlin Generic Interfaces and type variance problem

52 views Asked by At

I come from Java so I'm relatively new with Kotlin. Yesterday afternoon I was trying to understand why cannot achieve this behaviour with type generics in Kotlin:

I have the following declarations:

data class Parent(val a : Double)
data class Child(val a : Double, val b : String) : Parent(a)
... more Parent derived classes


interface Foo<T : Parent> {
   fun setValue(v : T)
}


class ChildFooImpl : Foo<Child> {
   override fun setValue(v : Child) {
      ...
   }
}
... more Foo implementations with Parent derived types

Next, I need to have a map to store some Foo implementations indexed by class type:

val myStore : Map<KClass<out Parent>, Foo<out Parent>> =  mapOf(
   Child::class to ChildFooImpl(),
   ... more Pair creation
)

And here comes the problem: Why do I obtain a Type mismatch: inferred type is Child but Nothing was expected error when calling setValue() method of the retrieved item from map?

val theImpl = myStore[Child::class]
theImpl?.setValue(Child(0.0, "aaa"))  // <------- The error

theImpl has Foo<out Parent>? type so I don't figure out why is this happening. I read the Kotlin documentation page about Generics and Type Safety but cannot understand the issue whith the type projection.

Some clear explanation would be really appreciated.

1

There are 1 answers

0
Joffrey On

Nothing in the type of the myStore map guarantees any relationship between the type of the key in the map, and the type of the corresponding Foo value.

So, when you do val theImpl = myStore[Child::class], the only thing you know about theImpl is that it's a Foo<out Parent>. It could be a Foo<OtherChild> for instance, which only accepts OtherChild values, not any arbitrary value of other subtypes of Parent. So theImpl.setValue(...) is not safe to call with anything at all (thus Nothing is expected).

The usual way to deal with this would be to wrap this map into a custom type that ensures you control the values that are put in there. The custom function to insert elements would enforce that the key matches the generic type of the Foo value, and the custom getter function would perform an unchecked cast (that you know is safe) on the value it returns.