Yes, it’s might be unintuitive, but it’s mostly due to code transformation happening to top-level statements. However, it is possible to check proper initialisation of these under Scala 3.4 using -Ysafe-init-global (do not confuse it with -Ysafe-init existing in all Scala 3 releases). Just be aware that these 2 are still an experimental flags, and might sometimes lead to false-positives
> scala-cli run main.scala -O -Ysafe-init -Ysafe-init-global -S 3.4 [warn] ./main.scala:2:1 [warn] Access uninitialized field value o. Calling trace: [warn] ├── @main def pdt={} [ main.scala:1 ] [warn] │ ^ [warn] ├── val o=C() [ main.scala:5 ] [warn] │ ^^^ [warn] └── class C extends o.CI: [ main.scala:2 ] [warn] ^
fwiw, note that Scala 2 errors on this with “illegal cyclic reference involving class C”
I don’t see how it matters here that top-level statements are involved in the originally posted code. Scala 3.4.0 still errors at runtime if you wrap the whole thing to not be top-level anymore:
scala> object O: | class C extends o.CI: | class CI | val o=C() | // defined object O scala> O.o java.lang.NullPointerException ... 35 elided
I don’t see how it matters here that top-level statements are involved in the originally posted code
I simply didn’t want to get into the details. Top-level statement are converted to an object, like the one you presented above, so it’s expected that is still fails (it also gives the same warnings under -Ysafe-init-global). I believe it would be very beneficial from the users perspective that stuff like -Ysafe-init-global would be enabled by default in Scala 3 once they’re stable.
I also didn’t want to get into the details, but I was curious about why the exception has no message text. First, though, I was curious about the Scala 3 REPL wrapping because of the clinit in the OP stack trace. Then, second, my curiosity evaporated. I guess it’s lazy module val all the way down? but it’s not trivial to ask the REPL to tell me directly. In Scala 2, I would :javap to verify that I’m not misreading -Vprint trees.
My clever remark was going to be that it’s not necessary to “wrap” inside a REPL session, which always wraps (somehow).
Welcome to Scala 3.4.2-RC1-bin-SNAPSHOT-git-e54be6e (21.0.2, Java OpenJDK 64-Bit Server VM). Type in expressions for evaluation. Or try :help. scala> class C extends o.CI: | class CI | val o=C() java.lang.NullPointerException ... 35 elided scala> throw null java.lang.NullPointerException: Cannot throw exception because "null" is null ... 33 elided scala> null.asInstanceOf[String].length java.lang.NullPointerException: Cannot invoke "String.length()" because "null" is null ... 33 elided
Anyway, it’s reassuring to know that, at the end of the day,