Reputation: 1213
I have a method/constructor that takes a bunch of parameters. Using a scala macro I can ofcourse extract the Tree representing the type of those parameters.
But I cannot find out how to convert this tree to something "useful", i.e. that I can get the parent types of, check if it is a primitive, etc.
Lets say I have a concrete type C
and if want all parameters that inherit from C
or are subtypes of Seq[C]
.
For a bit of context:
case cd@q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$_ } with ..$_ { $self => ..$stats }" :: tail =>
// extract everything that is subtype of C or Seq[C]
val cs = paramss.head.map {
case q"$mods val $name: $tpt = $default" => ???
}
Everything that goes in a macro should be typechecked, right? So $tpt should have a "type"? How do I get it and what exactly do I get back?
Upvotes: 1
Views: 486
Reputation: 39577
I haven't used them in anger, but it looks like you're using macro annotations.
No, you get untyped annotees.
http://docs.scala-lang.org/overviews/macros/annotations.html
macro annottees are untyped, so that we can change their signatures
Given a client:
package thingy
class C
@testThing class Thingy(val c: C, i: Int) { def j = 2*i }
object Test extends App {
Console println Thingy(42).stuff
}
then then the macro implementation sees:
val t = (annottees map (c typecheck _.tree)) match {
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$_ } with ..$_ { $self => ..$stats }" :: _ =>
val p @ q"$mods val $name: ${tpt: Type} = $default" = paramss.head.head
Console println showRaw(tpt)
Console println tpt.baseClasses
toEmit
}
where the tree has been typechecked explicitly and the type is annotated in the extraction:
case q"val $name: ${tpt: Type} = ???" =>
I realize my spelling of annotee differs from the official docs. I just can't figure out why it should be different from devotee.
Here is the toy skeleton code, for other beginners:
package thingy
import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
class testThing extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro testThing.impl
}
object testThing {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val toEmit = q"""{
class Thingy(i: Int) {
def stuff = println(i)
}
object Thingy {
def apply(x: Int) = new Thingy(x)
}
()
}"""
def dummy = Literal(Constant(()))
//val t = (annottees map (_.tree)) match {
val t = (annottees map (c typecheck _.tree)) match {
case Nil =>
c.abort(c.enclosingPosition, "No test target")
dummy
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$_ } with ..$_ { $self => ..$stats }" :: _ =>
//val p @ q"$mods val $name: $tpt = $default" = paramss.head.head
val p @ q"$mods val $name: ${tpt: Type} = $default" = paramss.head.head
Console println showRaw(tpt)
Console println tpt.baseClasses
toEmit
/*
case (classDeclaration: ClassDef) :: Nil =>
println("No companion provided")
toEmit
case (classDeclaration: ClassDef) :: (companionDeclaration: ModuleDef) :: Nil =>
println("Companion provided")
toEmit
*/
case _ => c.abort(c.enclosingPosition, "Invalid test target")
dummy
}
c.Expr[Any](t)
}
}
Upvotes: 0
Reputation: 18148
This is how I determine if a type inherits from Iterable and not from Map, and if a type inherits from Map
val iterableType = typeOf[Iterable[_]].typeSymbol
val mapType = typeOf[Map[_, _]].typeSymbol
def isIterable(tpe: Type): Boolean = tpe.baseClasses.contains(iterableType) && !isMap(tpe)
def isMap(tpe: Type): Boolean = tpe.baseClasses.contains(mapType)
I use this on e.g. the fields in a class's primary constructor:
// tpe is a Type
val fields = tpe.declarations.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m
}.get.paramss.head
val iterableFields = fields.filter(f => isIterable(f.typeSignature))
val mapFields = fields.filter(f => isMap(f.typeSignature))
Upvotes: 1