Debug Utilities
Debug utilities help you inspect macro expansions during compilation. They allow you to preview:
- the final code (after all macro expansions, implicit summoning, etc.)
- the final code resolved as
implicit/givenfor some type - the inferred type of an expression
- or the AST (Abstract Syntax Tree) of such code
Compiler Diagnostics, Not Runtime Logging
These utilities use compiler reporting mechanisms (Environment.reportInfo), not structured logging or runtime output. The output appears:
- As IDE hints (blue squiggly lines in VS Code/IntelliJ - hover to see the output)
- In compilation logs (when compiling from command line)
- In Scastie (as hover hints)
The output is only visible during compilation, not at runtime. This makes them perfect for debugging macro implementations without modifying your macro code.
When to Use What
withFinalCodeInIDE: Use to see the final expanded code after all macro transformations. Shows human-readable Scala code.withFinalASTInIDE: Use to see the AST structure of the final expanded code. Useful for understanding the internal representation.withInferredTypeInIDE: Use to see what type the compiler infers for an expression. Shows the dealiased type representation. Inspired by typeLogger.withGivenCodeInIDE[T]: Use to see what implicit/given value is being summoned for typeT. Shows the code that implements the implicit.withGivenASTInIDE[T]: Use to see the AST structure of the implicit/given value being summoned.withGivenTypeInIDE[T]: Use to see the concrete type of the implicit/given value summoned for typeT.
Tip
Start with withFinalCodeInIDE or withGivenCodeInIDE for most debugging scenarios. Use withInferredTypeInIDE when you want to check what type the compiler infers without looking at the full code or AST. Use the AST variants (withFinalASTInIDE, withGivenASTInIDE) when you need to understand the internal tree structure, such as when debugging macro implementations.
Debug utilities - Scala 2
//> using scala 2.13.16
//> using dep com.kubuszok::hearth::0.3.0
import hearth.debug.Debug
// Preview final expanded code
Debug.withFinalCodeInIDE {
val a = 10
s"Some example: $a"
}
// Hover over the code above in your IDE to see the expanded version
// Preview AST structure
Debug.withFinalASTInIDE {
val a = 10
s"Another example: $a"
}
// Preview inferred type
Debug.withInferredTypeInIDE {
List(1, 2, 3)
}
// Preview implicit/given resolution
Debug.withGivenCodeInIDE[scala.collection.Factory[Int, List[Int]]]
Debug.withGivenASTInIDE[scala.collection.Factory[Int, List[Int]]]
Debug.withGivenTypeInIDE[scala.collection.Factory[Int, List[Int]]]
Debug utilities - Scala 3
//> using scala 3.3.7
//> using dep com.kubuszok::hearth::0.3.0
import hearth.debug.Debug
// Preview final expanded code
Debug.withFinalCodeInIDE {
val a = 10
s"Some example: $a"
}
// Hover over the code above in your IDE to see the expanded version
// Preview AST structure
Debug.withFinalASTInIDE {
val a = 10
s"Another example: $a"
}
// Preview inferred type
Debug.withInferredTypeInIDE {
List(1, 2, 3)
}
// Preview macro expansion inside inline methods
inline def example[A]: String = {
Debug.withFinalCodeInIDE {
import scala.compiletime.*
inline erasedValue[A] match {
case _: Option[a] => "yes, it's an option"
case _ => "no, it's not an option"
}
}
}
example[Option[String]]
// Preview implicit/given resolution
Debug.withGivenCodeInIDE[scala.collection.Factory[Int, List[Int]]]
Debug.withGivenASTInIDE[scala.collection.Factory[Int, List[Int]]]
Debug.withGivenTypeInIDE[scala.collection.Factory[Int, List[Int]]]
Extension Methods (Dot Notation)
All expression-wrapping Debug methods are also available as extension methods through DebugOps.
This allows you to use dot notation instead of wrapping expressions in Debug.* calls.
Extension methods - Scala 2
//> using scala 2.13.16
//> using dep com.kubuszok::hearth::0.3.0
import hearth.debug.Debug
import hearth.debug.DebugOps._
// These pairs are equivalent:
// Object method vs extension method for code preview
Debug.withFinalCodeInIDE { "hello" }
"hello".withFinalCodeInIDE
// Object method vs extension method for AST preview
Debug.withFinalASTInIDE { 42 }
42.withFinalASTInIDE
// Object method vs extension method for type preview
Debug.withInferredTypeInIDE { List(1, 2, 3) }
List(1, 2, 3).withInferredTypeInIDE
Extension methods - Scala 3
//> using scala 3.3.7
//> using dep com.kubuszok::hearth::0.3.0
import hearth.debug.Debug
import hearth.debug.DebugOps.*
// These pairs are equivalent:
// Object method vs extension method for code preview
Debug.withFinalCodeInIDE { "hello" }
"hello".withFinalCodeInIDE
// Object method vs extension method for AST preview
Debug.withFinalASTInIDE { 42 }
42.withFinalASTInIDE
// Object method vs extension method for type preview
Debug.withInferredTypeInIDE { List(1, 2, 3) }
List(1, 2, 3).withInferredTypeInIDE
Note
Extension methods are only available for methods that take an expression argument (withFinalCodeInIDE, withFinalASTInIDE, withInferredTypeInIDE). Methods that only take a type parameter (withGivenCodeInIDE, withGivenASTInIDE, withGivenTypeInIDE) are only available on the Debug object.
How It Works
Debug utilities use the same printers as the better-printers module, but expose them directly to users through compiler diagnostics. This allows you to check how code looks without modifying your macro implementation.
The output appears as compiler informational messages, which IDEs and build tools can display as hints. This makes them perfect for:
- Debugging macro expansions without adding
printlnstatements - Understanding implicit resolution by seeing what code is actually being used
- Inspecting inferred types to understand what types the compiler sees
- Learning macro internals by inspecting AST structures
- Temporary debugging - you can add these utilities temporarily to understand code, then remove them
Even if you don't want to use Hearth for your macros, you can still use these utilities as a temporary dependency to check how some AST looks, which can help improve your macro implementations.
Troubleshooting
Q: I don't see any output in my IDE
- Make sure you're hovering over the code wrapped in
Debug.withFinalCodeInIDE(or similar) - Check your IDE's settings for compiler hints/diagnostics
- Try compiling from command line - the output will appear in compilation logs
- In VS Code with Metals, ensure diagnostics are enabled
Q: The output is too verbose
- Use
withFinalCodeInIDEinstead ofwithFinalASTInIDEfor more readable output - Use
withInferredTypeInIDEif you only care about the type, not the full code - The AST variants show the full tree structure, which can be very detailed
Q: Can I use this in production code?
- These utilities are designed for debugging during development
- They add compiler diagnostics but don't affect runtime behavior
- However, it's recommended to remove them before committing production code