Debugging Scala Macros

println is the golden hammer of debugging, but when it comes to debugging your Scala Macros, I find it useful to be able to set a breakpoint at the point of the macro expansion. In this post, I’ll show you how to construct a simple macro and how to debug it in IntelliJ IDEA.

Screencast

Macros from Cake Solutions Ltd. on Vimeo.

If you are terribly impatient, head over to https://github.com/janm399/activator-macros, download & build.

The macro

Let’s say we’d like to write a function that returns the member names in a specific type without using reflection.

object TypesDemo extends App {

  class A {
    def a: Int = 42
    def b: String = "b"
  }

  class B {
    def beta: String = "beta"
  }

  println(methodNames[A])
  println(methodNames[B])
}

Running this code should print List(, a, b) and List(, beta). We need to implement the methodNames function. And this will be a job for a macro. (The macro will need to be defined in a different module than the modulethat contains the TypeDemo.

The macro

Let’s start the Types module with just its skeleton.

import language.experimental.macros
import scala.reflect.macros.Universe

object Types {

  def methodNames[A]: List[String] = macro methodNames_impl[A]

  def methodNames_impl[A : c.WeakTypeTag](c: Context): c.Expr[List[String]] = {
    ???
  }

Notice the import of the language.experimental.macros, which enables macros, and then the definition of the methodNames[A] function and the methodNames_impl which mirrors it. Let’s now add the implementation of the macro:

object Types {

  def methodNames[A]: List[String] = macro methodNames_impl[A]

  def methodNames_impl[A : c.WeakTypeTag](c: Context): c.Expr[List[String]] = {
    import c.universe._

    val methods: List[String] = c.weakTypeOf[A].typeSymbol.typeSignature.
      declarations.toList.filter(_.isMethod).map(_.name.toString)

    val listApply = Select(reify(List).tree, newTermName("apply"))

    c.Expr[List[String]](Apply(listApply, 
      List(methods.map(x => Literal(Constant(x))):_*)))
  }

}

The methods variable looks complex, but it is the well-known Scala collections dance. The interesting part is what follows. We define listApply to be a “pointer” to the apply function in List. We then return an expression containing List[String] by applying the listApply method to its parameters. If you examine the List.apply method, you’ll see that it takes one parameter of type A*. And so, the second parameter to Apply must be a list containing a single element (we have one parameter), and its value must be varargs representing the method names. Hence, we arrive at c.Expr[List[String]](Apply(listApply, List(methods.map(x => Literal(Constant(x))):_*))).

Running the TypesDemo now really prints the desired output.

Debugging

I’ve decided to write slightly more complex macro to make it meaningful to debug. Of course, you cannot just add a breakpoint to the macro and expect to hit the breakpoint when you run your program. Macros run at compile time. And so, if we want to debug the macro, then instead of debugging our program, we must debug the Scala compiler.

In other words, the class that we’re running is the scala.tools.nsc.Main with some settings, namely:

  • -Dscala.usejavacp=true VM parameter
  • -cp types.Types demo/src/main/scala/types/TypesDemo.scala parameters

In IntelliJ IDEA, the debug settings for our example are

macdeb

This entry was posted in Jan's Blog and tagged , , . Bookmark the permalink.

4 Responses to Debugging Scala Macros

  1. Pingback: This week in #Scala (30/09/2013) | Cake Solutions Team Blog

  2. Alberto Paro says:

    Good article. I give some hints to you and your readers.
    An alternative method to debug a macro, that works with every IDE, is to:
    - start sbt in remote debug mode
    - start your IDE in remote debug (port 5005, … – Idea is already configured for it)
    - put your breakpoints
    - sbt compile and voilà the debugger is fired

    Remember to the users that macros are compile actions so after compiling, if you don’t touch the code you need to clean the build and recompile the project to call the macro.

    Have you had any issues with Intellij IDEA that doesn’t see macro methods and variable injected in a macro annotation?

  3. Jan Machacek says:

    Thanks for the tip! I can’t say I noticed IntelliJ IDEA not seeing the macro methods… but then again, I’ve been using it for such a long time that I’m used to its Scala plugin’s sometimes erratic behaviour. I’ll keep an eye out for it and report here.

    –J

  4. Good, useful article, thanks for making the video

Leave a Reply