How can I splice in a type and a default value in Scala quasiquotes?
-
23-12-2019 - |
Question
I'm trying to make a type-provider that gives updated case classes.
How might I splice in a type and a default value (or omit the default value)?
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
import Flag._
val result = {
annottees.map(_.tree).toList match {
case q"$mods class $name[..$tparams](..$first)(...$rest) extends ..$parents { $self => ..$body }" :: Nil =>
val valType = //TODO
val valDefault = //TODO
val helloVal = q"""val x: $valType = $valDefault"""
q"$mods class $name[..$tparams](..$first, $helloVal)(...$rest) extends ..$parents { $self => ..$body }"
}
}
c.Expr[Any](result)
}
I've tried:
I've tried simply val valType = q"String"
, but then I get an error as if a default value was not found: not enough arguments for method apply
I've also tried splicing in a val defined as typeOf[String]
, and I've also tried splicing lists of ValDef
s into my q"$mods class...
(like I've seen done into a q"def...
in some similar questions on this site), but in each case there is a typer error.
Any tips? Thanks very much for looking.
Solution
You can use the tq
interpolator in the definition of valType
to create the type tree.
The rest is a little trickier. It seems to work just fine if you define the extra parameter directly:
q"""
$mods class $name[..$tparams](
..$first,
val x: $valType = $valDefault
)(...$rest) extends ..$parents { $self => ..$body }
"""
But when you define $helloVal
and then plug that in you end up without the default parameter flag. You could write a helper like this:
def makeDefault(valDef: ValDef) = valDef match {
case ValDef(mods, name, tpt, rhs) => ValDef(
Modifiers(
mods.flags | DEFAULTPARAM, mods.privateWithin, mods.annotations
),
name, tpt, rhs
)
}
Now you can write the following:
val valType = tq"String"
val valDefault = q""""foo""""
val helloVal = makeDefault(q"val x: $valType = $valDefault")
And everything should work as expected.