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:

Not an expert: Guessing that there is some reference to X that is not in scope when the code generation happens. I wonder if this note on Ref is relevant: " For example, it is incorrect to have C.this.foo outside the class body of C, or have foo outside the lexical scope for the definition of foo."

The note doesn’t say what would happen if a Ref is used in the wrong lexical scope. This error is ugly, but about what I’d expect.

Hey, welcome to Scala Users! thanks for opening this thread,

Most likely you don’t have problematic code, but either way the compiler should never crash, in these cases it’s always a bug in the compiler.

Please could you open an issue at lampepfl/dotty