scala / bug

Scala 2 bug reports only. Please, no questions — proper bug reports only.
https://scala-lang.org
232 stars 21 forks source link

ClassCastException: ConcreteNode cannot be cast to scala.runtime.Null$ #8634

Open scabug opened 10 years ago

scabug commented 10 years ago

The following code will fail at runtime with a ClassCastException triggered by identity(<value>) in processContainer().

trait SelfTyped {
  type Self
}

sealed trait Container { self =>
  type Child = SelfTyped { type Self <: self.ChildSelf }
  type ChildSelf
  def children: List[Child]
}

sealed trait ExampleChild extends SelfTyped

case class Example (children: List[Example#Child]) extends Container {
  type Self = Example
  type ChildSelf = ExampleChild
}

sealed trait AbstractNode extends ExampleChild { }
sealed case class ConcreteNode () extends AbstractNode { override type Self = ConcreteNode }

object ProofOfConcept {
  def processContainer [C <: Container] (container: C): Unit = {
    container.children.foreach {
      case n: ConcreteNode =>
        identity(n) // java.lang.ClassCastException: ConcreteNode cannot be cast to scala.runtime.Null$
      case _ =>
    }
  }
}
ProofOfConcept.processContainer(Example(List(ConcreteNode())))
scabug commented 10 years ago

Imported From: https://issues.scala-lang.org/browse/SI-8634?orig=1 Reporter: Landon Fuller (landonf) Affected Versions: 2.10.4, 2.11.0

scabug commented 10 years ago

@retronym said: Taking a quick look at this:

A slightly leaner test case:

trait SelfTyped {
  type Self
}

sealed trait Container { self =>
  type Child = SelfTyped { type Self <: self.ChildSelf }
  type ChildSelf
  def child: Child
}

sealed trait ExampleChild extends SelfTyped

case class Example (child: Example#Child) extends Container {
  type Self = Example
  type ChildSelf = ExampleChild
}

sealed trait AbstractNode extends ExampleChild { }
sealed case class ConcreteNode () extends AbstractNode { override type Self = ConcreteNode }

object ProofOfConcept {
  def processContainer(container: Container): Unit = {
    val c: container.Child = container.child
    c match {
      case n: ConcreteNode => // java.lang.ClassCastException: ConcreteNode cannot be cast to scala.runtime.Null$
        n
    }
  }
}
object Test extends App {
  ProofOfConcept.processContainer(Example(ConcreteNode()))
}

Internally, I think the failure comes from our imprecise LUB/GLB computation:

this = {scala.tools.nsc.transform.patmat.MatchTranslation$MatchTranslator$BoundTree@3642}"x1: <empty>.this.SelfTyped{type Self <: container.ChildSelf} with <empty>.this.ConcreteNode{} (binder: <empty>.this.SelfTyped{type Self <: container.ChildSelf}) @ (_: <empty>.this.ConcreteNode)"

// private def typeTestStep(sub: Symbol, subPt: Type)     = step(TypeTestTreeMaker(sub, binder, subPt, glbWith(subPt))(pos))()

glb(
  SelfTyped{type Self <: container.ChildSelf} with ConcreteNode{},
  container.Child
) = Null    

A workaround is to upcast the scrutinee of the pattern match to Any before the type test on _: ConcreteNode.