Skip to content

Using a constructor inside inside a lambda, with a specified wider return type, causes a spooky java.lang.NoClassDefFoundError error in the outermost object's class initialization #23924

@neko-kai

Description

@neko-kai

Compiler version

3.7.2 and 3.8.0-RC1-bin-20250814-0cf7a18-NIGHTLY

Minimized code

To minimize we have to run the following code without zio on the classpath. We may do that by executing the following, assuming you have Coursier installed as cs on your system:

mkdir target
cs launch scala-cli:1.9.0 -- example.scala -d target/
java -cp target:`cs fetch org.scala-lang:scala-library:2.13.6` zio.main

Where example.scala:

//> using scala 3.7.2
//> using dep dev.zio::zio::2.1.21
package zio

object main extends App {
  println(outer.helloWorld)
}

object outer {
  def helloWorld = "Hello world!"

  object outer1 {
    object inner {
      def crashCausingLambdaFn: Boolean => ZIO[Any, Nothing, Boolean] = (f: Boolean) => new ZIO.Sync(zio.Trace.empty, () => f) // if this line is commented out, the code works.
      def manualCastMakesItWorkSomehow: Boolean => ZIO[Any, Nothing, Boolean] = 
        (f: Boolean) => new ZIO.Sync(zio.Trace.empty, () => f).asInstanceOf[ZIO[Any, Nothing, Boolean]]
      def applyMethodWorks: Boolean => ZIO[Any, Nothing, Boolean] =
        (a: Boolean) => { val x = ZIO.Sync.apply[Boolean]; x(zio.Trace.empty, () => a) }
    }
  }
}

EDIT: I minimized the example further and removed the inlines that weren't the real cause of this behavior following @som-snytt's comment below.

Output

Exception in thread "main" java.lang.NoClassDefFoundError: zio/ZIO
	at zio.main$.<clinit>(example.scala:9)
	at zio.main.main(example.scala)
Caused by: java.lang.ClassNotFoundException: zio.ZIO
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:528)
	... 2 more

Expectation

Expected to print Hello World.

  • If the line with crashCausingLambdaFn is commented out, the code works.
  • If we apply a manual cast to ZIO - the code works, the constructor call by itself does not cause the crash, only when combined with implicit widening.
  • If we forcefully call ZIO.Sync.apply instead of new, the code works.
  • Weirdly enough, the code also works if the return type signature is omitted in the inline definition (succeedNewNoTypeSig in example) That was caused by a narrower type being inferred.

Note: the reason to run code with some of the dependencies missing is to be able to ship libraries with batteries-included support for various frameworks - e.g. with typeclass instances and conversions for cats & ZIO, without the downstream user necessarily having to use them, by compiling with said libraries as % Optional dependencies.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions