The case object gets initialized to zero - how is this possible?

I am having a bizarre mistake. One of the case objects (only one) gets null as its value and, obviously, throws Null pointer exceptions later in life. But why?

I tried different things (renaming objects, changing indecision, even rewriting one line twice, getting one of them to compile nice, and the other null ). I cloned the repo to make sure this is not some kind of ridiculous transition effect. Both Scala 2.11.4 and 2.11.5 cause the same thing.

The code is rather twisty and the context that cuts it off makes little sense. But here he is, in case anyone else calls someone.

 sealed trait A {} sealed trait B extends A {} sealed trait C { val n: Int } object C { val C1: C = new C { val n = 1 } val C2: C = new C { val n = 2 } val C3: C = new C { val n = 3 } val C4a: C = new C { val n = 4 } val C4b: C = new C { val n = 5 } } class D (depends: Seq[C], f: (Seq[Double]) => Double) { // ...methods removed } sealed trait E { val e: Int = 1 } object D { import C._ private def f( v: Seq[Double] ): Double = 0 private def list( acc: Boolean ) = List( C1, C2, C3, if (acc) C4a else C4b ) case object D1 extends D( list(true), f ) with E // <-- this gets to be 'null'!! case object D2 extends D( list(false), f ) val keys: Seq[D] = List(D1, D2) println(s"D1: $D1") println(s"D2: $D2") assert(D1 != null) // <-- caught here assert(D2 != null) } 

I tried to save as many details as possible in the code, if they have any meaning.

What could it be?

I marked it as "heisenbug", but when I compile the material, it is consistent (i.e. successive test runs will always produce the same effect).

EDIT: I initially said that this only happened with sbt test (and not sbt run ), but it is not. Both were equally affected.

EDIT: It seems @ dk14 filed this as Scala issue 9115 . Thanks!

+7
scala heisenbug
source share
2 answers

I have no idea what exactly is happening, but I suspect that there is some kind of construction order. I can reproduce the problem in 100% of cases if I paste the code and do

 scala> D.D1 D1: null D2: D2 java.lang.AssertionError: assertion failed at scala.Predef$.assert(Predef.scala:151) ... 47 elided 

Also, if I ask D2 first, it won't work either, so it's not related to E

 scala> D.D2 D1: D1 D2: null java.lang.AssertionError: assertion failed at scala.Predef$.assert(Predef.scala:151) ... 47 elided 

But if I ask D first, it works 100% of the time:

 scala> D D1: D1 D2: D2 res1: D.type = D$@50434135 scala> D.D1 res2: D.D1.type = D1 

I will be able to fix the problem in a shorter code example:

 class Bug(val f: Int) {} object Bug { private def f(n: Int): Int = n case object Bug1 extends Bug(f(1)) case object Bug2 extends Bug(f(2)) val values: List[Bug] = List(Bug1, Bug2) println(s"1: $Bug1") println(s"2: $Bug2") } 

The Bug.Bug1 request will fail. But if I delete println lines - everything works. Or first "execute" the Bug . However, the presence of values is fine.

I assume that some initialization locks go wrong when set directly to Bug.Bug1 or Bug.Bug2 .

+8
source share

It does not work only with direct access of D.D1 or D.D2 to object D , since it is not initialized here, so the value of case object is still null . It seems like an error for me (you should initialize D before calling D.D1 ). Workaround: Just call D (to start initialization) somewhere before D.D1 . See fooobar.com/questions/412987 / ... - lazy initialization is actually a function, but not if you find it. A simplified example:

 object D { def aaa = 1 //that's the reason class Z (depends: Any) case object D1 extends Z(aaa) // <-- this gets to be 'null'!! case object D2 extends Z(aaa) println(D1) println(D2) } 

Results:

 defined object D scala> D.D1 null D2 res32: D.D1.type = D1 

After overriding D :

 defined object D scala> D.D2 D1 null res34: D.D2.type = D2 

So, scala sub-items to initialize the requested sub-object (if it refers to some other object inside the definition of the sub-object) before starting the initialization of the object. It initializes this sub-object after (when aaa is defined). I think the main intention was to initialize all the mentioned members before the requested member, but it still seems to be a mistake, since it changes the initialization order, depending on how D was used in the code.

+1
source share

All Articles