I'm trying to generate Tree
for function that accepts case class value and returns value of case class parameter at given position. This is useful for extracting values of private parameters.
import reflect.runtime.currentMirror
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox
val tb = currentMirror.mkToolBox()
case class A(private val first: Int)
case class B(first: Int, private val second: Int)
def get(tpe: Type, position: Option[Int]): Tree = {
val pos = s"${position.map(p => s"._${p + 1}").getOrElse("")}"
tb.parse(s"(a: $tpe) => $tpe.unapply(a).get$pos")
}
println(tb.eval(get(typeOf[A], None)).asInstanceOf[(A) => Int](A(1)))
println(tb.eval(get(typeOf[B], Some(1))).asInstanceOf[(B) => Int](B(1, 2)))
Also I added following dependencies:
scalaVersion := "2.11.8"
libraryDependencies ++= Seq(
"org.scala-lang" % "scala-reflect" % scalaVersion.value,
"org.scala-lang" % "scala-compiler" % scalaVersion.value
)
position
is None
when case class has only one parameter.
My solution is working, but how can I get rid of tb.parse(s"...")
and replace it with quasiquote q"..."
?
I tried that but it fails with:
Don't know how to unquote here
[error] q"(a: $tpe) => $tpe.unapply(a).get$pos"
[error] ^
As I understood I can not insert into quasiquote some string that's constructed at runtime and q"..."
is parsed in compile time unlike tb.parse
. Am I right?
Also is it safe to interpolate like that s"(a: $tpe) => $tpe.unapply(a).get$pos"
? When using q"..."
syntax quasiquote knows that $tpe
is Type
but string interpolation makes a string from it. I'm not sure that this will work always and in more complex and specific cases.
You are not right. Quasiquotes can't just interpolate normal strings, they must interpolate other pieces of AST. If you build up some fragments of AST yourself, then you can use them with q
. Therefore, you can write this:
def getElem(tpe: Type, pos: Option[Int]): Tree = {
// Make something like TermName("_N")
val tupleAccess = pos.map("_" + _).map(TermName.apply)
val tupleExpr = {
val plain = q"${tpe.typeSymbol.companion}.unapply(a).get"
// Build into $companion.unapply(a).get(._N)
tupleAccess.foldLeft(plain)(Select.apply)
}
q"(a: $tpe) => $tupleExpr"
}
And voilĂ !
object A { object B { object C { case class D(private val x: Int, val y: String) } } }
val all = tb.eval(getElem(typeOf[A.B.C.D], None)).asInstanceOf[A.B.C.D => (Int, String)]
val int = tb.eval(getElem(typeOf[A.B.C.D], Some(1))).asInstanceOf[A.B.C.D => Int]
val str = tb.eval(getElem(typeOf[A.B.C.D], Some(2))).asInstanceOf[A.B.C.D => String]
val ds = {
val D = A.B.C.D
List(D(1, "one"), D(2, "two"), D(3, "three"))
}
ds.map(all) == List((1, "one"), (2, "two"), (3, "three"))
ds.map(int) == List(1, 2, 3)
ds.map(str) == List("one", "two", "three")