Search code examples
scalascala-macrosscalameta

Passing type parameter to scala meta macro/annotations


package scalaworld.macros
import scala.meta._

class Argument(arg: Int) extends scala.annotation.StaticAnnotation {
  inline def apply(defn: Any): Any = meta {
    println(this.structure)
    val arg = this match {
      // The argument needs to be a literal like `1` or a string like `"foobar"`.
      // You can't pass in a variable name.
      case q"new $_(${Lit(arg: Int)})"                      => arg
      // Example if you have more than one argument.
      case q"new $_(${Lit(arg: Int)}, ${Lit(foo: String)})" => arg
      case _                                                => ??? // default     value
    }
    println(s"Arg is $arg")
    defn.asInstanceOf[Stat]
  }
}

I would like to modify the macro above and add type parameter [A]. I tried the following but it does not compile

package scalaworld.macros
import scala.meta._

class Argument2[A](arg: A) extends scala.annotation.StaticAnnotation {
  inline def apply(defn: Any): Any = meta {
    println(this.structure)
    val arg = this match {
      case q"new $_(${Lit(arg: A)})"                      => arg
      case q"new $_(${Lit(arg: A)}, ${Lit(foo: String)})" => arg
      case _                                              => ???
    }
    println(s"Arg is $arg")
    defn.asInstanceOf[Stat]
  }
}

Solution

  • The arguments passed into macro annotations are passed in as meta trees.

    Although literals such as Int/Double/String can be extracted via the Lit() extractor. this is not the case for other things.

    When parsed in meta

    • @someMacro(1) becomes @someMacro(Lit(1))
    • @someMacro("Foo") becomes @someMacro(Lit("Foo"))

    Everything else gets passed as it would be normally

    • @someMacro(foo) becomes @someMacro(Term.Name("foo"))
    • @someMacro(Option(2)) becomes @someMacro(Term.Apply(Term.Name("Option"), Seq(Lit(2))))

    This means you do not have runtime access to this thing. You cannot even instantiate an object properly without the Semantic Api to resolve symbols etc. It may be possible in scalameta 2 and paradise 4, but it definately is not possible now. What you could do, is make a runtime pattern match to check the value.

    I do some similar things here (Note this is very WIP): https://github.com/DavidDudson/Elysium/blob/master/gen/src/main/scala/nz/daved/elysium/gen/MacroAnnotation.scala

    See specifically https://github.com/DavidDudson/Elysium/blob/master/gen/src/main/scala/nz/daved/elysium/gen/MacroAnnotation.scala#L149

    Note: This means that at runtime (which just so happens to be compile time in that example), if the arg is passed in of an incorrect type, a runtime exception