0
# Macro Development
1
2
Compile-time reflection contexts for macro development including blackbox and whitebox macro support. Macros enable code generation and transformation at compile time, providing powerful metaprogramming capabilities.
3
4
## Capabilities
5
6
### Blackbox Context
7
8
Context for blackbox macros with restricted compile-time capabilities.
9
10
```scala { .api }
11
/**
12
* Blackbox macro context providing compile-time reflection capabilities
13
*/
14
trait Context extends scala.reflect.macros.Aliases
15
with scala.reflect.macros.Enclosures
16
with scala.reflect.macros.Names
17
with scala.reflect.macros.Reifiers
18
with scala.reflect.macros.Typers
19
with scala.reflect.macros.Universe {
20
21
/** The universe for this macro context */
22
val universe: Universe
23
24
/** Mirror for this macro context */
25
val mirror: universe.Mirror
26
27
/** The prefix expression (receiver) for this macro call */
28
def prefix: Expr[_]
29
30
/** Create expression wrapper */
31
def Expr[T: WeakTypeTag](tree: universe.Tree): Expr[T]
32
33
/** Issue compilation error */
34
def error(pos: Position, msg: String): Unit
35
36
/** Issue compilation warning */
37
def warning(pos: Position, msg: String): Unit
38
39
/** Issue compilation info message */
40
def info(pos: Position, msg: String, force: Boolean): Unit
41
42
/** Abort compilation with error */
43
def abort(pos: Position, msg: String): Nothing
44
45
/** Fresh name generation */
46
def freshName(): String
47
def freshName(name: String): String
48
49
/** Enclosing position */
50
def enclosingPosition: Position
51
52
/** Macro application position */
53
def macroApplication: Tree
54
}
55
```
56
57
**Basic Blackbox Macro Example:**
58
59
```scala
60
import scala.reflect.macros.blackbox.Context
61
import scala.language.experimental.macros
62
63
// Macro implementation
64
def debugImpl(c: Context)(expr: c.Expr[Any]): c.Expr[Any] = {
65
import c.universe._
66
67
val exprCode = show(expr.tree)
68
val debugTree = q"""
69
{
70
val result = $expr
71
println(s"Debug: $exprCode = " + result)
72
result
73
}
74
"""
75
76
c.Expr[Any](debugTree)
77
}
78
79
// Macro definition
80
def debug(expr: Any): Any = macro debugImpl
81
82
// Usage
83
debug(2 + 3) // Prints: Debug: 2 + 3 = 5
84
debug("hello".toUpperCase) // Prints: Debug: "hello".toUpperCase = HELLO
85
```
86
87
### Whitebox Context
88
89
Context for whitebox macros with expanded compile-time capabilities.
90
91
```scala { .api }
92
/**
93
* Whitebox macro context extending blackbox capabilities
94
*/
95
trait Context extends blackbox.Context {
96
// Whitebox macros can refine their return type beyond what's declared
97
// They have access to additional type information and can influence typing
98
}
99
```
100
101
**Whitebox Macro Example:**
102
103
```scala
104
import scala.reflect.macros.whitebox.Context
105
import scala.language.experimental.macros
106
107
// Whitebox macro that can refine return types
108
def selectDynamicImpl(c: Context)(name: c.Expr[String]): c.Expr[Any] = {
109
import c.universe._
110
111
name.tree match {
112
case Literal(Constant(fieldName: String)) if fieldName == "length" =>
113
// Return refined type - whitebox can specify exact return type
114
c.Expr[Int](q"${c.prefix}.toString.length")
115
case Literal(Constant(fieldName: String)) if fieldName == "upper" =>
116
c.Expr[String](q"${c.prefix}.toString.toUpperCase")
117
case _ =>
118
c.abort(c.enclosingPosition, s"Unknown field: ${show(name.tree)}")
119
}
120
}
121
122
class DynamicAccessor(value: Any) {
123
def selectDynamic(name: String): Any = macro selectDynamicImpl
124
}
125
126
// Usage - whitebox can provide exact return types
127
val accessor = new DynamicAccessor("hello")
128
val len: Int = accessor.length // Refined to Int
129
val upper: String = accessor.upper // Refined to String
130
```
131
132
### Macro Utilities and Context Operations
133
134
Common operations and utilities available in macro contexts.
135
136
```scala { .api }
137
/**
138
* Macro context utilities and operations
139
*/
140
trait MacroUtils { this: Context =>
141
/** Type checking */
142
def typecheck(tree: Tree): Tree
143
def typecheck(tree: Tree, mode: TypecheckMode): Tree
144
def typecheck(tree: Tree, pt: Type): Tree
145
146
/** Untypecheck tree (remove type information) */
147
def untypecheck(tree: Tree): Tree
148
149
/** Parse string as Scala code */
150
def parse(code: String): Tree
151
152
/** Show tree as code */
153
def show(tree: Tree): String
154
155
/** Show raw tree structure */
156
def showRaw(tree: Tree): String
157
158
/** Show type */
159
def show(tpe: Type): String
160
161
/** Fresh term name */
162
def freshTermName(): TermName
163
def freshTermName(prefix: String): TermName
164
165
/** Fresh type name */
166
def freshTypeName(): TypeName
167
def freshTypeName(prefix: String): TypeName
168
169
/** Internal APIs */
170
val internal: Internal
171
}
172
```
173
174
**Macro Utilities Example:**
175
176
```scala
177
import scala.reflect.macros.blackbox.Context
178
import scala.language.experimental.macros
179
180
def printTypeInfoImpl(c: Context)(expr: c.Expr[Any]): c.Expr[Unit] = {
181
import c.universe._
182
183
// Get type information
184
val exprType = expr.actualType
185
val exprTree = expr.tree
186
187
// Type checking
188
val typecheckedTree = c.typecheck(exprTree)
189
190
// Fresh names
191
val tempVar = c.freshTermName("temp")
192
193
// Generate debug info
194
val debugInfo = show(exprTree)
195
val typeInfo = show(exprType)
196
val rawInfo = showRaw(exprTree)
197
198
val result = q"""
199
{
200
val $tempVar = $expr
201
println("Expression: " + $debugInfo)
202
println("Type: " + $typeInfo)
203
println("Raw: " + $rawInfo)
204
println("Value: " + $tempVar)
205
}
206
"""
207
208
c.Expr[Unit](result)
209
}
210
211
def printTypeInfo(expr: Any): Unit = macro printTypeInfoImpl
212
213
// Usage
214
printTypeInfo(List(1, 2, 3))
215
printTypeInfo("hello".length)
216
printTypeInfo(Map("a" -> 1, "b" -> 2))
217
```
218
219
### Quasiquotes
220
221
String interpolation syntax for constructing and deconstructing ASTs.
222
223
```scala { .api }
224
/**
225
* Quasiquote string interpolators for AST construction
226
*/
227
object Quasiquotes {
228
/** Expression quasiquote */
229
implicit class QuasiquoteExpr(val sc: StringContext) {
230
def q(args: Any*): Tree = macro ???
231
}
232
233
/** Type quasiquote */
234
implicit class QuasiquoteType(val sc: StringContext) {
235
def tq(args: Any*): Tree = macro ???
236
}
237
238
/** Pattern quasiquote */
239
implicit class QuasiquotePattern(val sc: StringContext) {
240
def pq(args: Any*): Tree = macro ???
241
}
242
243
/** Case definition quasiquote */
244
implicit class QuasiquoteCaseDef(val sc: StringContext) {
245
def cq(args: Any*): Tree = macro ???
246
}
247
}
248
```
249
250
**Quasiquotes Example:**
251
252
```scala
253
import scala.reflect.macros.blackbox.Context
254
import scala.language.experimental.macros
255
256
def loggerImpl(c: Context)(message: c.Expr[String]): c.Expr[Unit] = {
257
import c.universe._
258
259
// Using quasiquotes for AST construction
260
val loggerTree = q"""
261
{
262
val timestamp = java.time.LocalDateTime.now()
263
val logMessage = s"[$timestamp] ${$message}"
264
println(logMessage)
265
}
266
"""
267
268
c.Expr[Unit](loggerTree)
269
}
270
271
def measureTimeImpl(c: Context)(expr: c.Expr[Any]): c.Expr[Any] = {
272
import c.universe._
273
274
val resultName = TermName(c.freshName("result"))
275
val startName = TermName(c.freshName("start"))
276
val endName = TermName(c.freshName("end"))
277
278
// Quasiquote with variable substitution
279
val timerTree = q"""
280
{
281
val $startName = System.nanoTime()
282
val $resultName = $expr
283
val $endName = System.nanoTime()
284
val duration = ($endName - $startName) / 1000000.0
285
println(s"Execution time: $duration ms")
286
$resultName
287
}
288
"""
289
290
c.Expr[Any](timerTree)
291
}
292
293
def logger(message: String): Unit = macro loggerImpl
294
def measureTime[T](expr: T): T = macro measureTimeImpl
295
296
// Usage
297
logger("Application started")
298
val result = measureTime {
299
Thread.sleep(100)
300
"Hello World"
301
}
302
```
303
304
### Annotation Macros
305
306
Macros triggered by annotations for code transformation.
307
308
```scala { .api }
309
/**
310
* Annotation macro support
311
*/
312
class MacroAnnotation extends scala.annotation.StaticAnnotation {
313
def macroTransform(annottees: Any*): Any = macro MacroAnnotation.impl
314
}
315
316
object MacroAnnotation {
317
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
318
import c.universe._
319
320
// Transform annotated code
321
val result = annottees.map(_.tree).toList match {
322
case (classDecl @ q"$mods class $className(..$params) extends ..$parents { ..$body }") :: Nil =>
323
// Transform class
324
q"""
325
$mods class $className(..$params) extends ..$parents {
326
..$body
327
def generatedMethod: String = "Generated by macro annotation"
328
}
329
"""
330
case _ => c.abort(c.enclosingPosition, "Annotation can only be applied to classes")
331
}
332
333
c.Expr[Any](result)
334
}
335
}
336
```
337
338
**Annotation Macro Example:**
339
340
```scala
341
import scala.reflect.macros.whitebox.Context
342
import scala.language.experimental.macros
343
import scala.annotation.{StaticAnnotation, compileTimeOnly}
344
345
@compileTimeOnly("enable macro paradise to expand macro annotations")
346
class toString extends StaticAnnotation {
347
def macroTransform(annottees: Any*): Any = macro toString.impl
348
}
349
350
object toString {
351
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
352
import c.universe._
353
354
def generateToString(className: TypeName, params: List[ValDef]): DefDef = {
355
val fieldAccess = params.map { param =>
356
val name = param.name.toString
357
q"$name + '=' + ${param.name}"
358
}
359
360
val toStringBody = if (fieldAccess.nonEmpty) {
361
fieldAccess.reduce((acc, field) => q"$acc + ', ' + $field")
362
} else {
363
q"''"
364
}
365
366
q"override def toString: String = ${className.toString} + '(' + $toStringBody + ')'"
367
}
368
369
val result = annottees.map(_.tree).toList match {
370
case (classDecl @ q"$mods class $className(..$params) extends ..$parents { ..$body }") :: Nil =>
371
val toStringMethod = generateToString(className, params)
372
q"""
373
$mods class $className(..$params) extends ..$parents {
374
..$body
375
$toStringMethod
376
}
377
"""
378
case _ => c.abort(c.enclosingPosition, "Can only annotate classes")
379
}
380
381
c.Expr[Any](result)
382
}
383
}
384
385
// Usage
386
@toString
387
class Person(name: String, age: Int)
388
389
val person = new Person("Alice", 25)
390
println(person) // Person(name=Alice, age=25)
391
```
392
393
### Advanced Macro Patterns
394
395
Complex macro patterns for advanced metaprogramming.
396
397
```scala { .api }
398
/**
399
* Advanced macro development patterns
400
*/
401
object AdvancedMacroPatterns {
402
/** Type-level computation macro */
403
def typeComputation[T](implicit c: Context, tag: c.WeakTypeTag[T]): c.Expr[String]
404
405
/** Compile-time reflection macro */
406
def reflectiveAccess(obj: Any, methodName: String): Any
407
408
/** Code generation macro */
409
def generateClass(className: String, fields: (String, String)*): Any
410
411
/** Validation macro */
412
def validate[T](expr: T): T
413
414
/** Serialization macro */
415
def serialize[T: Context#WeakTypeTag](obj: T): String
416
}
417
```
418
419
**Complete Macro Development Example:**
420
421
```scala
422
import scala.reflect.macros.blackbox.Context
423
import scala.language.experimental.macros
424
425
// Comprehensive macro example with multiple features
426
object ComprehensiveMacros {
427
428
// 1. Debug macro with type information
429
def debugWithTypeImpl(c: Context)(expr: c.Expr[Any]): c.Expr[Any] = {
430
import c.universe._
431
432
val exprType = expr.actualType
433
val exprString = show(expr.tree)
434
val typeString = show(exprType)
435
val pos = expr.tree.pos
436
437
q"""
438
{
439
val result = $expr
440
println(s"[${${pos.toString}}] $exprString: $typeString = " + result)
441
result
442
}
443
"""
444
}
445
446
// 2. Assertion macro with custom messages
447
def assertImpl(c: Context)(condition: c.Expr[Boolean], message: c.Expr[String]): c.Expr[Unit] = {
448
import c.universe._
449
450
val conditionString = show(condition.tree)
451
452
q"""
453
if (!$condition) {
454
throw new AssertionError(s"Assertion failed: $conditionString - " + $message)
455
}
456
"""
457
}
458
459
// 3. Performance measurement macro
460
def benchmarkImpl(c: Context)(name: c.Expr[String])(expr: c.Expr[Any]): c.Expr[Any] = {
461
import c.universe._
462
463
val resultVar = TermName(c.freshName("result"))
464
val startVar = TermName(c.freshName("start"))
465
val endVar = TermName(c.freshName("end"))
466
467
q"""
468
{
469
println(s"Starting benchmark: " + $name)
470
val $startVar = System.nanoTime()
471
val $resultVar = $expr
472
val $endVar = System.nanoTime()
473
val duration = ($endVar - $startVar) / 1000000.0
474
println(s"Completed benchmark: " + $name + s" in $duration ms")
475
$resultVar
476
}
477
"""
478
}
479
480
// 4. Compile-time string processing
481
def processStringImpl(c: Context)(str: c.Expr[String]): c.Expr[String] = {
482
import c.universe._
483
484
str.tree match {
485
case Literal(Constant(s: String)) =>
486
// Process string at compile time
487
val processed = s.toUpperCase.reverse
488
c.Expr[String](Literal(Constant(processed)))
489
case _ =>
490
c.abort(c.enclosingPosition, "String must be a literal")
491
}
492
}
493
494
// Macro definitions
495
def debugWithType(expr: Any): Any = macro debugWithTypeImpl
496
def assert(condition: Boolean, message: String): Unit = macro assertImpl
497
def benchmark[T](name: String)(expr: T): T = macro benchmarkImpl
498
def processString(str: String): String = macro processStringImpl
499
}
500
501
// Usage examples
502
import ComprehensiveMacros._
503
504
// Debug with type information
505
val list = debugWithType(List(1, 2, 3).map(_ * 2))
506
507
// Custom assertions
508
assert(list.length == 3, "List should have 3 elements")
509
510
// Performance measurement
511
val result = benchmark("List processing") {
512
(1 to 1000000).toList.filter(_ % 2 == 0).sum
513
}
514
515
// Compile-time string processing
516
val processed = processString("hello") // Becomes "OLLEH" at compile time
517
println(processed)
518
519
// Error handling in macros
520
def safeDebugImpl(c: Context)(expr: c.Expr[Any]): c.Expr[Any] = {
521
import c.universe._
522
523
try {
524
val result = q"""
525
{
526
val value = $expr
527
println(s"Value: " + value)
528
value
529
}
530
"""
531
c.Expr[Any](result)
532
} catch {
533
case ex: Exception =>
534
c.error(c.enclosingPosition, s"Macro expansion failed: ${ex.getMessage}")
535
expr
536
}
537
}
538
539
def safeDebug(expr: Any): Any = macro safeDebugImpl
540
```