Compiler throws exception while executing macro

Hi,
I am currently trying to implement a macro in Scala 3 with inlining. I try to generate some code, but I get a compile error, which has no clear message to me. Does anybody know, if I am doing something wrong here?

I am using Scala 3.2.2 and sbt 1.8.2 to compile the code.

Here are the code snippets:

package derivation

import api.*

import scala.quoted.*

protected[derivation] class SumMacro[From: Type, To: Type, Q <: Quotes]()(using ctx: Q) {
  import ctx.reflect.*

  private val fromTypeRepresentation = TypeRepr.of[From]
  private val toTypeRepresentation   = TypeRepr.of[To]

  private case class Variant(name: String, variantType: Type[?], symbol: Symbol)

  def generateMapper(): Expr[Mapper[From, To]] = '{
    new Mapper[From, To] {
      override def map(from: From): To = ${ mapImplementation('{ from }) }
    }
  }

  private def mapImplementation(from: Expr[From]): Expr[To] = {
    val toVariants   = collectVariants(toTypeRepresentation)
    val fromVariants = collectVariants(fromTypeRepresentation)

    val partialFunctions: List[Expr[PartialFunction[From, To]]] = toVariants.map { variant =>
      val foundFromVariant = fromVariants
        .find(_.name == variant.name)
        .getOrElse(report.errorAndAbort(s"Could not find a variant for ${variant.name}"))

      val selecToTerm: Term = Ref(toTypeRepresentation.typeSymbol.companionModule).select(variant.symbol)

      val condition: Expr[Boolean] = foundFromVariant.variantType match {
        case '[tpe] => '{ ${ from }.isInstanceOf[tpe] }
      }

      '{
        new PartialFunction[From, To] {

          override def isDefinedAt(x: From): Boolean = ${ condition }

          override def apply(v1: From): To = ${ selecToTerm.asExprOf[To] }
        }
      }
    }

    val mappingFunction: Expr[PartialFunction[From, To]] = partialFunctions.foldLeft('{
      PartialFunction.empty[From, To]
    }) { case (acc, next) => '{ ${ acc }.orElse(${ next }) } }

    '{ ${ mappingFunction }.applyOrElse(${ from }, _ => throw new Error("some error")) }
  }

  private def collectVariants(typeRepr: TypeRepr): List[Variant] = {
    typeRepr.typeSymbol.children.map { case caseSymbol =>
      Variant(caseSymbol.name, typeRepr.select(caseSymbol).asType, caseSymbol)
    }
  }

}

object SumMacro {

  def generateMapping[From: Type, To: Type, Q <: Quotes](
      config: InternalConfig
  )(using ctx: Q): Expr[Mapper[From, To]] = SumMacro().generateMapper()

}

package derivation

import api.Mapper
import api.syntax.*
import api.syntax.Types.{MapperBuilder, MappingConfig}

import scala.annotation.tailrec
import scala.quoted.*

object Derivation {

  inline def deriveMapperMacro[From, To](
      inline mapperBuilder: MapperBuilder[From, To]
  ): Mapper[From, To] = ${
    deriveImpl[From, To]('{ mapperBuilder })
  }

  private def deriveImpl[From: Type, To: Type](
      mapperBuilder: Expr[MapperBuilder[From, To]]
  )(using ctx: Quotes): Expr[Mapper[From, To]] = {
    import ctx.reflect.*

    val config = summonConfiguration[ctx.type]

    val flags = TypeTree.of[From].symbol.flags

    if (flags.is(Flags.Case)) {
      ProductMacro.generateMapping[From, To, ctx.type](mapperBuilder, config)
    } else if (flags.is(Flags.Enum | Flags.Trait & Flags.Sealed)) {
      SumMacro.generateMapping[From, To, ctx.type](config) // TODO mapper config
    } else {
      report.errorAndAbort("Unsupported kind of Mapping")
    }
  }

  private def summonConfiguration[Q <: Quotes](using
      ctx: Q
  ): InternalConfig = {
    import ctx.reflect.*

    Expr.summon[MappingConfig] match {
      case Some(expr) =>
        expr.asTerm match {
          case Inlined(_, _, _) => evalConfiguration[ctx.type](expr)
          case _ =>
            report.errorAndAbort(
              "The given configuration instance has to be inlined."
            )
        }
      case None =>
        InternalConfig()
    }
  }

  private def evalConfiguration[Q <: Quotes](
      configurationExpr: Expr[MappingConfig]
  )(using ctx: Q): InternalConfig = {
    import ctx.reflect.*

    @tailrec
    def loop(
        expr: Expr[MappingConfig],
        currentConfig: InternalConfig
    ): InternalConfig = {
      expr match {
        case '{ makeConfig } =>
          currentConfig
        case '{ disableDefaultValues($nested) } =>
          loop(nested, currentConfig.copy(useDefaultValues = false))
        case '{ enableDefaultValueForOption($nested) } =>
          loop(nested, currentConfig.copy(useDefaultValueForOption = true))
        case '{ enableDefaultValueForUnit($nested) } =>
          loop(nested, currentConfig.copy(useDefaultValueForUnit = true))
        case '{ enableUnsafeOptionMapping($nested) } =>
          loop(nested, currentConfig.copy(useUnsafeOptionMapping = true))
        case _ => report.errorAndAbort("Invalid configuration.")
      }
    }

    loop(configurationExpr, InternalConfig())
  }

}

package api

import api.*
import derivation.Derivation

object syntax {

  object Types {
    opaque type MapperBuilder[From, _] = From

    opaque type MappingConfig = Any
  }

  import Types.*

  inline def deriveMapper[From, To]: Mapper[From, To] =
    buildMapper[From, To].build

  def buildMapper[From, To]: MapperBuilder[From, To] =
    throw new Error("unexpected runtime call")

  extension [From, To](self: MapperBuilder[From, To]) {
    def withValue[Field](
        selector: To => Field,
        value: Field
    ): MapperBuilder[From, To] = throw new Error("unexpected runtime call")

    def withComputed[Field](
        selector: To => Field,
        computation: From => Field
    ): MapperBuilder[From, To] = throw new Error("unexpected runtime call")

    def withRenamed[FromField, ToField](
        toSelector: To => ToField,
        fromSelector: From => FromField
    ): MapperBuilder[From, To] =
      throw new Error("unexpected runtime call")
  }

  extension [From, To](inline self: MapperBuilder[From, To]) {

    inline def build: Mapper[From, To] =
      Derivation.deriveMapperMacro[From, To](self)

  }

  def makeConfig: MappingConfig = throw new Error("unexpected runtime call")

  extension (self: MappingConfig) {

    def disableDefaultValues: MappingConfig = throw new Error(
      "unexpected runtime call"
    )

    def enableDefaultValueForOption: MappingConfig = throw new Error(
      "unexpected runtime call"
    )

    def enableDefaultValueForUnit: MappingConfig = throw new Error(
      "unexpected runtime call"
    )

    def enableUnsafeOptionMapping: MappingConfig = throw new Error(
      "unexpected runtime call"
    )

  }

}

import api.*
import api.syntax.*

import scala.quoted.*

object Main extends App {

  enum Test {
    case X, Y
  }

  enum TestTo {
    case X
    case Y
  }

  val testMapper: Mapper[Test, TestTo] = deriveMapper[Test, TestTo]

}

The error message I got is the following:

[IJ]clean;compile
[success] Total time: 0 s, completed Apr 23, 2023, 10:37:04 PM
[info] compiling 9 Scala sources to /home/tammo/Documents/bachelorarbeit/implementation/target/scala-3.2.2/classes ...
Error while emitting Playground.scala
java.util.NoSuchElementException: val X while running genBCode on /home/tammo/Documents/bachelorarbeit/implementation/src/main/scala/Playground.scala
java.util.NoSuchElementException: val X while compiling /home/tammo/Documents/bachelorarbeit/implementation/src/main/scala/Playground.scala, /home/tammo/Documents/bachelorarbeit/implementation/src/main/scala/api/Mapper.scala, /home/tammo/Documents/bachelorarbeit/implementation/src/main/scala/api/Test.scala, /home/tammo/Documents/bachelorarbeit/implementation/src/main/scala/api/TestB.scala, /home/tammo/Documents/bachelorarbeit/implementation/src/main/scala/api/syntax.scala, /home/tammo/Documents/bachelorarbeit/implementation/src/main/scala/derivation/Derivation.scala, /home/tammo/Documents/bachelorarbeit/implementation/src/main/scala/derivation/InternalConfig.scala, /home/tammo/Documents/bachelorarbeit/implementation/src/main/scala/derivation/ProductMacro.scala, /home/tammo/Documents/bachelorarbeit/implementation/src/main/scala/derivation/SumMacro.scala
[error] ## Exception when compiling 9 sources to /home/tammo/Documents/bachelorarbeit/implementation/target/scala-3.2.2/classes
[error] java.util.NoSuchElementException: val X
[error] scala.collection.mutable.AnyRefMap$ExceptionDefault.apply(AnyRefMap.scala:508)
[error] scala.collection.mutable.AnyRefMap$ExceptionDefault.apply(AnyRefMap.scala:507)
[error] scala.collection.mutable.AnyRefMap.apply(AnyRefMap.scala:207)
[error] dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder$locals$.load(BCodeSkelBuilder.scala:514)
[error] dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoadTo(BCodeBodyBuilder.scala:423)
[error] dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:290)
[error] dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genComparisonOp$1(BCodeBodyBuilder.scala:1515)
[error] dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genCond(BCodeBodyBuilder.scala:1556)
[error] dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genPrimitiveOp(BCodeBodyBuilder.scala:260)
[error] dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genApply(BCodeBodyBuilder.scala:793)
[error] dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoadTo(BCodeBodyBuilder.scala:369)
[error] dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.emitNormalMethodBody$1(BCodeSkelBuilder.scala:809)
[error] dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.genDefDef(BCodeSkelBuilder.scala:832)
[error] dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.gen(BCodeSkelBuilder.scala:615)
[error] dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.gen$$anonfun$1(BCodeSkelBuilder.scala:621)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.immutable.List.foreach(List.scala:333)
[error] dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.gen(BCodeSkelBuilder.scala:621)
[error] dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.genPlainClass(BCodeSkelBuilder.scala:233)
[error] dotty.tools.backend.jvm.GenBCodePipeline$Worker1.visit(GenBCode.scala:266)
[error] dotty.tools.backend.jvm.GenBCodePipeline$Worker1.run(GenBCode.scala:231)
[error] dotty.tools.backend.jvm.GenBCodePipeline.buildAndSendToDisk(GenBCode.scala:598)
[error] dotty.tools.backend.jvm.GenBCodePipeline.run(GenBCode.scala:564)
[error] dotty.tools.backend.jvm.GenBCode.run(GenBCode.scala:69)
[error] dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:316)
[error] scala.collection.immutable.List.map(List.scala:246)
[error] dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:320)
[error] dotty.tools.backend.jvm.GenBCode.runOn(GenBCode.scala:77)
[error] dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:238)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
[error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
[error] scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1321)
[error] dotty.tools.dotc.Run.runPhases$1(Run.scala:249)
[error] dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:257)
[error] dotty.tools.dotc.Run.compileUnits$$anonfun$adapted$1(Run.scala:266)
[error] dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:68)
[error] dotty.tools.dotc.Run.compileUnits(Run.scala:266)
[error] dotty.tools.dotc.Run.compileUnits(Run.scala:196)
[error] dotty.tools.dotc.Driver.finish(Driver.scala:56)
[error] dotty.tools.dotc.Driver.doCompile(Driver.scala:36)
[error] dotty.tools.xsbt.CompilerBridgeDriver.run(CompilerBridgeDriver.java:88)
[error] dotty.tools.xsbt.CompilerBridge.run(CompilerBridge.java:22)
[error] sbt.internal.inc.AnalyzingCompiler.compile(AnalyzingCompiler.scala:91)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$7(MixedAnalyzingCompiler.scala:193)
[error] scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
[error] sbt.internal.inc.MixedAnalyzingCompiler.timed(MixedAnalyzingCompiler.scala:248)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4(MixedAnalyzingCompiler.scala:183)
[error] sbt.internal.inc.MixedAnalyzingCompiler.$anonfun$compile$4$adapted(MixedAnalyzingCompiler.scala:163)
[error] sbt.internal.inc.JarUtils$.withPreviousJar(JarUtils.scala:239)
[error] sbt.internal.inc.MixedAnalyzingCompiler.compileScala$1(MixedAnalyzingCompiler.scala:163)
[error] sbt.internal.inc.MixedAnalyzingCompiler.compile(MixedAnalyzingCompiler.scala:211)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1(IncrementalCompilerImpl.scala:534)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileInternal$1$adapted(IncrementalCompilerImpl.scala:534)
[error] sbt.internal.inc.Incremental$.$anonfun$apply$5(Incremental.scala:179)
[error] sbt.internal.inc.Incremental$.$anonfun$apply$5$adapted(Incremental.scala:177)
[error] sbt.internal.inc.Incremental$$anon$2.run(Incremental.scala:463)
[error] sbt.internal.inc.IncrementalCommon$CycleState.next(IncrementalCommon.scala:116)
[error] sbt.internal.inc.IncrementalCommon$$anon$1.next(IncrementalCommon.scala:56)
[error] sbt.internal.inc.IncrementalCommon$$anon$1.next(IncrementalCommon.scala:52)
[error] sbt.internal.inc.IncrementalCommon.cycle(IncrementalCommon.scala:263)
[error] sbt.internal.inc.Incremental$.$anonfun$incrementalCompile$8(Incremental.scala:418)
[error] sbt.internal.inc.Incremental$.withClassfileManager(Incremental.scala:506)
[error] sbt.internal.inc.Incremental$.incrementalCompile(Incremental.scala:405)
[error] sbt.internal.inc.Incremental$.apply(Incremental.scala:171)
[error] sbt.internal.inc.IncrementalCompilerImpl.compileInternal(IncrementalCompilerImpl.scala:534)
[error] sbt.internal.inc.IncrementalCompilerImpl.$anonfun$compileIncrementally$1(IncrementalCompilerImpl.scala:488)
[error] sbt.internal.inc.IncrementalCompilerImpl.handleCompilationError(IncrementalCompilerImpl.scala:332)
[error] sbt.internal.inc.IncrementalCompilerImpl.compileIncrementally(IncrementalCompilerImpl.scala:425)
[error] sbt.internal.inc.IncrementalCompilerImpl.compile(IncrementalCompilerImpl.scala:137)
[error] sbt.Defaults$.compileIncrementalTaskImpl(Defaults.scala:2363)
[error] sbt.Defaults$.$anonfun$compileIncrementalTask$2(Defaults.scala:2313)
[error] sbt.internal.server.BspCompileTask$.$anonfun$compute$1(BspCompileTask.scala:30)
[error] sbt.internal.io.Retry$.apply(Retry.scala:46)
[error] sbt.internal.io.Retry$.apply(Retry.scala:28)
[error] sbt.internal.io.Retry$.apply(Retry.scala:23)
[error] sbt.internal.server.BspCompileTask$.compute(BspCompileTask.scala:30)
[error] sbt.Defaults$.$anonfun$compileIncrementalTask$1(Defaults.scala:2311)
[error] scala.Function1.$anonfun$compose$1(Function1.scala:49)
[error] sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:62)
[error] sbt.std.Transform$$anon$4.work(Transform.scala:68)
[error] sbt.Execute.$anonfun$submit$2(Execute.scala:282)
[error] sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:23)
[error] sbt.Execute.work(Execute.scala:291)
[error] sbt.Execute.$anonfun$submit$1(Execute.scala:282)
[error] sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:265)
[error] sbt.CompletionService$$anon$2.call(CompletionService.scala:64)
[error] java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
[error] java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
[error] java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
[error] java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
[error] java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
[error] java.base/java.lang.Thread.run(Thread.java:833)
[error]            
[error] stack trace is suppressed; run last Compile / compileIncremental for the full output
[error] (Compile / compileIncremental) java.util.NoSuchElementException: val X
[error] Total time: 0 s, completed Apr 23, 2023, 10:37:05 PM
[IJ]

I just dont understand the compiler error message. Maybe there is a bug, but I wanted to check before, if I am using everything correctly.

Thanks for help :slight_smile: