Focus on the strong and generic parts.
Let's say I have this extension function:
fun Collection.myContains(item: E) : Boolean {
// quite pointless, I know, but a simple example
return item in this
}
the intention is to write a function that only accepts types of the collection elements (E), but this is not validated by the compiler?!
val isItInside: Boolean = listOf(1, 2).myContains("1")
happily compiles. My guess is that E is inferred to be Any.
How can I enforce this restriction within the Kotlin type system/generics?
(Kotlin version 1.3.41)
Original context
An exercise to try to write a small assertion framework. A bit more complicated, but tried to get the simplest repro above.
class Asserter(val value: T)
infix fun T.should(block: Asserter.() -> Unit) =
Asserter(this).block()
fun > Asserter.haveSize(size: Int) {
check(this.value.size == size) {
"expecting ${this.value} to be of size $size"
}
}
fun > Asserter.contain(item: E) {
check(item in this.value) {
"$item does not exist in $item"
}
}
class ShouldTest {
@Test fun intList() {
listOf(1, 2) should {
haveSize(2)
contain(1)
contain("2") // this shouldn't compile
}
}
@Test fun stringList() {
listOf("1", "2") should {
haveSize(2)
contain(1) // this shouldn't compile
contain("2")
}
}
}
解决方案
This appears to be due to the variance on the Collection interface's parameter, which is defined as Collection.
This means that Collection is a supertype of Collection, and so (apparently) the Collection.myContains() extension can be called on a Collection.
You can confirm this by replacing it with the invariant MutableCollection (and also the listOf() with mutableListOf()); you then get the compile-time ‘Type mismatch’ error as expected.
This surprised me, though. I guess the compiler must infer E using both the receiver type and the parameter type. (Can anyone confirm this?) And, as you point out, it has the annoying effect of preventing stricter type-safety.