scala / scala3

The Scala 3 compiler, also known as Dotty.
https://dotty.epfl.ch
Apache License 2.0
5.84k stars 1.05k forks source link

Runtime error ` java.lang.ClassFormatError: Duplicate method name` reported by Scala 3.3.1 #19384

Open mtomko opened 9 months ago

mtomko commented 9 months ago

Compiler version

[info] Setting Scala version to 3.3.1 on 1 projects.

Minimized code

I have the example down to a fairly minimal case (~10 lines), but I have a dependency there (cats) so I made a github repository for it. You can check out the code at: https://github.com/mtomko/duplicate-method-name

Here is the Scala code in its entirety:

import cats.syntax.all._

object Main {

  def main(args: Array[String]): Unit = {
    Left(()).recoverWith { _ =>
      List.empty[Int].find(_ == 1) match {
        case None    => Left(())
        case Some(_) => Right(())
      }
    }
  }

}

Output

$ sbt ++3.3.1 run
[info] welcome to sbt 1.9.8 (GraalVM Community Java 21.0.1)
[info] loading settings for project global-plugins from plugins.sbt ...
[info] loading global plugins from /Users/mtomko/.sbt/1.0/plugins
[info] loading settings for project repro-build-build from metals.sbt ...
[info] loading project definition from /opt/broad/gpp/repro/project/project
[info] loading settings for project repro-build from metals.sbt ...
[info] loading project definition from /opt/broad/gpp/repro/project
[success] Generated .bloop/repro-build.json
[success] Total time: 1 s, completed Jan 5, 2024, 8:16:03 AM
[info] loading settings for project repro from build.sbt ...
[info] set current project to repro (in build file:/opt/broad/gpp/repro/)
[info] Setting Scala version to 3.3.1 on 1 projects.
[info] Reapplying settings...
[info] set current project to repro (in build file:/opt/broad/gpp/repro/)
[info] running Main
Exception in thread "sbt-bg-threads-1" java.lang.ClassFormatError: Duplicate method name "Main$$$_$_$$anonfun$$anonfun$1" with signature "(I)Z" in class file Main$
    at java.base/java.lang.ClassLoader.defineClass1(Native Method)
    at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1027)
    at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
    at java.base/java.net.URLClassLoader.defineClass(URLClassLoader.java:524)
    at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:427)
    at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:421)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:714)
    at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:420)
    at sbt.internal.ManagedClassLoader.findClass(ManagedClassLoader.java:103)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:593)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
    at Main.main(Main.scala)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
    at java.base/java.lang.reflect.Method.invoke(Method.java:580)
    at sbt.Run.invokeMain(Run.scala:144)
    at sbt.Run.execute$1(Run.scala:94)
    at sbt.Run.$anonfun$runWithLoader$5(Run.scala:121)
    at sbt.Run$.executeSuccess(Run.scala:187)
    at sbt.Run.runWithLoader(Run.scala:121)
    at sbt.Defaults$.$anonfun$bgRunTask$6(Defaults.scala:1988)
    at sbt.Defaults$.$anonfun$termWrapper$2(Defaults.scala:1927)
    at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
    at scala.util.Try$.apply(Try.scala:213)
    at sbt.internal.BackgroundThreadPool$BackgroundRunnable.run(DefaultBackgroundJobService.scala:367)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
    at java.base/java.lang.Thread.run(Thread.java:1583)
[success] Total time: 0 s, completed Jan 5, 2024, 8:16:04 AM

Expectation

This code compiles and runs as expected on Scala 2.13.12, producing no output. On Scala 3.3.1, it compiles but throws a java.lang.ClassFormatError: Duplicate method name at runtime. I believe that this code should probably just compile and run. However, if there is truly an error in the source code, then the compiler should detect and report the error, rather than generating a class file that causes the JVM to throw ClassFormatError.

mtomko commented 9 months ago

See also some discussion on Discord here: https://discord.com/channels/632150470000902164/632150470000902166/1192856719727001641

nicolasstucki commented 9 months ago

Self contained version

object Main {

  def main(args: Array[String]): Unit = {
    Left(()).recoverWith { _ =>
      List.empty[Int].find(_ == 1) match {
        case None    => Left(())
        case Some(_) => Right(())
      }
    }
  }
}

implicit final def catsSyntaxEither[A, B](eab: Either[A, B]): EitherOps[A, B] = new EitherOps(eab)
final class EitherOps[A, B](private val eab: Either[A, B]) extends AnyVal {
  def recoverWith[AA >: A, BB >: B](pf: PartialFunction[A, Either[AA, BB]]): Either[AA, BB] =
    eab match {
      case Left(a) if pf.isDefinedAt(a) => pf(a)
      case _                            => eab
    }
}
nicolasstucki commented 9 months ago

Minimized

@main def Main: Unit =
  f{ x =>
    { (y: Int) => y } match
      case _ => ()
  }

def f(pf: PartialFunction[Unit, Unit]): Unit = ()

The nested lambda needs to be in the match scrutinee.

nicolasstucki commented 9 months ago

Workaround

    Left(()).recoverWith { _ =>
-     List.empty[Int].find(_ == 1) match {
+     val a = List.empty[Int].find(_ == 1)
+     a match {
        case None    => Left(())
        case Some(_) => Right(())
      }
    }