Search code examples
scalametaprogrammingscala-macros

Implicit materialization in the same module


I have a parameterized class like

class Test[T]{
    //...
}

object Test{
    implicit def materializeTest[T]: Test[T] = macro impl[T]

    def impl[T: c.WeakTypeTag](c: blackbox.Context) = //...
}

If using the materialized implicit from the same module it throws an error:

macro implementation not found

But the problem is extracting a single class into a separate module looks absolutely ugly and cumbersome. Maybe there is some "well-known workaround" to avoid that? Maybe shapeless can be helpful here?

UPD:

scalaVersion in ThisBuild := "2.13.2"

Here is my minimal example:

import scala.language.experimental.macros
import scala.reflect.macros.blackbox

object Main {
  sealed trait Adt
  case object Adt1 extends Adt
  case object Adt2 extends Adt

  trait Test[Adtt <: Adt] {
    def restrict(restrictions: List[Int]): List[Int]
  }

  object Test {
    def apply[Adtt <: Adt](implicit ev: Test[Adtt]): Test[Adtt] = ev

    implicit def implicitMaterializer[
        Adtt <: Adt
    ]: Test[Adtt] = macro impl[Adtt]

    def impl[Adtt <: Adt: c.WeakTypeTag](
        c: blackbox.Context
    ): c.Expr[Test[Adtt]] = {

      import c.universe._
      c.Expr[Test[Adtt]](q"""???""")
    }
  }

  def main(args: Array[String]): Unit = {
    Test[Adt1.type].restrict(List(1, 2, 3))
  }
}

which results in the following error:

[error] Main.scala:32:9: macro implementation not found: implicitMaterializer
[error] (the most common reason for that is that you cannot use macro implementations in the same compilation run that defines them)

Solution

  • You can extract to a separate module not Test but TestMacro

    core

    import scala.language.experimental.macros
    
    class Test[T]
    
    object Test {
      implicit def materializeTest[T]: Test[T] = macro TestMacro.impl[T]
    }
    
    implicitly[Test[Int]] // compiles
    

    macros

    import scala.reflect.macros.blackbox
    
    object TestMacro {
      def impl[T: c.WeakTypeTag](c: blackbox.Context) = {
        import c.universe._
        q"new Test[${weakTypeOf[T]}]"
      }
    }
    

    Ugly or not but macro implementations must be compiled before they are applied (but Is there any trick to use macros in the same file they are defined?).

    This is improved in Scala 3

    http://dotty.epfl.ch/docs/reference/metaprogramming/macros.html#defining-a-macro-and-using-it-in-a-single-project

    Shapeless just hides some predefined set of standard macros, it can't help with your own macros.