scalametaprogrammingscala-macrosscala-quasiquotes

MacroAnnotation to remove annotation from method's type member


I'm learning how to write Scala Macros and wrote a macro annotation that removes an annotation from a type parameter of an annotated function. Here it is.

The annotation to remove:

class garbage extends StaticAnnotation

Implementation of macro to remove the annotation:

@compileTimeOnly("Compile-time only annotation")
class removeGarbage extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro impl
}

object removeGarbage {
  def impl(c: whitebox.Context)(annottees: c.Tree*) = {
    import c.universe._
    println(annottees)
    val expandee = annottees.toList collect {
      case q"$mods def $templatename[..$typeparams](...$paramss): $tpt = $body" =>
        val modifiedParams = typeparams collect {
          case q"$mods type $name[..$args] = $tpt" =>
            val modifiedMods = mods match {
              case Modifiers(flags, privateWithin, annots) =>
                Modifiers(flags, privateWithin, annots.filter(_ == q"new garbage()"))
            }
            q"$modifiedMods type $name[..$args] = $tpt"
        }
        q"$mods def $templatename[..$modifiedParams](...$paramss): $tpt = $body"
      case annottee =>
        c.abort(c.enclosingPosition, s"$annottee cannot be annotated with @removeGarbage. Only def methods are allowed")
    }
    println(expandee)
    q"..$expandee"
  }
}

Test method:

trait Test{
  @removeGarbage
  def someMethod[@garbage Source, G[_]](i: Int): G[List[Int]]
}

That seem to work fine. To check it I compared the log added with println(annottees) and println(expandees):

List(def someMethod[@new garbage() Source, G[_]](i: Int): G[List[Int]])
List(def someMethod[Source, G[_]](i: Int): G[List[Int]])

The problem about the solution is it looks difficult to read. Maybe I didn't use quasiquotes to their full potential. Is there a way to simplify the macro implementation (probably using quasiquotes more extensively...)?


Solution

  • That's ok for a macro code to be difficult to read :) This is why metaprogramming shouldn't be the tool #1.

    I can't see how your code can be reduced significantly.

    You can replace

    val modifiedMods = mods match {
      case Modifiers(flags, privateWithin, annots) =>
        Modifiers(flags, privateWithin, annots.filter(_ == q"new garbage()"))
    }
    

    with one-liner

    val modifiedMods = mods.mapAnnotations(_.filter(_ == q"new garbage()"))
    

    If you keep doing the same set of transformations in many macros you can similarly define helper methods like mapDef, mapTypeParams ...

    If quasiquotes become too cumbersome you can consider to use ClassDef, Template, DefDef ... instead of quasiquotes or mix them with quasiquotes when convenient.

    (Such questions are normally for https://codereview.stackexchange.com/ although metaprogramming seems to be not so popular there.)