Debugging Scala Macros
Join the DZone community and get the full member experience.
Join For Freeprintln
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.
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
.)
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 can't 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
Published at DZone with permission of Jan Machacek, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
What Is mTLS? How To Implement It With Istio
-
Building A Log Analytics Solution 10 Times More Cost-Effective Than Elasticsearch
-
How To Design Reliable IIoT Architecture
-
What Is Test Pyramid: Getting Started With Test Automation Pyramid
Comments