Categories
Mastering Development

Kotlin smart-cast assumes too much?

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

Leave a Reply

Your email address will not be published. Required fields are marked *