I'm fairly new to Scala and very new to writing macros and looking for a little help/advise. I have the following code...
trait ValidationRule
case class Required() extends ValidationRule
case class HasLength(l: Int) extends ValidationRule
case class Person(name: String)
myMacro[Person] { p => p.name.is(Required(), HasLength(255)) }
Obviously there is some missing code here but this is just pseudo to get the question out.
So given a Tree representing p => p.name.is(Required(), HasLength(255))
I'm trying to write a match/case
to select out all expressions representing a ValidationRule
. Something like:
case TypeApply(Select(_, ....
Can anyone suggest the best match case to be able to extract a List of Trees that represent each "all" ValidationRules
from within the "is" method?
You should definitely look into Quasiquotes.
Quasiquotes are used to do two things: to build Trees, and to pattern match Trees. They allow you to express the tree that you want to work with in terms of the equivalent Scala code. You let the quasiquote library deal with how Scala code maps to a Tree graph, and that's a good thing!
You can play around with them in the REPL, though the results might be slightly different in the macro universe:
scala> import scala.reflect.runtime.universe._
scala> showRaw(cq"p => p.name.is(Required(), HasLength(255))")
res0: String = CaseDef(
Bind(
TermName("p"),
Ident(termNames.WILDCARD)),
EmptyTree,
Apply(
Select(
Select(
Ident(TermName("p")),
TermName("name")),
TermName("is")),
List(
Apply(
Ident(TermName("Required")),
List()),
Apply(
Ident(TermName("HasLength")),
List(Literal(Constant(255)))))))
The other thing you can do with Quasiquotes is to actually use them to pattern match.
scala> val fromTree = cq"p => p.name.is(Required(), HasLength(255))"
scala> val cq"p => p.name.is($x, $y)" = fromTree
x: reflect.runtime.universe.Tree = Required()
y: reflect.runtime.universe.Tree = HasLength(255)
Now, you have to be careful, because that pattern ONLY matches if the user named their pattern variable p
.
scala> val fromTree = cq"x => x.name.is(Required(), HasLength(255))"
scala> val cq"p => p.name.is($x, $y)" = fromTree
scala.MatchError: case (x @ _) => x.name.is(Required(), HasLength(255)) (of class scala.reflect.internal.Trees$CaseDef)
... 33 elided
Instead, you'll want to be a bit more generic:
scala> val cq"${p1:TermName} => ${p2:TermName}.name.is($x, $y)" = fromTree
p1: reflect.runtime.universe.TermName = x
p2: reflect.runtime.universe.TermName = x
x: reflect.runtime.universe.Tree = Required()
y: reflect.runtime.universe.Tree = HasLength(255)
scala> p1 == p2
res2: Boolean = true
And, of course, if you were doing this as part of a pattern match, you could do:
case cq"${p1:TermName} => ${p2:TermName}.name.is($x, $y)" if p1 == p2 =>
???
Keep in mind that Macros are a deep, dark hole. If you're just getting started, expect to spend a lot of time getting your macro code correct. After that, expect to spend a lot of time dealing with edge cases.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With