Search code examples
scalaannotationsscala-macrosscala-macro-paradise

Scala macro-based annotation reuse


Consider a Scala macro-based annotation such as @memoise from macmemo. The annotation requires two arguments: a max cache size and a time-to-live, e.g.,

@memoize(maxSize = 20000, expiresAfter = 2 hours)

Say you want to create a @cacheall annotation that is equivalent to @memoize(maxSize = Int.MaxValue, expiresAfter = 100 days) in order to reduce boilerplate and have a single point of parameterization.

Is there a standard pattern for this type of reuse? Obviously,

class cacheall extends memoize(Int.MaxValue, 100 days)

won't work because of the compile-time argument parsing in the macro.


Solution

  • Standard pattern is to make your annotation a macro annotation that being expanded switches on necessary annotation with necessary parameters.

    import scala.annotation.StaticAnnotation
    import scala.language.experimental.macros
    import scala.reflect.macros.blackbox
    
    class cacheall extends StaticAnnotation {
      def macroTransform(annottees: Any*): Any = macro cacheallMacro.impl
    }
    
    object cacheallMacro {
      def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
        import c.universe._
    
        val memoize = q"""
          new _root_.com.softwaremill.macmemo.memoize(
            _root_.scala.Int.MaxValue, {
              import _root_.scala.concurrent.duration._
              100.days 
          })"""
    
        annottees match {
          case q"${mods: Modifiers} def $tname[..$tparams](...$paramss): $tpt = $expr" :: _ =>
            q"${mods.mapAnnotations(memoize :: _)} def $tname[..$tparams](...$paramss): $tpt = $expr"
        }
      }
    }