Repository navigation
Working with Macros
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
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.
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.
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
Toolboxto 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).
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
HasContexttrait that your code needs to mix-in or extend; this trait provides acontextmember. - at the macro implementation site you compose together HasContext traits to create whatever functionality you need. Be sure to set the type parameter of
HasContextto the singleton typecontext.typebased on thecontextobject passed through the macro. Otherwise your macro code cannot combine trees from theHasContextobject with trees generated elsewhere.
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]