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.

Part 0 - 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.2.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.

Part 1 - Tokens


Make sure you have setup your environment from Part 0 - 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._
res0: scala.runtime.BoxedUnit = ()

Here's how to tokenize a small expression.

scala> "val x = 2".tokenize.get
res0: scala.meta.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: java.lang.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: java.lang.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 = 

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.getClass}")
  .mkString("\n")
res0: java.lang.String = 
"BOF [0..0) -> class scala.meta.tokens.Token$BOF
val [0..3) -> class scala.meta.tokens.Token$KwVal
    [3..4) -> class scala.meta.tokens.Token$Space
  x [4..5) -> class scala.meta.tokens.Token$Ident
    [5..6) -> class scala.meta.tokens.Token$Space
  = [6..7) -> class scala.meta.tokens.Token$Equals
    [7..8) -> class scala.meta.tokens.Token$Space
  2 [8..9) -> class scala.meta.tokens.Token$Constant$Int
EOF [9..9) -> class scala.meta.tokens.Token$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: java.lang.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: java.lang.Boolean = true
Even if we move the tokens around
scala> "kas foobar".tokenize.get(3).syntax == "foobar kas".tokenize.get(1).syntax
res0: java.lang.Boolean = true
The tokens are also structurally equal.
scala> "foobar".tokenize.get(1).structure == "foobar kas".tokenize.get(1).structure
res0: java.lang.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: java.lang.Boolean = false

.get

Tokenization can sometimes fail, for example in this case:

scala> """ val str = "unclosed literal """.tokenize.get
res0: scala.meta.tokenizers.TokenizeException = <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 Tokenized.Success(tokenized) => tokenized
  case Tokenized.Error(e) => ???
}
res0: scala.meta.tokens.Tokens =  val str = "closed literal" 

Exercises

Enough with explanations. Time to get your hands dirty.

Check if a string has balanced number of curly braces

Implement the following method so that BalancedSuite passes.

/** Replaces all var tokens with val tokens */
def isBalanced(tokens: Tokens): Boolean = ???

Strip away trailing commas

Implement the following method so that TrailingCommaSuite passes.

/** Removes all commas behind the last argument of function calls */
def stripTrailingCommas(tokens: Tokens): String = ???

NOTE. You don't have access to an AST so you can't know for certain which parentheses belong to a function application. For this exercise, it's OK to just guess.

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, for example in the case of Strip away trailing commas.

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

Part 2 - Trees


Reminder. We assume you have this import in scope:

scala> import scala.meta._
res0: scala.runtime.BoxedUnit = ()

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: scala.meta.Defn$Class$DefnClassImpl = case class User(name: String, age: Int)

Quasiquotes can be composed

scala> val method = q"def `is a baby` = age < 1"
scala> q"""
case class User(name: String, age: Int) {
  $method
}
"""
res0: scala.meta.Defn$Class$DefnClassImpl = 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: java.lang.String = 
"You `is a baby` if your age < 1"

NOTE. Quasiquotes currently ignore comments:

scala> q"val x = 2 // assignment".syntax
res0: java.lang.String = 
"val x = 2"
If you need comments, you can use .parse[T]
scala> "val x = 2 // assignment".parse[Stat].get.syntax
res0: java.lang.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$SourceImpl = 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$Success = 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].get
res0: scala.meta.parsers.ParseException = <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.Defn$Val$DefnValImpl = 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$CaseImpl = 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$SourceImpl = 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$SourceImpl = 
  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: java.lang.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: java.lang.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: java.lang.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: java.lang.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: scala.collection.immutable.$colon$colon = 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.Term$Apply$TermApplyImpl = 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: java.lang.Boolean = false
This means you need to be explicit if you mean syntactic equality
scala> q"foo(bar)".syntax == q"foo(bar)".syntax
res0: java.lang.Boolean = true

or structural equality

scala> q"foo(bar)".structure == q"foo(bar)".structure
res0: java.lang.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)"
res0: scala.meta.Decl$Def$DeclDefImpl = 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"
res0: scala.meta.Defn$Def$DefnDefImpl = 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:

Exercises

Don't catch Throwable

// Refactor this
object A {
  try danger() {
    catch e: Throwable => // swallows stack overflow / out of memory errors
      body
  }
}

// into this
object A {
  try danger() {
    catch scala.util.control.NonFatal(e) =>
      body
  }
}
For extra credit, you can:
catch {
  case e: Throwable => // scalaworld: off
}

Rewrite lst.filter(cond).headOption to lst.find(cond)

Implement noFilterHeadOption so that FilterHeadSuite passes.

/** Rewrites lst.filter(cond).headOption to lst.find(cond) */
def noFilterHeadOption(tree: Tree): Tree = ???

Remove redundant calls to sbt settings

// Refactor this
project

  .settings(commonSettings: _*)
  .settings(publishSettings: _*)
  .settings(
    libraryDeps += ""
  )

// into this
project.settings(
  commonSettings,
  publishSettings,
  libraryDeps += ""
)

Never do lst.length == 0

// Refactor this
lst.length == 0

// into this
lst.nonEmpty

Avoid Future.apply when possible

// Refactor this
Future(<Some literal>)
Future { <Some literal> }

// into this
Future.successful(<Some literal>)
Future.successful { <Some literal> }

Prefer Option(x) to Some(x)

To prevent Some(null).

// Refactor this
Some(arg)

// into this
Option(arg)

Part 3 - Devtools


Let's build a command line tool called scalaworld. The tool will run the Don't catch Throwable rewrite on scala source files.

NonFatal Rewrite

To start with, open the file NonFatal.scala in the package scalaworld.rewrite. The file looks like this:

package scalaworld.rewrite

import scala.meta._
import scalaworld.Fixed
import scalaworld.util.logger

/**
  * Rewrite this
  * {{{
  *   catch {
  *     case e: Throwable => ...
  *   }
  * }}}
  *
  * into this
  * {{{
  *   catch {
  *     case NonFatal(e) => ...
  *   }
  * }}}
  *
  */
object NonFatal extends Rewrite {
  override def rewrite(code: Input): Fixed =
    withParsed(code) { tree =>
      val patches = tree.collect {
        case c @ p"case $name: Throwable => $expr" =>
          val pat = c.asInstanceOf[Case].pat
          Patch(pat.tokens.head, pat.tokens.last, s"NonFatal(${name.syntax})")
      }
      Fixed.Success(Patch.run(tree.tokens, patches))
    }
}

Plug in your implementation of the Don't catch Throwable exercise. Keep running ~core/testOnly scalaworld.rewrite.NonFatalTest until the tests suite passes.

Building CLI

Let's run your NonFatal rewrite rule on some real-world Scala code.

Run the following commands to package and install the scalaworld CLI interface.

sbt cli/pack
cd cli/target/pack
make install // adds `scalaworld` to PATH

Next, clone scala-repos. The repository contains source files over 3 million lines of Scala source code from over 20 Scala open source projects.

Enter the scala-repos directory and execute

scalaworld -i --rewrite NonFatal repos/kafka

Everything ok? Try this now

scalaworld -i --rewrite NonFatal repos // Run on EVERYTHING

Part 4 - 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._
  def apply(defn: Defn): 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]): 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] {
              import shapeless.{::, HNil, CNil, :+:, Inr, Inl}
              $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)
  }

  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))
  }

  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
  }
}

FAQ


For any unanswered questions, don't hestitate to ask on gitter: Join the chat at https://gitter.im/scalameta/scalameta

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.

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(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]
  }
}

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 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

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?

Only 2.11.x for now.

Resources