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

NoClassDefFoundError: while executing macro expansion #19601

Closed
WojciechMazur opened this issue Feb 3, 2024 · 4 comments · Fixed by #19645
Closed

NoClassDefFoundError: while executing macro expansion #19601

WojciechMazur opened this issue Feb 3, 2024 · 4 comments · Fixed by #19645
Assignees
Labels
Milestone

Comments

@WojciechMazur
Copy link
Contributor

When trying to reproduce and minimize OpenCB failure in zio/zio-prelude Open CB logs failing due to too restrictive cyclic macro dependency which is probably a regression (it works in previous version) I've stumbled upon other, possibly related issue.

Compiler version

All Scala 3 versions

Minimized code

// main.scala
package prelude

sealed trait Assertion[-A]:
  def unary_! : Assertion[A] = ???
  def apply(a: A): Either[AssertionError, Unit] = ???
object Assertion:
  val anything: Assertion[Any] = ???
  def greaterThanOrEqualTo[A](value: A)(implicit ordering: Ordering[A]): Assertion[A] = ???

sealed trait AssertionError

abstract class NewtypeCustom[A] {
  type Type
  protected inline def validateInline(inline value: A): Unit

  inline def apply(inline a1: A): Type = {
    validateInline(a1)
    a1.asInstanceOf[Type]
  }
}
abstract class Newtype[A] extends NewtypeCustom[A] {
  def assertion: Assertion[A] = Assertion.anything
  protected inline def validateInline(inline value: A): Unit = ${
    Macros.validateInlineImpl[A]('assertion, 'value)
  }
}


abstract class Subtype[A] extends Newtype[A] {
  type Type <: A
}


package object newtypes {
  type Natural = Natural.Type
  object Natural extends Subtype[Int] {

    override inline def assertion = Assertion.greaterThanOrEqualTo(0)
  
    val one: Natural =  Natural(1) // triggers macro
  }
}
// macro.scala
package prelude
import scala.quoted.*

object Macros {
  def validateInlineImpl[A: Type](assertionExpr: Expr[Assertion[A]], a: Expr[A])(using
      Quotes
  ): Expr[Unit] = {
    import quotes.reflect.*
    val crashRoot = assertionExpr.value
    '{ () }

  }
  given [A](using Type[A]): FromExpr[Assertion[A]] with {
    def unapply(assertion: Expr[Assertion[A]])(using Quotes): Option[Assertion[A]] = {
      import quotes.reflect.*

      assertion match {
        case '{ Assertion.greaterThanOrEqualTo[A](${ LiteralUnlift(value) })($_) } =>
          Some(Assertion.greaterThanOrEqualTo(value)(orderingForValue(value)))
        case _ => None
      }
    }
  }

  object LiteralUnlift {
    def unapply[A: Type](expr: Expr[A])(using Quotes): Option[A] = expr match {
      case '{ ${ Expr(int) }: Int }       => Some(int)
      case '{ Int.MaxValue }              => Some(Int.MaxValue.asInstanceOf[A])
      case '{ Int.MinValue }              => Some(Int.MinValue.asInstanceOf[A])
      case '{ ${ Expr(string) }: String } => Some(string)
      case '{ ${ Expr(double) }: Double } => Some(double)
      case '{ ${ Expr(float) }: Float }   => Some(float)
      case '{ ${ Expr(long) }: Long }     => Some(long)
      case '{ ${ Expr(short) }: Short }   => Some(short)
      case '{ ${ Expr(byte) }: Byte }     => Some(byte)
      case '{ ${ Expr(char) }: Char }     => Some(char)
      case _                              => None
    }
  }

  private def orderingForValue(any: Any)(using Quotes): Ordering[Any] = null.asInstanceOf[Ordering[Any]]
}

Output (click arrow to expand)

-- Error: /Users/wmazur/projects/sandbox/core.scala:40:31 ----------------------
40 |    val one: Natural =  Natural(1) // triggers macro
   |                        ^^^^^^^^^^
   |Exception occurred while executing macro expansion.
   |java.lang.NoClassDefFoundError: prelude/Assertion$
   |    at prelude.Macros$given_FromExpr_Assertion.unapply(macro.scala:19)
   |    at scala.quoted.Quotes.value(Quotes.scala:68)
   |    at scala.quoted.Quotes.value$(Quotes.scala:44)
   |    at scala.quoted.runtime.impl.QuotesImpl.value(QuotesImpl.scala:41)
   |    at prelude.Macros$.validateInlineImpl(macro.scala:9)
   |Caused by: java.lang.ClassNotFoundException: prelude.Assertion$
   |    at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445)
   |    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:587)
   |    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
   |    at prelude.Macros$given_FromExpr_Assertion.unapply(macro.scala:19)
   |    at scala.quoted.Quotes.value(Quotes.scala:68)
   |    at scala.quoted.Quotes.value$(Quotes.scala:44)
   |    at scala.quoted.runtime.impl.QuotesImpl.value(QuotesImpl.scala:41)
   |    at prelude.Macros$.validateInlineImpl(macro.scala:9)
   |    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   |    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
   |    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
   |    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
   |    at dotty.tools.dotc.quoted.Interpreter.interpretedStaticMethodCall$$anonfun$1(Interpreter.scala:174)
   |    at dotty.tools.dotc.quoted.Interpreter.stopIfRuntimeException(Interpreter.scala:231)
   |    at dotty.tools.dotc.quoted.Interpreter.interpretedStaticMethodCall(Interpreter.scala:174)
   |    at dotty.tools.dotc.quoted.Interpreter.interpretTree(Interpreter.scala:80)
   |    at dotty.tools.dotc.transform.Splicer$SpliceInterpreter.interpretTree(Splicer.scala:266)
   |    at dotty.tools.dotc.quoted.Interpreter.interpretTree$$anonfun$2(Interpreter.scala:99)
   |    at dotty.tools.dotc.transform.Splicer$.$anonfun$2(Splicer.scala:61)
   |    at scala.Option.fold(Option.scala:263)
   |    at dotty.tools.dotc.transform.Splicer$.splice(Splicer.scala:61)
   |    at dotty.tools.dotc.inlines.Inliner.dotty$tools$dotc$inlines$Inliner$$expandMacro(Inliner.scala:1051)
   |    at dotty.tools.dotc.inlines.Inliner$InlineTyper.typedSplice(Inliner.scala:838)
   |    at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:3160)
   |    at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:3199)
   |    at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:174)
   |    at dotty.tools.dotc.inlines.Inliner$InlineTyper.typedUnadapted(Inliner.scala:918)
   |    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3276)
   |    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3280)
   |    at dotty.tools.dotc.typer.ReTyper.typedTyped(ReTyper.scala:65)
   |    at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:3120)
   |    at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:3199)
   |    at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:174)
   |    at dotty.tools.dotc.inlines.Inliner$InlineTyper.typedUnadapted(Inliner.scala:918)
   |    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3276)
   |    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3280)
   |    at dotty.tools.dotc.inlines.Inliner.inlined(Inliner.scala:680)
   |    at dotty.tools.dotc.inlines.Inlines$InlineCall.expand(Inlines.scala:481)
   |    at dotty.tools.dotc.inlines.Inlines$.inlineCall(Inlines.scala:152)
   |    at dotty.tools.dotc.inlines.Inliner$InlineTyper.inlineIfNeeded(Inliner.scala:914)
   |    at dotty.tools.dotc.inlines.Inliner$InlineTyper.typedApply(Inliner.scala:819)
   |    at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:3115)
   |    at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:3199)
   |    at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:174)
   |    at dotty.tools.dotc.inlines.Inliner$InlineTyper.typedUnadapted(Inliner.scala:918)
   |    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3276)
   |    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3280)
   |    at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:3329)
   |    at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:3348)
   |    at dotty.tools.dotc.typer.Typer.typedBlockStats(Typer.scala:1193)
   |    at dotty.tools.dotc.typer.Typer.typedBlock(Typer.scala:1197)
   |    at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:3123)
   |    at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:3199)
   |    at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:174)
   |    at dotty.tools.dotc.inlines.Inliner$InlineTyper.typedUnadapted(Inliner.scala:918)
   |    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3276)
   |    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3280)
   |    at dotty.tools.dotc.typer.ReTyper.typedTyped(ReTyper.scala:65)
   |    at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:3120)
   |    at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:3199)
   |    at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:174)
   |    at dotty.tools.dotc.inlines.Inliner$InlineTyper.typedUnadapted(Inliner.scala:918)
   |    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3276)
   |    at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3280)
   |    at dotty.tools.dotc.inlines.Inliner.inlined(Inliner.scala:680)
   |    at dotty.tools.dotc.inlines.Inlines$InlineCall.expand(Inlines.scala:481)
   |    at dotty.tools.dotc.inlines.Inlines$.inlineCall(Inlines.scala:152)
   |    at dotty.tools.dotc.transform.Inlining$InliningTreeMap.transform(Inlining.scala:96)
   |    at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1595)
   |    at dotty.tools.dotc.ast.TreeMapWithImplicits.transform(TreeMapWithImplicits.scala:67)
   |    at dotty.tools.dotc.transform.Inlining$InliningTreeMap.transform(Inlining.scala:90)
   |    at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.loop$2(tpd.scala:1254)
   |    at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformStats(tpd.scala:1254)
   |    at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformStats(tpd.scala:1256)
   |    at dotty.tools.dotc.ast.TreeMapWithImplicits.transform(TreeMapWithImplicits.scala:58)
   |    at dotty.tools.dotc.transform.Inlining$InliningTreeMap.transform(Inlining.scala:109)
   |    at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1600)
   |    at dotty.tools.dotc.ast.TreeMapWithImplicits.transform(TreeMapWithImplicits.scala:67)
   |    at dotty.tools.dotc.transform.Inlining$InliningTreeMap.transform(Inlining.scala:90)
   |    at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.loop$2(tpd.scala:1254)
   |    at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformStats(tpd.scala:1254)
   |    at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformStats(tpd.scala:1256)
   |    at dotty.tools.dotc.ast.TreeMapWithImplicits.transform(TreeMapWithImplicits.scala:58)
   |    at dotty.tools.dotc.transform.Inlining$InliningTreeMap.transform(Inlining.scala:109)
   |    at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1600)
   |    at dotty.tools.dotc.ast.TreeMapWithImplicits.transform(TreeMapWithImplicits.scala:67)
   |    at dotty.tools.dotc.transform.Inlining$InliningTreeMap.transform(Inlining.scala:90)
   |    at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.loop$2(tpd.scala:1254)
   |    at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformStats(tpd.scala:1254)
   |    at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformStats(tpd.scala:1256)
   |    at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1611)
   |    at dotty.tools.dotc.ast.TreeMapWithImplicits.transform(TreeMapWithImplicits.scala:67)
   |    at dotty.tools.dotc.transform.Inlining$InliningTreeMap.transform(Inlining.scala:98)
   |    at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.loop$2(tpd.scala:1254)
   |    at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformStats(tpd.scala:1254)
   |    at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformStats(tpd.scala:1256)
   |    at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1611)
   |    at dotty.tools.dotc.ast.TreeMapWithImplicits.transform(TreeMapWithImplicits.scala:67)
   |    at dotty.tools.dotc.transform.Inlining$InliningTreeMap.transform(Inlining.scala:98)
   |    at dotty.tools.dotc.transform.Inlining$$anon$2.transform(Inlining.scala:59)
   |    at dotty.tools.dotc.transform.MacroTransform.run(MacroTransform.scala:18)
   |    at dotty.tools.dotc.transform.Inlining.run(Inlining.scala:35)
   |    at dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:354)
   |    at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
   |    at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
   |    at scala.collection.immutable.List.foreach(List.scala:333)
   |    at dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:360)
   |    at dotty.tools.dotc.transform.Inlining.runOn(Inlining.scala:39)
   |    at dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:315)
   |    at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
   |    at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
   |    at scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1323)
   |    at dotty.tools.dotc.Run.runPhases$1(Run.scala:337)
   |    at dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:350)
   |    at dotty.tools.dotc.Run.compileUnits$$anonfun$adapted$1(Run.scala:360)
   |    at dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:69)
   |    at dotty.tools.dotc.Run.compileUnits(Run.scala:360)
   |    at dotty.tools.dotc.Run.compileUnits(Run.scala:267)
   |    at dotty.tools.dotc.Run.compileSuspendedUnits(Run.scala:371)
   |    at dotty.tools.dotc.Driver.finish(Driver.scala:57)
   |    at dotty.tools.dotc.Driver.doCompile(Driver.scala:38)
   |    at dotty.tools.dotc.Driver.process(Driver.scala:196)
   |    at dotty.tools.dotc.Driver.process(Driver.scala:164)
   |    at dotty.tools.dotc.Driver.process(Driver.scala:176)
   |    at dotty.tools.dotc.Driver.main(Driver.scala:206)
   |    at dotty.tools.dotc.Main.main(Main.scala)
   |
   |----------------------------------------------------------------------------
   |Inline stack trace
   |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   |This location contains code that was inlined from core.scala:23
23 |  protected inline def validateInline(inline value: A): Unit = ${
   |                                                               ^
24 |    Macros.validateInlineImpl[A]('assertion, 'value)
25 |  }
   |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   |This location contains code that was inlined from core.scala:23
17 |    validateInline(a1)
   |    ^^^^^^^^^^^^^^^^^^
    ----------------------------------------------------------------------------
@som-snytt
Copy link
Contributor

Someone just mentioned this error on discord -- I said something about the scala 2 options for macro classloader, and they said they were on Scala 3. I don't remember which room. I'll follow up if I can.

@nicolasstucki
Copy link
Contributor

There is a cyclic dependency in the macro. This should have been caught by the macro expansion and reported as such.

object newtypes should be defined in a different file because the macro expansion depends on the compiled implementation of Assertion.

nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Feb 8, 2024
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Feb 8, 2024
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Feb 8, 2024
@WojciechMazur
Copy link
Contributor Author

object newtypes should be defined in a different file because the macro expansion depends on the compiled implementation of Assertion.

There might have been a simplificaiton in the reproducer - in the original most of types are placed in multiple files:

  • trait Assertion
  • class AssertionError
  • class NewType, class NewTypeCustom
  • object newtypes
    However, when split like that the reproducer compiles. It might suggest that the reproducer is incomplete and the bug might be still around. When current fix would be merged I'll check if the project compiles.

@nicolasstucki
Copy link
Contributor

When current fix would be merged I'll check if the project compiles.

If the classes are in the same file it might not compile. Now we detect the cycle correctly and suspend the compilation of that file. We could have two outcomes: (1) the non-suspended files are compiled and allow the compilation of the suspended file, or (2) there is a true cycle and the compilation fails, in this later case we need to split the files.

nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Feb 8, 2024
@Kordyjan Kordyjan added this to the 3.4.1 milestone Feb 14, 2024
WojciechMazur pushed a commit that referenced this issue Jun 28, 2024
WojciechMazur pushed a commit that referenced this issue Jun 30, 2024
WojciechMazur pushed a commit that referenced this issue Jun 30, 2024
WojciechMazur pushed a commit that referenced this issue Jun 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants