TL;DR
The Kotlin compiler gives an error (type mismatch) here:
fun <T: A()> getUtil(t: T): Util<T> = if (t is B) UtilB() else // ...
With signature of class B
being: class B : A()
, class Util
being class Util<T: A>
and class UtilB
being class UtilB: Util<B>()
.
The Kotlin compiler gives a warning (unchecked cast) here:
fun <T: A()> getUtil(t: T): Util<T> = if (t is B) UtilB() as Util<T> else // ...
To my understanding Kotlin smart cast should know that UtilB() as Util<T>
is checked by t is B
.
Java code and compiler give the exact same result.
As far as I know this must be a limitation on Java generics. How can I fix this?
Problem description
I have the following setup where an abstract class has multiple implementations and a util class that provides the same functionality for each of those implementations.
To be type safe I figured I would create an abstract class Util<T: A>
and for each derived class of A
another UtilB: Util<B>
class.
To get the right util class for each implementation, I created a function on a companion object getUtil
which returns the correct util class for each implementation based on a parameter of generic type T which extends A: T: A
thus having return type Util<T>
.
However, when I wrote the function body for each derived class of A
by checking the type of the parameter with is B
and then returning the correct util with UtilB()
, the Kotlin compiler gave me an error at the return point saying that UtilB
isn't of type Util<T>
even though it should be.
I then casted UtilB
to Util<B>
and that worked but gave me an error "Unchecked cast". According to my understanding Kotlin smart cast should be able to figure out that it is indeed a valid checked cast (checked with is B
) and after running a quick test it turned out to be valid as well...
I rewrote the same code in Java with the exact same results...
As far as I know this is a limitation of Java/Kotlin generics. I would like to know how I can check this cast. Is it even possible?
Code
Here is a minimal working (or not working) example:
abstract class A
class B : A()
class C : A()
abstract class Util<T : A> {
abstract fun getName(): String
companion object {
fun <T : A> getUtil(t: T): Util<T> = when(t) {
is B -> UtilB() as Util<T> // warning
is C -> UtilC() // this event gives an error
else -> throw IllegalArgumentException("No util for this class.")
}
}
}
class UtilB : Util<B>() {
override fun getName(): String = "B"
}
class UtilC : Util<C>() {
override fun getName(): String = "C"
}
fun main() {
val b = B()
val c = C()
val utilB = Util.getUtil(b)
val utilC = Util.getUtil(c)
println(utilB.getName()) // prints B
println(utilC.getName()) // prints C
}