scala / scala3

The Scala 3 compiler, also known as Dotty.
https://dotty.epfl.ch
Apache License 2.0
5.86k stars 1.06k forks source link

ClassCastException when matching on ValueOf #12366

Open OlivierBlanvillain opened 3 years ago

OlivierBlanvillain commented 3 years ago

Here is a variation of tests/run/i12194.scala that leads to a ClassCastException:

import scala.compiletime.package$package.summonAll

@main def Test(): Unit = {
  val list: List[ValueOf["foo"] | ValueOf["bar"]] =
    summonAll[Tuple.Map[("foo", "bar"), ValueOf]].toList

  println(list.map {
    case str: ValueOf[_] ⇒ str.value
    // java.lang.ClassCastException: scala.ValueOf cannot be cast to java.lang.String              
  })
}
abgruszecki commented 3 years ago

This doesn't look like a GADT problem. We infer no GADT constraints here. It seems like a problem with value classes perhaps? After typer, the tree looks like this:

    @main() def Test(): Unit = 
      {
        val list: List[ValueOf["foo".type] | ValueOf["bar".type]] = compiletime.summonAll[Tuple.Map[Tuple2["foo".type, "bar".type], ValueOf]].toList
        println(
          list.map[String](
            {
              def $anonfun(x$1: ValueOf[("foo" : String)] | ValueOf[("bar" : String)]): String = 
                x$1 match 
                  {
                    case str @ _:ValueOf[_ @  >: Nothing <: Any] => 
                      str.value:("foo" : String) | ("bar" : String)
                  }
              closure($anonfun)
            }
          )
        )
      }

After genBCode, we have this fragment:

    private final def Test$$anonfun$1(x$1: Object): String = 
      matchResult1[String]: 
        {
          case val x1: Object = x$1
          if x1.isInstanceOf[ValueOf] then 
            {
              case val str: Object = new ValueOf(x1.asInstanceOf[Object])
              {
                return[matchResult1] str.asInstanceOf[ValueOf].value().asInstanceOf[String]:String
              }
            }
           else ()
          throw new MatchError(x1)
        }
  }

Note the call to new ValueOf when creating val str, it looks like that is the source of the problem. A wrong-looking tree appears sooner, but it's too big to paste here.

I can keep debugging this in my spare time, but perhaps it'd be trivial to fix for someone with an understanding of how value types are desugared?

Additional data point:

scala> import scala.compiletime.package$package.summonAll                                                                                                                                                      
     | 
     | @main def Test(): Unit = {
     |   val list: List[ValueOf["foo"] | ValueOf["bar"]] =
     |     summonAll[Tuple.Map[("foo", "bar"), ValueOf]].toList
     | 
     |   println(list.map {
     |     case str: ValueOf[_] =>
     |       println(str)
     |       val str1 : ValueOf[_ <: String] = str
     |       println(str1)
     |       str1.value : String
     |     // java.lang.ClassCastException: scala.ValueOf cannot be cast to java.lang.String              
     |   })
     | }
def Test(): Unit

scala> Test()                                                                                                                                                                                                  
scala.ValueOf@18cc6
java.lang.ClassCastException: scala.ValueOf cannot be cast to java.lang.String
  at rs$line$9$.Test$$anonfun$1(rs$line$9:10)
  at scala.collection.immutable.List.map(List.scala:246)
  at rs$line$9$.Test(rs$line$9:14)
  ... 28 elided

Note how only one println happens.