Skip to content

Working with Macros

Sebastian Riedel edited this page Aug 18, 2014 · 7 revisions

Debugging macros in Intellij

If you want to (a) see the source code a macro expands to, and (b) debug the macro code (not the generated code though), you can create a new Run configuration with the following setup:

  • Main Class: scala.tools.nsc.Main
  • VM Options: -Dscala.usejavacp=true
  • Program Arguments: -Ymacro-debug-lite -cp $Classpath$ src/test/scala/scalapplcodefest/macros/TestIris.scala

$Classpath$ is itself an Intellij macro for the classpath of the current project.

Running code with macros in Intellij

When you want to test the macro you need to put your code in the test tree. This is required because it macros need to be compiled in a different run than the code that uses the macro. Note that this is not necessary in client code that depends on the wolfe library.

When you made changes to the macro implementation, but not to your test file that uses the macro, intellij won't recompile. I usually just make a small trivial change to the test file to ensure recompilation happens.

context.resetLocalAttrs is your friend

Sometimes you find that your generated AST looks okay, but the scala compiler still throws cryptic error messages. In this cases calling

context.resetLocalAttrs(tree)

often helps.

Unit Tests with Macros

It's a little difficult to test smaller unit of code within the macro code, as this code needs a macro context to work. There are two options

  • Implement smaller macros that return sub-results of the macro process, and then test these. One example is in MetaStructureSpecs.
  • Use the reflection Toolbox to get a universe and other helpers, and write code such that it only depends on the universe, not on a macro context (which also has a universe).

Class Structure

One challenge of working with Scala macros is the fact that for almost all code you require a Context object to be around, or at least a Universe. Moreover, all AST classes are inner classes of a Universe, and it's difficult to pass around AST objects between functions w/o type check errors. The current approach to deal with this is the following:

  • there is a HasContext trait that your code needs to mix-in or extend; this trait provides a context member.
  • at the macro implementation site you compose together HasContext traits to create whatever functionality you need. Be sure to set the type parameter of HasContext to the singleton type context.type based on the context object passed through the macro. Otherwise your macro code cannot combine trees from the HasContext object with trees generated elsewhere.

Dealing with Type objects

I found it a little difficult to access (build-in) types from the macro universe. One (probably unnecessarily complex) way is

val booleanSymbol = rootMirror.staticClass("scala.Boolean") 
val booleanType = booleanSymbol.typeSignature 

Another, possible better option is

val booleanType = c.typeOf[Boolean]

Clone this wiki locally