I just noticed something in Kotlin design that concerns me. is
and as
operators are unsafe to use with reified types, if the reified type is parameterized. Let’s see this example:
fun Any?.fixedCast(): List<String>? {
return this as? List<String> // unchecked cast warning
return if (this is List<String>) this else null // compile error
}
inline fun <reified T> Any?.reifiedCast(): T? {
return this as? T // no warnings/errors
return if (this is T) this else null // no warnings/errors
}
val list = listOf(1, 2, 3)
.reifiedCast<List<String>>() // no warnings/errors
val item = list?.first() // ClassCastException
This case is even mentioned in the docs here: Type checks and casts | Kotlin
I know about the type erasure and I understand what is the problem here. I’m concerned because we don’t even get any warning here. And because of this the problem may very easily propagate to other functions, making them unsafe as well. filterIsInstance()
is one example. This function is unsafe to use with parameterized types, even despite the fact that it does not suppress any unchecked casts:
val list = mapOf("foo" to "bar", "baz" to 5).entries
.filterIsInstance<Map.Entry<String, String>>()
val item = list[1].value // ClassCastException
I also understand that showing a warning here isn’t that easy, because there are two conditions for this problem to occur. One of them happens on the def-site of the inline function and another on its use-site. Maybe we could fix this problem by flagging reified types as unsafe to use with parameterized types - either explicitly by the developer or implicitly by detecting is
/as
. Then on use-site compiler would check whether we passed parameterized type as reified type and show a warning or compile error?
Does it make any sense? Are there any further problems or side-effects I don’t see? Should I report this on YouTrack?