I was reading here: https://kotlinlang.org/docs/reference/typecasts.html#type-erasure-and-generic-type-checks
When I came upon this:
fun handleStrings(list: List<String>) {
if (list is ArrayList) {
// `list` is smart-cast to `ArrayList<String>`
}
}
And I had a nagging feeling that the smart-cast might be assuming too much about the generic parameter that it's casting too. AKA, how does it know that list is ArrayList<String> as opposed to ArrayList<some subtype of String>
I then wrote the following, which is a silly example but doesn't it violate the invariance of class B's parameter?
fun main(args: Array<String>) {
val test:B<String> = B()
val dest:A<Any> = test
if (dest is B) {
val flaw:B<Any> = dest
}
}
open class A<out T>
class B<T>:A<T>() {
fun consumer(inbound: T) = Any() //the return is irrelevant
}
Basically what I tried to do above is leverage the out descriptor on class A to assign a String parameterized object to an Any parameterized variable, and then used smart-cast to make it a class B object again.
Edit: Tenfour04 has commented with a great continuation of my example:
fun main() {
val test:B<String> = B()
val dest:A<Any> = test
if (dest is B) {
val flaw: B<Any> = dest
flaw.consume(5)
val x: String = test.produce()
}
}
open class A<out T>
class B<T>:A<T>() {
val list = mutableListOf<T>()
fun consume(inbound: T) {
list.add(inbound)
}
fun produce():T = list.first()
}