Fork me on GitHub

A Whirlwind Tour of scala.meta


For any scala.meta related questions, don't hesitate to ask on our gitter channel: Join the chat at https://gitter.im/scalameta/scalameta

Note. This tutorial was originally created for a workshop at the scala.world conference. The workshop material turned out to be useful for many so it has been moved here. You will still find occasional references to scala.world.

Setup


  1. Clone the workshop repo. Alternatively, for a minimal project template that uses the bleeding edge version of scalameta/paradise, clone this repo.
  2. Run sbt test to make sure everything works.
  3. Setup the project in your favorite IDE, for example IntelliJ, ENSIME or vim.
  4. Open up core/src/test/scala/scalaworld/Playground.scala.
  5. Run core/src/test/scala/scalaworld/Playground.scala. Run sbt "~core/testOnly scalaworld.Playground".
  6. (Optional) To experiment with scala.meta in the REPL, you can run the following in the Ammonite-REPL
    import $ivy.`org.scalameta:scalameta_2.11:1.7.0`, scala.meta._
    Note. The macro annotation examples will not run in the REPL, follow scalameta/paradise#10 for updates.

Video

To accompany the workshop, here is the recording from the original scala.world conference talk.

Tokens


Make sure you have setup your environment from Setup. You can decide to run these examples from the console or from Playground.scala.

This whole workshop will assume you have this import in scope:

scala> import scala.meta._
import scala.meta._

Here's how to tokenize a small expression.

scala> "val x = 2".tokenize.get
res0: scala.meta.tokens.Tokens = Tokens(, val,  , x,  , =,  , 2, )
Let's discuss the most interesting methods on tokens.

Tokens.syntax

The simplest method we can call is Tokens.syntax The method returns a string representation of the actual code behind the tokens, or how the code should look like to a developer.

scala> "val x = 2".tokenize.get.syntax
res0: String = val x = 2

Tokens.toString() uses .syntax behind the scenes. However, you should never rely on toString() when manipulating scala.meta structures, prefer to explicitly call .syntax. It's maybe not so obvious why right now but it will make more sense soon.

Tokens.structure

Another useful method is Tokens.structure. The method shows details that may be relevant to us as metaprogrammers.

scala> "val x = 2".tokenize.get.structure
res0: String = Tokens(BOF [0..0), val [0..3),   [3..4), x [4..5),   [5..6), = [6..7),   [7..8), 2 [8..9), EOF [9..9))

.structure is often useful for debugging and testing.

Tokens vs. Token

The class Tokens is a wrapper around a sequence of Token objects. There are multiple subtypes of Token while there only one type Tokens.

scala> "val x = 2".tokenize.get.head
res0: scala.meta.tokens.Token =

BOF stands for "Beginning of file". Let's see what other kinds of token types are in the string
scala> "val x = 2".tokenize.get.
  map(x => f"${x.structure}%10s -> ${x.productPrefix}").
  mkString("\n")
res0: String =
BOF [0..0) -> BOF
val [0..3) -> KwVal
    [3..4) -> Space
  x [4..5) -> Ident
    [5..6) -> Space
  = [6..7) -> Equals
    [7..8) -> Space
  2 [8..9) -> Int
EOF [9..9) -> EOF

Even spaces get their own tokens. The [0...3) part indicates that the val tokens start at offset 0 and ends at offset 3.

==

How does token equality look like?

scala> "foobar".tokenize.get(1) == "foobar kas".tokenize.get(1)
res0: Boolean = false
Huh, why are they not the same?

Token equality is implemented with reference equality. You need to be explicit if you actually mean syntactic (.syntax), or structural (.structure) equality.

The tokens are syntactically equal.
scala> "foobar".tokenize.get(1).syntax == "foobar kas".tokenize.get(1).syntax
res0: Boolean = true
Even if we move the tokens around
scala> "kas foobar".tokenize.get(3).syntax == "foobar kas".tokenize.get(1).syntax
res0: Boolean = true
The tokens are also structurally equal.
scala> "foobar".tokenize.get(1).structure == "foobar kas".tokenize.get(1).structure
res0: Boolean = true
However, they are not structurally equal if we move them around.
scala> "kas foobar".tokenize.get(3).structure == "foobar kas".tokenize.get(1).structure
res0: Boolean = false

.get

Tokenization can sometimes fail, for example in this case:

scala> """ val str = "unclosed literal """.tokenize
res0: scala.meta.tokenizers.Tokenized =
<input>:1: error: unclosed string literal
val str = "unclosed literal
           ^

If you prefer, you can safely pattern match on the tokenize result

scala> """ val str = "closed literal" """.tokenize match {
  case tokenizers.Tokenized.Success(tokenized) => tokenized
  case tokenizers.Tokenized.Error(e, _, _) => ???
}
res0: scala.meta.tokens.Tokens = Tokens(,  , val,  , str,  , =,  , "closed literal",  , )

Conclusion

Scala.meta tokens are the foundation of scala.meta. Sometimes you don't have access to a parsed AST and then your best shot is work with tokens.

In the following chapter we will discuss another exciting data structure: the incredible scala.meta.Tree.

Trees


Reminder. We assume you have this import in scope:

scala> import scala.meta._
import scala.meta._

q"Quasiquotes"

The easiest way to get started with scala.meta trees is using quasiquotes.

scala> q"case class User(name: String, age: Int)"
res0: meta.Defn.Class = case class User(name: String, age: Int)

Quasiquotes can be composed

scala> val method = q"def `is a baby` = age < 1"
method: meta.Defn.Def = def `is a baby` = age < 1

scala> q"""
case class User(name: String, age: Int) {
  $method
}
"""
res0: meta.Defn.Class = case class User(name: String, age: Int) { def `is a baby` = age < 1 }

Quasiquotes can also be used to deconstruct trees with pattern matching

scala> q"def `is a baby` = age < 1" match {
  case q"def $name = $body" =>
    s"You ${name.syntax} if your ${body.syntax}"
}
res0: String = You `is a baby` if your age < 1

NOTE. Quasiquotes currently ignore comments:

scala> q"val x = 2 // assignment".syntax
res0: String = val x = 2
If you need comments, you can use .parse[T]
scala> "val x = 2 // assignment".parse[Stat].get.syntax
res0: String = val x = 2 // assignment

.parse[T]

If the contents that you want to parse are only known at runtime, you can't use quasiquotes. For example, this happens when you need to parse file contents.

Here's how to parse a compilation unit.

scala> "object Main extends App { println(1) }".parse[Source].get
res0: scala.meta.Source = object Main extends App { println(1) }

Pro tip. You can also call .parse[T] on a File, just like this

scala> new java.io.File("readme/ParseMe.scala").parse[Source]
res0: scala.meta.parsers.Parsed[scala.meta.Source] =
class ParseMe { println("I'm inside a file") }

If we try to parse a statement as a compilation unit we will fail.

scala> "val x = 2".parse[Source]
res0: scala.meta.parsers.Parsed[scala.meta.Source] =
<input>:1: error: expected class or object definition
val x = 2
^

We need to explicitly parse it as a statement (Stat).

scala> "val x = 2".parse[Stat].get
res0: scala.meta.Stat = val x = 2

We can also parse case statement

scala> "case Foo(bar) if bar > 2 => println(bar)".parse[Case].get
res0: scala.meta.Case = case Foo(bar) if bar > 2 => println(bar)

Scala.meta has dozens of parsers:

However, .parse[Stat] and .parse[Source] are usually all you need.

dialects

I didn't tell the whole story when I said you need to pass in a type argument to parse statements. You also need to pass in a dialect! However, scala.meta will by default pick the Scala211 dialect for you if you don't provide one explicitly.

With the SBT dialects, we can parse vals as top-level statements.

scala> dialects.Sbt0137(
  "lazy val core = project.settings(commonSettings)"
).parse[Source].get
res0: scala.meta.Source = lazy val core = project.settings(commonSettings)

We can even parse multiple top level statements

scala> dialects.Sbt0137(
  """
  lazy val core = project.settings(commonSettings)

  lazy val extra = project.dependsOn(core)
  """
).parse[Source].get
res0: scala.meta.Source =

  lazy val core = project.settings(commonSettings)

  lazy val extra = project.dependsOn(core)

For the remainder of the workshop, we will only work with the Scala211 dialect.

Tree.syntax

Just like with tokens, we can also run .syntax on trees.

scala> "foo(bar)".parse[Stat].get.syntax
res0: String = foo(bar)
However, scala.meta can also do this even if you manually construct the tree
scala> Term.Apply(
  Term.Name("foo"),
  scala.collection.immutable.Seq(
    Term.Name("bar"): Term.Arg
  )
).syntax
res0: String = foo(bar)

We never gave scala.meta parentheses but still it figured out we needed them. Pretty cool huh.

Tree.structure

Just like with tokens, we can also run .structure on trees.

scala> "foo(bar)".parse[Stat].get.structure
res0: String = Term.Apply(Term.Name("foo"), Seq(Term.Name("bar")))

.structure ignores any syntactic trivia like whitespace and comments

scala> "foo  ( /* this is a comment */ bar  ) // eol".parse[Stat].get.structure
res0: String = Term.Apply(Term.Name("foo"), Seq(Term.Name("bar")))

This can be useful for example in debugging, testing or equality checking.

Tree.collect

You can collect on scala.meta.Tree just like regular collections.

scala> source"""sealed trait Op[A]
    object Op extends B {
      case class Foo(i: Int) extends Op[Int]
      case class Bar(s: String) extends Op[String]
    }""".collect { case cls: Defn.Class => cls.name }
res0: List[meta.Type.Name] = List(Foo, Bar)

Tree.transform

Transform scala.meta.Tree with .transform.

scala> q"myList.filter(_ > 3 + a).headOption // comments are removed :(".transform {
  case q"$lst.filter($cond).headOption" => q"$lst.find($cond)"
}
res0: scala.meta.Tree = myList.find(_ > 3 + a)

.transform does not preserve syntactic details such as comments and formatting. There has been made some work on source aware transformation, see #457, but it still requires a bit more work.

Tree.==

Just like with tokens, tree equality is by default by reference:

scala> q"foo(bar)" == q"foo(bar)"
res0: Boolean = false
This means you need to be explicit if you mean syntactic equality
scala> q"foo(bar)".syntax == q"foo(bar)".syntax
res0: Boolean = true

or structural equality

scala> q"foo(bar)".structure == q"foo(bar)".structure
res0: Boolean = true

Comprehensive trees

A key feature of scala.meta trees is that they comprehensively cover all corners of the Scala syntax. A side effect of this is that the scala.meta tree hierarchy containsĀ a lot of types. For example, there is a different tree node for an abstract def (Decl.Def)

scala> q"def add(a: Int, b: Int)" // Decl.Def
res0: meta.Decl.Def = def add(a: Int, b: Int): Unit
and a def with an implementation (Defn.Def)
scala> q"def add(a: Int, b: Int) = a + b" // Defn.Def
res0: meta.Defn.Def = def add(a: Int, b: Int) = a + b

Fortunately, most of the time you won't need to worry about this. Quasiquotes help you create/match/compose/deconstruct the correct instances. However, occasionally you may need to debug the types of the trees you have.

For your convenience, I've compiled together the most common types in this handy diagram:

Macro annotations


Scala.meta makes it possible to write new-style macro annotations. In comparison with the state of the art based on scala.reflect, new-style macro annotations are:

In order to define a new-style macro annotation, create a class that extends StaticAnnotation and create an inline apply method with a meta block in it. Inside the meta block, you can take apart the annotated member and generate new code using scala.meta quasiquotes. inline and meta are new language constructs introduced by macro paradise 3.x.

It's possible to write macro annotations on scala.meta trees using scala.meta paradise. From sbt, run your first macro annotation with

macros/test:run
Here is an example macro annotation:
package scalaworld.macros

import scala.meta._

class Main extends scala.annotation.StaticAnnotation {
  inline def apply(defn: Any): Any = meta {
    defn match {
      case q"object $name { ..$stats }" =>
        val main = q"def main(args: Array[String]): Unit = { ..$stats }"
        q"object $name { $main }"
      case _ =>
        abort("@main must annotate an object.")
    }
  }
}
The annotation wraps the body of an object into a main function, serving a similar function as extending App.

Class2Map

Implement a Class2Map macro annotation that injects a toMap method that creates a Map[String, Any] from the fields of this class.

Solution:

package scalaworld.macros

import scala.collection.immutable.Seq
import scala.meta._

// Before:
// @Class2Map
// class Class2MapExample(a: Int, b: String)(c: List[Int]) {
// After:
// class Class2MapExample(a: Int, b: String)(c: List[Int]) {
//   def toMap: _root_.scala.collection.Map[String, Any] =
//     _root_.scala.collection.Map(("a", a), ("b", b), ("c", c))
// }

class Class2Map extends scala.annotation.StaticAnnotation {
  inline def apply(defn: Any): Any = meta {
    defn match {
      case cls @ Defn.Class(_, _, _, Ctor.Primary(_, _, paramss), template) =>
        val namesToValues: Seq[Term.Tuple] = paramss.flatten.map { param =>
          q"(${param.name.syntax}, ${Term.Name(param.name.value)})"
        }
        val toMapImpl: Term =
          q"_root_.scala.collection.Map[String, Any](..$namesToValues)"
        val toMap =
          q"def toMap: _root_.scala.collection.Map[String, Any] = $toMapImpl"
        val templateStats: Seq[Stat] = toMap +: template.stats.getOrElse(Nil)
        cls.copy(templ = template.copy(stats = Some(templateStats)))
      case _ =>
        println(defn.structure)
        abort("@Class2Map must annotate a class.")
    }
  }
}

WithApply

Implement a WithApply macro annotation that creates a apply method to construct an instance of the class (just like is created for case classes).

The challenge here is to handle the companion object correctly.

Solution:

package scalaworld.macros

import scala.collection.immutable.Seq
import scala.meta._

// Before:
// @WithApply
// class WithApplyExample(a: Int)(b: String)
// After:
// class WithApplyExample(a: Int)(b: String)
// object WithApplyExample {
//   def apply(a: Int)(b: String): WithApplyExample = new WithApplyExample(a)(b)
// }

class WithApply extends scala.annotation.StaticAnnotation {
  inline def apply(defn: Any): Any = meta {
    def createApply(name: Type.Name, paramss: Seq[Seq[Term.Param]]): Defn.Def = {
      val args = paramss.map(_.map(param => Term.Name(param.name.value)))
      q"""def apply(...$paramss): $name =
            new ${Ctor.Ref.Name(name.value)}(...$args)"""
    }
    defn match {
      // companion object exists
      case Term.Block(
          Seq(cls @ Defn.Class(_, name, _, ctor, _),
              companion: Defn.Object)) =>
        val applyMethod = createApply(name, ctor.paramss)
        val templateStats: Seq[Stat] =
          applyMethod +: companion.templ.stats.getOrElse(Nil)
        val newCompanion = companion.copy(
          templ = companion.templ.copy(stats = Some(templateStats)))
        Term.Block(Seq(cls, newCompanion))
      // companion object does not exists
      case cls @ Defn.Class(_, name, _, ctor, _) =>
        val applyMethod = createApply(name, ctor.paramss)
        val companion   = q"object ${Term.Name(name.value)} { $applyMethod }"
        Term.Block(Seq(cls, companion))
      case _ =>
        println(defn.structure)
        abort("@WithApply must annotate a class.")
    }
  }
}

Debug

Open up Debug.scala and implement a Debug macro annotation for methods that:

Solution:

package scalaworld.macros

import scala.annotation.compileTimeOnly
import scala.meta._

// Before:
// @Debug
// def complicated(a: Int, b: String)(c: Int): Int = {
//   Thread.sleep(500)
//   a + b.length + c
// }
// After:
// def complicated(a: Int, b: String)(c: Int): Int = {
//   {
//     println("a" + ": " + a)
//     println("b" + ": " + b)
//     println("c" + ": " + c)
//   }
//   val start = System.currentTimeMillis()
//   val result = {
//     Thread.sleep(500)
//     a + b.length + c
//   }
//   val elapsed = System.currentTimeMillis() - start
//   println("Method " + "complicated" + " ran in " + elapsed + "ms")
//   result
// }
class Debug extends scala.annotation.StaticAnnotation {
  import autocomplete._
  inline def apply(defn: Any): Any = meta {
    defn match {
      case defn: Defn.Def =>
        val printlnStatements = defn.paramss.flatten.map { param =>
          q"""println(
                ${param.name.syntax} + ": " +
                ${Term.Name(param.name.value)})"""
        }
        val body: Term = q"""
          { ..$printlnStatements }
          val start = _root_.java.lang.System.currentTimeMillis()
          val result = ${defn.body}
          val elapsed = _root_.java.lang.System.currentTimeMillis() - start
          println("Method " + ${defn.name.syntax} + " ran in " + elapsed + "ms")
          result
          """
        defn.copy(body = body)
      case _ =>
        abort("@Debug most annotate a def")
    }
  }
}

For extra credit:

generic

Implement a generic macro annotation to automatically derive a shapeless Generic[T] instance.

Solution:

package scalaworld.macros

import scala.collection.immutable.Seq
import scala.meta._

// Before:
// @generic
// case class Foo(i: Int, s: String)
//
// @generic
// sealed trait Bar
// object Bar {
//   case class Baz(i: Int)     extends Bar
//   case class Quux(s: String) extends Bar
// }
//
// After:
// // infix operators are used where possible, avoiding the syntax ::[A, B]
// case class Foo(i: Int, s: String)
// object Foo {
//   implicit val FooGeneric: _root_.shapeless.Generic[Foo] =
//     new _root_.shapeless.Generic[Foo] {
//       import shapeless._
//       type Repr = Int :: String :: HNil
//       def from(r: Repr): Foo = r match {
//         case i :: s :: HNil => new Foo(i, s)
//       }
//       def to(t: Foo): Repr = t.i :: t.s :: HNil
//     }
// }
// sealed trait Bar
// object Bar {
//   implicit val BarGeneric: _root_.shapeless.Generic[Bar] =
//     new _root_.shapeless.Generic[Bar] {
//       import shapeless._
//       type Repr = Baz :+: Quux :+: CNil
//       def from(r: Repr): Bar = r match {
//         case Inl(t)      => t
//         case Inr(Inl(t)) => t
//         case Inr(Inr(cnil)) => cnil.impossible
//       }
//       def to(t: Bar): Repr = t match {
//         case t: Baz  => Inl(t)
//         case t: Quux => Inr(Inl(t))
//       }
//     }
//   case class Baz(i: Int) extends Bar()
//   case class Quux(s: String) extends Bar()
// }

// This implementation contains quite a bit of boilerplate because we
// generate similar code in term, type and pattern position.
class generic extends scala.annotation.StaticAnnotation {
  inline def apply(defn: Any): Any = meta {
    defn match {
      // Sealed ADT, create coproduct Generic.
      case Term.Block(
          Seq(t @ ClassOrTrait(mods, name), companion: Defn.Object))
          if GenericMacro.isSealed(mods) =>
        val oldTemplStats = companion.templ.stats.getOrElse(Nil)
        val subTypes = oldTemplStats.collect {
          case t: Defn.Class if GenericMacro.inherits(name)(t) => t
        }
        val newStats =
          GenericMacro.mkCoproductGeneric(name, subTypes) +: oldTemplStats
        val newCompanion =
          companion.copy(templ = companion.templ.copy(stats = Some(newStats)))
        Term.Block(Seq(t, newCompanion))
      // Plain class with companion object, create HList Generic.
      case Term.Block(
          Seq(cls @ Defn.Class(_, name, _, ctor, _),
              companion: Defn.Object)) =>
        val newStats =
          GenericMacro.mkHListGeneric(name, ctor.paramss) +:
            companion.templ.stats.getOrElse(Nil)
        val newCompanion =
          companion.copy(templ = companion.templ.copy(stats = Some(newStats)))
        Term.Block(Seq(cls, newCompanion))
      // Plain class without companion object, create HList Generic.
      case cls @ Defn.Class(_, name, _, ctor, _) =>
        val companion =
          q"""object ${Term.Name(name.value)} {
                ${GenericMacro.mkHListGeneric(name, ctor.paramss)}
              }
           """
        Term.Block(Seq(cls, companion))
      case defn: Tree =>
        println(defn.structure)
        abort("@generic must annotate a class or a sealed trait/class.")
    }
  }
}

object GenericMacro {
  def mkCoproductTerm(depth: Int): Term =
    if (depth <= 0) q"Inl(t)"
    else q"Inr(${mkCoproductTerm(depth - 1)})"

  def mkCoproductPattern(depth: Int): Pat =
    if (depth <= 0) p"Inl(t)"
    else p"Inr(${mkCoproductPattern(depth - 1)})"

  // final unreachable case in `from` for coproduct generic.
  def mkCantHappen(depth: Int): Pat =
    if (depth <= 0) p"Inr(cnil)"
    else p"Inr(${mkCantHappen(depth - 1)})"

  def mkGeneric(name: Type.Name,
                repr: Type,
                to: Term,
                from: Seq[Case],
                importStat: Stat): Stat = {
    val reprTyp: Stat = q"type Repr = $repr"
    val toDef: Stat   = q"def to(t: $name): Repr = $to"
    val fromDef: Stat =
      q"def from(r: Repr): $name = r match { ..case $from }"
    val implicitName = Pat.Var.Term(Term.Name(name.syntax + "Generic"))

    q"""implicit val $implicitName: _root_.shapeless.Generic[$name] =
            new _root_.shapeless.Generic[$name] {
              $importStat
              $reprTyp
              $toDef
              $fromDef
            }
       """
  }

  def mkCoproductGeneric(superName: Type.Name,
                         subTypes: Seq[Defn.Class]): Stat = {
    val coproductType: Type = subTypes.foldRight[Type](t"CNil") {
      case (cls, accum) =>
        t"${cls.name} :+: $accum"
    }
    val coproductTermCases: Seq[Case] = subTypes.zipWithIndex.map {
      case (cls, i) =>
        p"case t: ${cls.name} => ${mkCoproductTerm(i)}"
    }
    val coproductTerm = q"t match { ..case $coproductTermCases }"
    val coproductPat: Seq[Case] = subTypes.zipWithIndex.map {
      case (cls, i) =>
        p"case ${mkCoproductPattern(i)} => t"
    }
    val cantHappen =
      p"""case ${mkCantHappen(subTypes.length - 1)} =>
              cnil.impossible
         """
    mkGeneric(superName,
              coproductType,
              coproductTerm,
              coproductPat :+ cantHappen,
              q"import shapeless.{CNil, :+:, Inr, Inl}")
  }

  def mkHListGeneric(name: Type.Name, paramss: Seq[Seq[Term.Param]]): Stat = {
    val params = paramss match {
      case params :: Nil => params
      case _             => abort("Can't create generic for curried functions yet.")
    }
    val hlistType: Type = params.foldRight[Type](t"HNil") {
      case (Term.Param(_, _, Some(decltpe: Type), _), accum) =>
        t"$decltpe :: $accum"
      case (param, _) =>
        abort(s"Unsupported parameter ${param.syntax}")
    }
    val hlistTerm: Term = params.foldRight[Term](q"HNil") {
      case (param, accum) =>
        q"t.${Term.Name(param.name.value)} :: $accum"
    }
    val hlistPat: Pat = params.foldRight[Pat](q"HNil") {
      case (param, accum) =>
        p"${Pat.Var.Term(Term.Name(param.name.value))} :: $accum"
    }
    val args = params.map(param => Term.Name(param.name.value))
    val patmat =
      p"case $hlistPat => new ${Ctor.Ref.Name(name.value)}(..$args)"
    mkGeneric(name,
              hlistType,
              hlistTerm,
              Seq(patmat),
              q"import shapeless.{::, HNil}")
  }

  def isSealed(mods: Seq[Mod]): Boolean = mods.exists(_.syntax == "sealed")

  // Poor man's semantic API, we check that X in `class Foo extends X`
  // matches syntactically the name of the annotated sealed type.
  def inherits(superType: Type.Name)(cls: Defn.Class): Boolean =
    cls.templ.parents.headOption.exists {
      case q"$parent()" => parent.syntax == superType.syntax
      case _            => false
    }
}

object ClassOrTrait {
  def unapply(any: Defn): Option[(Seq[Mod], Type.Name)] = any match {
    case t: Defn.Class => Some((t.mods, t.name))
    case t: Defn.Trait => Some((t.mods, t.name))
    case _             => None
  }
}

Semantic


Scala.meta v1.6 released a new semantic API. See sbt-semantic-example on how to get started with the semantic API.

The semantic API is still new and experimental. This section will be expanded once the API becomes more mature.

Contrib


Scala.meta contrib is a module that provides common utilities for handling scala.meta data structures.

To use contrib, import scala.meta.contrib._.

Contrib exposes some collection-like methods on Tree.

scala> source"""
class A
trait B
object C
object D
""".find(_.is[Defn.Object])
res0: Option[scala.meta.Tree] = Some(object C)

scala> source"""
class A
trait B
object C {
  val x = 2
  val y = 3
}
object D
""".collectFirst { case q"val y = $body" =>  body.structure }
res1: Option[String] = Some(Lit.Int(3))

scala> source"""
class A
trait B
object C {
  val x = 2
  val y = 3
}
object D
""".exists(_.is[Defn.Def])
res2: Boolean = false

Contrib has a Equal typeclass for comparing trees by structural or syntactic equality.

scala> q"val x = 2".isEqual(q"val x = 1")
res0: Boolean = false

scala> (q"val x = 2": Stat).isEqual("val x = 2 // comment".parse[Stat].get)
res1: Boolean = true

scala> (q"val x = 2": Stat).isEqual[Syntactically]("val x = 2 // comment".parse[Stat].get)
res2: Boolean = false

scala> q"lazy val x = 2".mods.exists(_.isEqual(mod"lazy"))
res3: Boolean = true

scala> q"lazy val x = 2".contains(q"3")
res4: Boolean = false

scala> q"lazy val x = 2".contains(q"2")
res5: Boolean = true

Contrib has an AssociatedCommments helper to extract leading and trailing comments of tree nodes.

scala> val code: Source = """
/** This is a docstring */
trait MyTrait // leading comment
""".parse[Source].get
code: scala.meta.Source =

/** This is a docstring */
trait MyTrait // leading comment

scala> val comments = AssociatedComments(code)
comments: scala.meta.contrib.AssociatedComments = scala.meta.contrib.AssociatedComments$$anon$1@198c802b

scala> val myTrait = code.find(_.is[Defn.Trait]).get
myTrait: scala.meta.Tree = trait MyTrait

scala> comments.leading(myTrait) -> comments.trailing(myTrait)
res0: (Set[meta.tokens.Token.Comment], Set[meta.tokens.Token.Comment]) = (Set(/** This is a docstring */),Set(// leading comment))

FAQ


Where can I ask more questions?

How do I get the type of a tree?

You can't do that with scala.meta 1.x. Semantic information (like inferred type, symbols, resolved names) is on the roadmap for scala.meta 2.x.

What is the quasiquote for X?

Here is an overview of quasiquote syntax: https://github.com/scalameta/scalameta/blob/master/notes/quasiquotes.md.

Can I use scala.meta with Scala.js?

Yes, the main scala.meta modules support Scala.js.

How do I pass an argument to the macro annotation?

You match on this as a scala.meta tree. For example:
package scalaworld.macros

import scala.meta._

class Argument(arg: Int) extends scala.annotation.StaticAnnotation {
  inline def apply(defn: Any): Any = meta {
    // `this` is a scala.meta tree.
    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.Int(arg)})" => arg
      // Example if you have more than one argument.
      case q"new $_(${Lit.Int(arg)}, ${Lit.String(foo)})" => arg
      case _  => ??? // default value
    }
    println(s"Arg is $arg")
    defn.asInstanceOf[Stat]
  }
}

Do I need to depend on scala.meta at runtime?

No. But your project needs a dependency on scala.meta. If you only use scala.meta at compile time, you can mark the dependency as % "provided" to exclude it from your runtime application.

How do I use macro annotations provided by a third-party library?

If your project depends on a library that provides macro annotations, you need to enable the `paradise` compiler plugin and declare a dependency on `scala-meta` so that macro annotations could be expanded:
addCompilerPlugin(
  ("org.scalameta" % "paradise" % paradiseVersion).cross(CrossVersion.full)
)

libraryDependencies +=
  "org.scalameta" %% "scalameta" % scalametaVersion % Provided

Here is a complete `settings` definition necessary and sufficient to enable dependent project to use the library (including workarounds for features that are being currently worked on):
lazy val enableMacroAnnotations: Seq[Def.Setting[_]] = Seq(
  addCompilerPlugin("org.scalameta" % "paradise" % paradiseVersion cross CrossVersion.full),
  libraryDependencies += "org.scalameta" %% "scalameta" % scalametaVersion % Provided,
  scalacOptions += "-Xplugin-require:macroparadise",
  // macroparadise plugin doesn't work in repl yet.
  scalacOptions in (Compile, console) := Seq()
)

How do I reuse code between macros?

If you try to call a method inside you macro class you get a "X not found" error.
class Argument(arg: Int) extends scala.annotation.StaticAnnotation {
  def helper(t: Any): Stat = ??? // utility method
  inline def apply(defn: Any): Any = meta {
    helper(defn) // ERROR: `helper` not found
    // Why? `this` is a scala.meta tree.
}
You can move the utility method to an external object.
package scalaworld.macros

import scala.meta._

object MacroUtil {
  def helper(defn: Any): Stat = q"class ReuseExample"
}

class Reuse extends scala.annotation.StaticAnnotation {
  inline def apply(defn: Any): Any = meta {
    MacroUtil.helper(defn)
  }
}

Incremental compiler is messing up / stale code

While editing the macro, it can be handy to keep this command running in SBT
~; macros/test:clean ; macros/test:run
Incremental compilation caches the macro expansion you need to clean the test project on every run.

My IDE/IntelliJ shows red squiggly marks

Your IDE might be all red like this

There are two possible workarounds:
  1. (Recommended if using IntelliJ) First, install the 2016.3 EAP. Then, select nightly or EAP update channel from Updates tab in Scala plugin settings Settings -> Languages and frameworks -> Scala -> Updates.
  2. (hack) Add import autocomplete._ to your file and a semicolon after inline, like this

    Remember to remove the semicolon when you run your macro.

New-style def macros

Scala.meta doesn't yet provide a possibility to write new-style def macros, but we are working hard on implementing this functionality. Attend Eugene Burmako's talk at Scala eXchange 2016 to learn more about our progress.

Compatibility with traditional macros

At the moment, new-style macros can only take apart existing Scala syntax and generate new syntax (so called syntactic API). This corresponds to the functionality provided by traditional macro annotations that only use tree constructors and quasiquotes.

Even this limited functionality should be enough to port most of the existing macro annotations to scala.meta. Oleksandr Olgashko has ported a large subset of Simulacrum's @typeclass features to new-style macros, so we are confident that new-style macros are powerful enough to support even more complex annotations.

For new-style def macros, we are working on semantic API, which will provide compiler information such as type inference, name resolution and other functionality that requires typechecking. It is too early to tell how compatible this API will be with what is provided by scala.reflect. We will provide more information as the design of the semantic API shapes up.

Which versions of Scala do the scala.meta macros support?

2.11.x and 2.12.x.

Resources