Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initialisation Exception #15832

Open
Arthurm1 opened this issue Aug 8, 2022 · 7 comments
Open

Initialisation Exception #15832

Arthurm1 opened this issue Aug 8, 2022 · 7 comments

Comments

@Arthurm1
Copy link

Arthurm1 commented Aug 8, 2022

Compiler version

3.1.3 and 3.2.0-RC1 on Scastie

Minimized code

object A {
  class Builder[K, V](creator: K => V) {
    def build(k: K): V = creator(k)
  }
  case class Foo(id: Int) extends AnyVal
  object Foo extends Builder[Int, Foo](k => new Foo(k)) {
    def apply(str: String): Foo = str match {
      case "Foo1" => foo1
    }
  }
  val foo1: Foo = Foo.build(1)
}

object B {
  val foo = A.Foo.apply("Foo1")
}

B.foo

Output

java.lang.ExceptionInInitializerError
	at Playground$A$Foo$.<init>(main.scala:8)
	at Playground$A$Foo$.<clinit>(main.scala:8)
	at Playground$B$.<clinit>(main.scala:17)
	at Playground$.<clinit>(main.scala:20)
	at Main$.<clinit>(main.scala:24)
	at Main.main(main.scala)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at sbt.Run.invokeMain(Run.scala:143)
	at sbt.Run.execute$1(Run.scala:93)
	at sbt.Run.$anonfun$runWithLoader$5(Run.scala:120)
	at sbt.Run$.executeSuccess(Run.scala:186)
	at sbt.Run.runWithLoader(Run.scala:120)
	at sbt.Run.run(Run.scala:127)
	at com.olegych.scastie.sbtscastie.SbtScastiePlugin$$anon$1.$anonfun$run$1(SbtScastiePlugin.scala:38)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
	at sbt.util.InterfaceUtil$$anon$1.get(InterfaceUtil.scala:17)
	at sbt.ScastieTrapExit$App.run(ScastieTrapExit.scala:259)
	at java.base/java.lang.Thread.run(Thread.java:831)
Caused by: java.lang.NullPointerException: Cannot invoke "Playground$A$Foo$.build(Object)" because "Playground$A$Foo$.MODULE$" is null
	at Playground$A$.<clinit>(main.scala:13)
	... 21 more

Expectation

My initialisation ordering might be dubious but this works in Scala 2.

Works if I change val foo1 to lazy val foo1 or if I put all the code intoobject A

@Arthurm1 Arthurm1 added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Aug 8, 2022
@WojciechMazur
Copy link
Contributor

WojciechMazur commented Aug 8, 2022

object A.Foo is initialized before object A - it's an example of classic cyclic dependency.
@odersky I wonder if it is designed. Cannot we change the order of initialization to force initialization of outer object A, before initialization of A.Foo?
I think that's how it's done in Scala 2, by printing stack trace in foo1 initializers we would get

java.lang.RuntimeException
        at A$.<clinit>(test.scala:7)
        at A$Foo$.apply(test.scala:4)
        at Test$.main(test.scala:16)
        at Test.main(test.scala)

but in Scala 3 it's

java.lang.RuntimeException
        at A$.<clinit>(test.scala:7)
        at A$Foo$.<init>(test.scala:3)
        at A$Foo$.<clinit>(test.scala:3)
        at Test$.main(test.scala:14)

Scala 3 version can be simplified to the following snippet (foo1 does not need to be used in Foo.apply)

object A {
  class Builder[K, V](val build: K => V)
  object Foo extends Builder[Int, Int](identity) {
    def apply(str: String): Int = 1
  }
  val foo1 = Foo.build(1)
}

@main def Test =
  // A // prevents java.lang.ExceptionInInitializerError
  A.Foo.apply("Foo1")

@WojciechMazur WojciechMazur added stat:needs spec area:initialization and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Aug 8, 2022
@Arthurm1
Copy link
Author

Arthurm1 commented Aug 8, 2022

@WojciechMazur Thanks for looking into it so quickly.

If it can't be changed then it would be nice if there was a compiler error rather than hitting this at runtime

@som-snytt
Copy link
Contributor

som-snytt commented Aug 8, 2022

The cycle is induced by the superarg of object Foo, which is supplied by a method in A in Scala 3 but is a A$Foo$$anonfun$$lessinit$greater$1.class in Scala 2.

@som-snytt
Copy link
Contributor

som-snytt commented Aug 8, 2022

@Arthurm1 I've never actually used this before, but:

➜  scalac -Ysafe-init i15832.scala
-- Warning: i15832.scala:4:32 ------------------------------------------------------------------------------------------
4 |    def build(k: K): V = creator(k)
  |                         ^^^^^^^^^^
  |               Call method Builder.this.creator.apply(k) on a value with an unknown initialization. Calling trace:
  |               -> val foo1: Foo = Foo.build(1)       [ i15832.scala:12 ]
  |                                  ^^^^^^^^^^^^
1 warning found

Edit: I tried it with HEAD but no warning, although it fails the same way.

@Arthurm1
Copy link
Author

Arthurm1 commented Aug 8, 2022

@som-snytt That's handy thanks. I can see why it's not on by default - lots of false positives.

@liufengyun
Copy link
Contributor

The code is related to the safe initialization of global objects, for which we are still searching for a solution that balances safety, expressiveness, and performance. Related #9176.

One design invariant for global objects we are considering is initialization-time irrelevance: the time when a global object is initialized should not change program semantics.

/cc: @olhotak

@liufengyun
Copy link
Contributor

Edit: I tried it with HEAD but no warning, although it fails the same way.

Thanks for looking into it @som-snytt . The behavior of the HEAD is expected, it's due to a recent improvement #15467.

@liufengyun liufengyun self-assigned this Aug 9, 2022
@Kordyjan Kordyjan added this to the Future versions milestone Dec 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants