Open
Description
$ cat sandbox/A_1.scala
class C {
class Inner
}
object Test {
def main(args: Array[String]): Unit = {
val c = new C
val i = new c.Inner
(i: Any) match {
case _: c.Inner =>
println("matched `case _: c.Inner`")
}
}
}
$ cat sandbox/A_2.scala
class C {
final class Inner
}
$ scalac sandbox/A_1.scala && scala Test && scalac sandbox/A_2.scala && scala Test
matched `case _: c.Inner`
java.lang.NoSuchMethodError: C$Inner.C$Inner$$$outer()LC;
at Test$.main(A_1.scala:10)
at Test.main(A_1.scala)
Inner classes marked final
do not store the outer pointer unless it is captured. They still have the outer parameter in their constructor, so new c.Inner
is binary compatible in the example above.
// access flags 0x1
public main([Ljava/lang/String;)V
// parameter final args
NEW C
DUP
INVOKESPECIAL C.<init> ()V
ASTORE 3
NEW C$Inner
DUP
ALOAD 3
INVOKESPECIAL C$Inner.<init> (LC;)V
But type tests in pattern matching and isInstanceOf
that check prefixes have a binary fragility.
ASTORE 4
ALOAD 4
ASTORE 5
ALOAD 5
INSTANCEOF C$Inner
IFEQ L0
ALOAD 5
CHECKCAST C$Inner
INVOKEVIRTUAL C$Inner.C$Inner$$$outer ()LC;
ALOAD 3
IF_ACMPNE L0
GETSTATIC scala/Predef$.MODULE$ : Lscala/Predef$;
LDC "matched `case _: c.Inner`"
The runtime prefix check is elided if it can the prefix of the scrutinee is statically the same as that of the tested type, hence the upcast to Any
in this test case.
We could make this more binary compatible by still generating the $outer
accessor in final inner classes (it would return null) and treating a null prefix in the scrutinee as a wildcard for the prefix check.