Search code examples
scalascala-macrosscala-3

Scala3: making macro implementation polymorphic


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


Solution

  • 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)}