bug
bug copied to clipboard
"Error while emitting" from scalac on pattern matching with AnyVal result
Reproduction steps
openjdk 17 and 21 scala version 2.13.10 and 2.13.14 and 2.13.15 sbt version 1.10.1 and 1.10.2
build.sbt
scalaVersion := "2.13.14" // or other 2.13.x versions
trait ParentTrait extends Any {
def value: String
}
class Child2(val value: String) extends AnyVal with ParentTrait
object Sample {
def test( sample: String): ParentTrait =
sample match {
case "0" if true => ???
case _ => new Child2(???)
}
}
I also tested with case classes and sealed traits.
Also, the predicate in if statement (true in this sample code) can be anything, the issue will be the same.
Problem
Compilation fails with unhelpful message:
[info] compiling 1 Scala source to .../target/scala-2.13/classes ...
[error] Error while emitting Sample$
[error] Index 0 out of bounds for length 0
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
And sometimes
[info] compiling 1 Scala source to .../target/scala-2.13/classes ...
[error] Error while emitting Sample$
[error] null
[error] one error found
[error] (Compile / compileIncremental) Compilation failed
Thanks @iogrm for the minimization!
Turns out this is very similar to https://github.com/scala/scala/pull/10775 / https://github.com/scala/bug/issues/12990 which I recently looked at. I simplified the example a tiny bit:
class C(val x: String) extends AnyVal
abstract class Test {
def c: C
def test(v: Int): Any =
v match {
case 1 if true => c
case _ => c
}
}
AST after patmat:
<method> def test(v: scala.this.Int): scala.this.Any = {
case <synthetic> val x1: scala.this.Int = v;
x1 match {
case 1 => if (true)
Test.this.c
else
default3()
case _ => default3(){
Test.this.c
}
}
}
The match tree is left in place because it's a match that can be translated to a switch bytecode by the backend. In the original example, the match is on a string, which is also translated to a switch thanks to https://github.com/scala/scala/pull/8451.
Now erasure comes along and realizes that the result has to be boxed: Test.this.c returns a String (the value class is erased), but the return type of test is Any. So erasure adds new C:
<method> def test(v: scala.this.Int): lang.this.Object = {
case <synthetic> val x1: scala.this.Int = v;
(x1: scala.this.Int) match {
case 1 => if (true)
new C.<init>(Test.this.c())
else
new C.<init>(default3())
case _ => default3(){
new C.<init>(Test.this.c())
}
}
}
The resulting AST new C.<init>(default3()) is not correct, as default3() is not a method call that returns a value. It's a jump to a "label".
I guess this is a bug in erasure, it should realize that the boxing is already added to the default case. The jump to default3() could just be left as it is.