I want to implement a polymorphic macro execution that delegates to context specific type class instances:
trait Compile[C, S] {
type T
def compile(e: Expr[S])(using Quotes): Expr[T]
}
object Memory {
given memoryCompile[A]: Compile[Memory.type, A] with
final type T = A
def compile(a: Expr[A])(using Quotes): Expr[A] = a
}
transparent inline def compile[C, S](ctx: C)(inline query: S) = ${
compileImpl('ctx)('query)
}
def compileImpl[C, S](
ctx: Expr[C]
)(query: Expr[S])(using q: Quotes, tc: Type[C], ts: Type[S]): Expr[Any] =
Expr.summon[Compile[C, S]] match {
case Some(c) =>
staging
.run(c)(using staging.Compiler.make(getClass.getClassLoader))
.compile(query)
case None => throw Exception(
s"No Compiler found for expression of type ${Type.show[S]} in the context of ${Type.show[C]}"
)
}
My own attempt, as shown, tries evaluating the expression using staging.run
, but that is subject to scala.quoted.runtime.impl.ScopeException: Cannot use Expr oustide of the macro splice `${...}` or the scala.quoted.staging.run(...)` where it was defined
Turns out macros can call other macros. There was no need for multi-stage compilation. The Compile[C,S]
givens could have their own macro expansions:
trait Compile[C, S] {
type T
def compile(e: S): T
}
given m[S]: Compile[Memory.type, S] with
type T = S
transparent inline def compile(e: S): S = ${Macros.compile('e)}
// In another file
object Macros {
def compile[S](e: Expr[S])(using Quotes): Expr[S] = e
}
Then, this:
staging
.run(c)(using staging.Compiler.make(getClass.getClassLoader))
.compile(query)
Becomse this:
'{$c.compile($query)}