Debugging Scala Macros

Posted by Jan Machacek

Find me on:

30-Sep-2013 16:09:00

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

Subscribe to Email Updates