scalatype-erasuretype-systems

Generic function output for covariance type is wrong, is it a type system bug or expected?


Consider

trait Fruit { 
  val name: String
}
case class Apple(override val name: String) extends Fruit
case class Banana(override val name: String) extends Fruit

When I define a function to collect only specific types

def onlyT[T](list: List[Fruit]): List[T] = list.collect { { case x: T => x } }
def onlyT2[T: ClassTag](list: List[Fruit]): List[T] = list.collect { { case x: T => x } }
val fruitList: List[Fruit] = List(Apple("app1"), Apple("app2"), Apple("app3"), Apple("app4"), Banana("ban1"), Banana("ban2"), Banana("ban3"))

And below val a is of type List[Banana] but contains entries of type Apple.

val a: List[Banana] = onlyT[Banana](fruitList)
val b: List[Banana] = fruitList.collect { case x: Banana => x }
val c: List[Banana] = onlyT2[Banana](fruitList)
println(a) // List(Apple(app1), Apple(app2), Apple(app3), Apple(app4), Banana(ban1), Banana(ban2), Banana(ban3))
println(b) // List(Banana(ban1), Banana(ban2), Banana(ban3))
println(c) // List(Banana(ban1), Banana(ban2), Banana(ban3))

Is this expected? Or is it a typesystem bug? I know at runtime type is erased, but not sure why it works for b but not a? I think the compiler should not allow a if it will be like this. Anything I am missing?

FYI, this is not a question to solve the issue I can resolve it by using ClassTag. The question is: Why is scala compiler allowing the type declaration val a: List[Banana] and we end up with a list of apples and bananas of type List[Banana]


Solution

  • This behavior is a consequence of type erasure.

    So... In Java any F[T] is simplified to F[Object] at runtime.

    Similarly your method

    def onlyT[T](list: List[Fruit]): List[T] = 
      list.collect { { case x: T => x } }
    

    is actually simplified to

    def onlyT(list: List[Fruit]): List[Object] = 
      list.collect { { case x: Object => x } }
    

    Which is why everything passes the filter when you call onlyT[Banana](fruitList).

    No such type erasure occurs in case of fruitList.collect { case x: Banana => x }.