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