macro-utils is a Scala 3 library providing helpers for building macros. Leverages visitor pattern to traverse data structures of different types. All major methods have a variant using StatementsCache abstraction to avoid nested splicing conflicts and cache/reuse produced methods.
CaseClassUtilsvisit and transfrom case class fields, names and valuesEnumUtilsvisit enums and sealed ADT hierarchies, and Java's enums, and build case pattern matcherUnionUtilsvisit types of the unionTupleUtilsvisit tuple and named tuple elementsOpaqueTypeUtilsunwrap the type is possibleSelectableUtilsmaybe visit selectable fieldsJavaRecordUtilsvisit record fields, names and valuesJavaMapUtilsvisit Java's Map names and valuesJavaIterableUtilsvisit Java' iterables
- Provided Utility Objects
- Dependencies
- Usage
- Examples
- Example: Using
CaseClassUtils.visitto Inspect Case Class Fields - Example: Using
CaseClassUtils.transformToListto create an instance with all string values upper cased - Example: Using
CaseClassUtils.transformToExprOfTupleto create a tuple from case class fields
- Example: Using
- Project content
- Scala >= 3.7.4
Use with SBT
libraryDependencies += "org.encalmo" %% "macro-utils" % "0.9.6"
or with SCALA-CLI
//> using dep org.encalmo::macro-utils:0.9.6
You can use CaseClassUtils.visit when you want to operate on each field of a case class, such as accumulating side effects or constructing values using a cache. The following macro visits each field of a case class and prints its name and value at compile time, leveraging the StatementsCache to build block of statements.
import org.encalmo.utils.CaseClassUtils
import org.encalmo.utils.StatementsCache
import scala.quoted.*
inline def printCaseClassFields[T](inline value: T): Unit = ${ printCaseClassFieldsImpl('value) }
def printCaseClassFieldsImpl[T: Type](value: Expr[T])(using Quotes): Expr[Unit] = {
given StatementsCache = new StatementsCache
printCaseClassFieldsUsingCache[T](value)
}
def printCaseClassFieldsUsingCache[T: Type](valueExpr: Expr[T])(using cache: StatementsCache): Expr[Unit] = {
given cache.quotes.type = cache.quotes
import cache.quotes.reflect.*
CaseClassUtils.visit[T](using cache)(
valueExpr.asTerm,
[A: Type] =>
(name, value, annotations) =>
val term = MethodUtils.callPrintln(using cache.quotes)(
Literal(StringConstant(name)),
Literal(StringConstant(": ")),
StringUtils.applyToString(value)
)
cache.addStatement(term)
)
cache.getBlockExprOfUnit
}Usage:
case class Point(x: Int, y: Int)
printCaseClassFields(Point(10, 20))Output:
x: 10
y: 20
This demonstrates how to walk through a case class with CaseClassUtils.visit and perform a custom action for each field, using the macro utilities' cache for correct code generation.
Suppose you want to write a macro that visits all fields of a case class and prints their names and values at compile time. The CaseClassUtils.collect method can be used to achieve this in a concise way.
import org.encalmo.utils.CaseClassUtils
import scala.quoted.*
inline def printCaseClassFields[T](inline value: T): Unit = ${ printCaseClassFieldsImpl('value) }
def printCaseClassFieldsImpl[T: Type](value: Expr[T])(using Quotes): Expr[Unit] = {
CaseClassUtils.collect[T](
value,
[A: Type] => (nameExpr, valueExpr, annotations) =>
'{ println(${ nameExpr } + ": " + ${ valueExpr }.toString) }
)
}Usage:
case class Person(name: String, age: Int)
printCaseClassFields(Person("Alice", 30))The output will be:
name: Alice
age: 30
This demonstrates how to traverse the fields of a case class using CaseClassUtils.visit and apply a custom function to each field.
Example: Using CaseClassUtils.transformToList to create an instance with all string values upper cased
This shows how to write a macro using CaseClassUtils.transformToList that upper-cases all String fields, while leaving other field types unchanged.
import org.encalmo.utils.CaseClassUtils
import org.encalmo.utils.AnnotationUtils.AnnotationInfo
import scala.quoted.*
inline def upperCaseStringFields[T, R <: Product](inline value: T): R =
${ upperCaseStringFieldsImpl[T, R]('value) }
def upperCaseStringFieldsImpl[T: Type, R <: Product: Type](value: Expr[T])(using Quotes): Expr[R] = {
import quotes.reflect.*
val args: List[quotes.reflect.Term] =
CaseClassUtils.transformToList[T, quotes.reflect.Term](
value,
[A: Type] =>
(nameExpr: Expr[String], valueExpr: Expr[A], annotations: Set[AnnotationInfo]) =>
Some {
Type.of[A] match {
case '[String] =>
'{ ${ valueExpr.asExprOf[String] }.toUpperCase }.asTerm
case _ =>
valueExpr.asTerm
}
}
)
CaseClassUtils.createInstanceUsingConstructor[R](args)
}Usage:
case class User(name: String, email: String, age: Int)
val user = User("alice", "alice@example.com", 30)
assert(upperCaseStringFields[User, User](user) == User("ALICE", "ALICE@EXAMPLE.COM", 30))Sometimes you might want to produce a tuple of fields or computed values from a case class at compile-time. Here's how you can use CaseClassUtils.transformToExprOfTuple in a macro to do this.
import org.encalmo.utils.CaseClassUtils
import org.encalmo.utils.AnnotationUtils.AnnotationInfo
import scala.quoted.*
inline def upperCaseStringFields2[T, R <: Product](inline value: T): R =
${ upperCaseStringFields2Impl[T, R]('value) }
def upperCaseStringFields2Impl[T: Type, R <: Product: Type](value: Expr[T])(using Quotes): Expr[R] = {
val tuple: Expr[Tuple] = CaseClassUtils.transformToExprOfTuple[T](
value,
[A: Type] =>
(nameExpr: Expr[String], valueExpr: Expr[A], annotations: Set[AnnotationInfo]) =>
Some {
Type.of[A] match
case '[String] =>
'{ ${ valueExpr.asExprOf[String] }.toUpperCase }
case _ =>
valueExpr
}
)
CaseClassUtils.createInstanceFromTuple[R](tuple)
}Usage:
case class User(name: String, email: String, age: Int)
val user = User("alice", "alice@example.com", 30)
assert(upperCaseStringFields2[User, User](user) == User("ALICE", "ALICE@EXAMPLE.COM", 30))├── .github
│ └── workflows
│ ├── pages.yaml
│ ├── release.yaml
│ └── test.yaml
│
├── .gitignore
├── .scalafmt.conf
├── AnnotationUtils.scala
├── CaseClassUtils.scala
├── CaseClassUtils.test.scala
├── CaseClassUtilsTestMacro.test.scala
├── DebugUtils.scala
├── EnumUtils.scala
├── EnumUtils.test.scala
├── EnumUtilsTestMacro.test.scala
├── JavaIterableUtils.scala
├── JavaMapUtils.scala
├── JavaRecordUtils.scala
├── JavaRecordUtils.test.scala
├── JavaRecordUtilsTestMacro.test.scala
├── LICENSE
├── MethodUtils.scala
├── MethodUtils.test.scala
├── MethodUtilsTestMacro.test.scala
├── OpaqueTypeUtils.scala
├── OpaqueTypeUtils.test.scala
├── OpaqueUtilsTestMacro.test.scala
├── Order.java
├── project.scala
├── README.md
├── SelectableUtils.scala
├── StatementsCache.scala
├── Status.java
├── test.sh
├── TestModel.test.scala
├── TupleUtils.scala
├── TupleUtils.test.scala
├── TupleUtilsTestMacro.test.scala
├── TypeNameUtils.scala
├── TypeNameUtils.test.scala
├── TypeUtils.scala
├── TypeUtils.test.scala
├── UnionUtils.scala
├── UnionUtils.test.scala
└── UnionUtilsTestMacro.test.scala