NullPointerException when putting a Singleton type in an AnyVal
Welcome to Scala 2.13.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_202-ea).
Type in expressions for evaluation. Or try :help.
scala> class Foo[S <: AnyRef](val s: S) extends AnyVal
defined class Foo
scala> def Foo[S <: AnyRef](name: S) = new Foo[name.type](name)
Foo: [S <: AnyRef](name: S)Foo[name.type]
scala> Foo("xx")
java.lang.NullPointerException
at Foo$.hashCode$extension(<console>:1)
at Foo.hashCode(<console>:1)
at java.lang.Object.toString(Object.java:236)
at scala.runtime.ScalaRunTime$.inner$1(ScalaRunTime.scala:238)
at scala.runtime.ScalaRunTime$.stringOf(ScalaRunTime.scala:243)
at scala.runtime.ScalaRunTime$.replStringOf(ScalaRunTime.scala:251)
at .$print$lzycompute(<synthetic>:8)
... 26 elided
class Foo[S <: AnyRef](val s: S) extends AnyVal
object Test {
def Foo[S <: AnyRef](name: S) = new Foo[name.type](name)
val foo = Foo("x")
def main(args: Array[String]) = println {
foo
}
}
The foo field initialization is incorrectly elided in constructors, apparently. Null foo results in NPE.
object Test extends Object {
def <init>(): Test.type = {
Test.super.<init>();
()
};
def Foo(name: Object): Object = name;
private[this] val foo: String("x") = Test.this.Foo("x").$asInstanceOf[String("x")]();
<stable> <accessor> def foo(): String("x") = Test.this.foo;
def main(args: Array[String]): Unit = scala.Predef.println(new Foo(Test.this.foo()))
}
[[syntax trees at end of constructors]] // t11659.scala
object Test extends Object {
def Foo(name: Object): Object = name;
<stable> <accessor> def foo(): String("x") = Test.this.foo;
def main(args: Array[String]): Unit = scala.Predef.println(new Foo(Test.this.foo()));
def <init>(): Test.type = {
Test.super.<init>();
()
}
}
I'm not familiar with this. Looks like I lost this? private[this] val foo: String("x") in object Test, tree.tpe=String("x")
I tested it. It seems that it has nothing to do with repl. It's a compiler error. The compiler did not call putstatic for field foo before use getstatic
DefDef( // val foo(): String("x") in object Test
<method> <stable> <accessor> <triedcooking>
"foo"
[]
List(Nil)
<tpt> // tree.tpe=String("x")
Test.this."foo " // private[this] val foo: String("x") in object Test, tree.tpe=String("x")
)
I did some interesting tests.
conclusion: lost private[this] val f: String("x") = _;,Test.this.f = Test.this.Foo("x").$asInstanceOf[String("x")](): String("x")
But I don't know where the relevant files are and whether they can be repaired? Can anyone tell me? Thank you
Singleton
object Test extends Object {
def Foo(name: Object): Object = name;
<stable> <accessor> def f(): String("x") = Test.this.f;
def main(args: Array[String]): Unit = scala.Predef.println(new sandbox.Foo(Test.this.f()));
def <init>(): sandbox.Test.type = {
Test.super.<init>();
()
}
}
non-Singleton
object Test extends Object {
def Foo(name: Object): Object = name;
private[this] val f: String = _;
<stable> <accessor> def f(): String = Test.this.f;
def main(args: Array[String]): Unit = scala.Predef.println(scala.Int.box(sandbox.Foo.hashCode$extension(Test.this.f())));
def <init>(): sandbox.Test.type = {
Test.super.<init>();
Test.this.f = Test.this.Foo("x").$asInstanceOf[String]();
()
}
}
Singleton but lazy
object Test extends Object {
final <synthetic> lazy private[this] var f: String("x") = _;
@volatile private[this] var bitmap$0: Boolean = _;
def Foo(name: Object): Object = name;
private def f$lzycompute(): String("x") = {
Test.this.synchronized[Unit](if (Test.this.bitmap$0.unary_!())
{
Test.this.f = (Test.this.Foo("x").$asInstanceOf[String("x")](): String("x"));
Test.this.bitmap$0 = true
});
Test.this.f
};
<stable> <accessor> lazy def f(): String("x") = (if (Test.this.bitmap$0.unary_!())
Test.this.f$lzycompute()
else
Test.this.f: String("x"));
def main(args: Array[String]): Unit = scala.Predef.println(new sandbox.Foo(Test.this.f()));
def <init>(): sandbox.Test.type = {
Test.super.<init>();
()
}
}
Dale and I looked at the Scala 3 behavior. Scala 3 doesn't crash, but note that it avoids the bug by emitting the field and properly initializing it. That isn't wrong, strictly speaking, but emitting the field isn't strictly necessary and has costs. See https://github.com/lampepfl/dotty/issues/12570 for a lot of discussion on this. But note that that ticket doesn't bring extends AnyVal into the picture.