Search code examples
scalascala-macrosscala-macro-paradise

scala macro with a import statement not working


I am annotating a trait such as:

@ScreenModel
trait TestTrait {
  .......
}

And then I have got the implementation of @ScreenModel with something like:

def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._

val output : List[Tree] = annottees.map(_.tree) match {
  case(cd @ q"$mods trait $name[..$tparams] extends ..$parents { ..$body }") :: Nil =>

    val compObjVar = s"""{
        import models.presentation.blocks.BlockModel;
        class AAAA {
          class BBBB {

          }
        }
    }""";

    val rawTree=c.parse(compObjVar)

    val merged = q"{..$rawTree}";
    merged :: Nil
  case _ => c.abort(c.enclosingPosition, "Invalid test target")
}
c.Expr[Any](q"..$output")
}

So, with that I get:

"top-level class without companion can only expand either into an eponymous class or into a block"

But, if I move the import just before the AAA class inside the AAA class, then it works:

    val compObjVar = s"""{
        class AAAA {
          import models.presentation.blocks.BlockModel;
          class BBBB {

          }
        }
    }""";

Why?


Solution

  • It breaks this rule from the documentation:

    Top-level expansions must retain the number of annottees, their flavors and their names, with the only exception that a class might expand into a same-named class plus a same-named module, in which case they automatically become companions as per previous rule.

    This means that if you are transforming a class or trait with a macro annotation, it must expand into a class of the same name, or a class with a companion of the same name. That is, we preserve the root of the tree that is expanding.

    If we try to expand class Ainto:

    import package.name
    class A
    

    The root of the tree we are expanding is no longer that of class A, but one that contains the class and the new import, but what is it? It can't be nothing. Reducing the amount of possible expansions reduces the complexity of the plugin, and therefore, the amount of things that can go wrong.

    If you must add imports, you can do so inside the class. If the imports need to appear in both the class and a companion object, they can be factored into their own Tree to be spliced in.