
autowire.Macros.scala Maven / Gradle / Ivy
The newest version!
package autowire
import scala.concurrent.Future
import scala.reflect.macros.Context
import language.experimental.macros
//import acyclic.file
import Core._
object Macros {
sealed trait Check[T] {
def map[V](f: T => V): Check[V]
def flatMap[V](f: T => Check[V]): Check[V]
def withFilter(f: T => Boolean): Check[T]
}
case class Luz[T](s: String) extends Check[T] {
def map[V](f: T => V) = Luz[V](s)
def flatMap[V](f: T => Check[V]) = Luz[V](s)
def withFilter(f: T => Boolean) = Luz[T](s)
}
case class Win[T](t: T, s: String) extends Check[T] {
def map[V](f: T => V) = Win(f(t), s)
def flatMap[V](f: T => Check[V]) = f(t)
def withFilter(f: T => Boolean) = if (f(t)) this else Luz(s)
}
class MacroHelp[C <: Context](val c: C) {
import c.universe._
def futurize(t: Tree, member: MethodSymbol) = {
if (member.returnType <:< c.typeOf[Future[_]]) t
else q"scala.concurrent.Future.successful($t)"
}
def extractableMembers(curCls: Type) = {
def isAMemberOfAnyRef(member: Symbol) = weakTypeOf[AnyRef].members.exists(_.name == member.name)
val membersOfBaseAndParents: Iterable[Symbol] = curCls.declarations ++ curCls.baseClasses.map(_.asClass.toType.declarations).flatten
for {
member <- membersOfBaseAndParents.toList.distinct
if !isAMemberOfAnyRef(member)
if !member.isSynthetic
if member.isPublic
if member.isTerm
memTerm = member.asTerm
if memTerm.isMethod
} yield {
member -> memTerm.asMethod
}
}
def getValsOrMeths(curCls: Type): Iterable[Either[(c.Symbol, MethodSymbol), (c.Symbol, MethodSymbol)]] = {
extractableMembers(curCls) flatMap { case (member, memTerm) =>
if (memTerm.isGetter) {
//This is a val (or a var-getter) so we will need to recur here
Seq(Left(member -> memTerm))
} else if (memTerm.isSetter || memTerm.isConstructor) {
//Ignore setters and constructors
Nil
} else {
Seq(Right(member -> memTerm))
}
}
}
def extractMethod(
pickleType: WeakTypeTag[_],
meth: MethodSymbol,
outerPath: Seq[String],
innerPath: Seq[String],
target: Expr[Any],
curCls: c.universe.Type,
innerPathOnly: Boolean): c.universe.Tree = {
val flattenedArgLists = meth.paramss.flatten
def hasDefault(i: Int) = {
val defaultName = s"${meth.name}$$default$$${i + 1}"
if (curCls.members.exists(_.name.toString == defaultName)) {
Some(defaultName)
} else {
None
}
}
val argName = c.fresh[TermName]("args")
val args: Seq[Tree] = flattenedArgLists.zipWithIndex.map { case (arg, i) =>
val default = hasDefault(i) match {
case Some(defaultName) => q"scala.util.Right(($target).${newTermName(defaultName)})"
case None => q"scala.util.Left(autowire.Error.Param.Missing(${arg.name.toString}))"
}
q"""autowire.Internal.read[$pickleType, ${arg.typeSignature}](
$argName,
$default,
${arg.name.toString},
${c.prefix}.read[${arg.typeSignature}](_)
)
"""
}
//val memSel = c.universe.newTermName(memPath.mkString("."))
val bindings = args.foldLeft[Tree](q"Nil") { (old, next) =>
q"$next :: $old"
}
val nameNames: Seq[TermName] = flattenedArgLists.map(x => x.name.toTermName)
val assignment = flattenedArgLists.foldLeft[Tree](q"Nil") { (old, next) =>
pq"scala.::(${next.name.toTermName}: ${next.typeSignature} @unchecked, $old)"
}
val memSel = innerPath.foldLeft(q"($target)") { (cur, nex) =>
q"$cur.${c.universe.newTermName(nex)}"
}
val futurized = futurize(q"$memSel(..$nameNames)", meth)
val frag = if (innerPathOnly) {
cq""" autowire.Core.Request(_, Seq(..$innerPath), $argName) =>
autowire.Internal.doValidate($bindings) match{ case (..$assignment) =>
$futurized.map(${c.prefix}.write(_))
case _ => ???
}
"""
} else {
cq""" autowire.Core.Request(Seq(..$outerPath), Seq(..$innerPath), $argName) =>
autowire.Internal.doValidate($bindings) match{ case (..$assignment) =>
$futurized.map(${c.prefix}.write(_))
case _ => ???
}
"""
}
frag
}
def getAllRoutesForClass(
pickleType: WeakTypeTag[_],
target: Expr[Any],
curCls: Type,
outerPath: Seq[String],
innerPath: Seq[String],
innerPathOnly: Boolean
): Iterable[c.universe.Tree] = {
//See http://stackoverflow.com/questions/15786917/cant-get-inherited-vals-with-scala-reflection
//Yep case law to program WUNDERBAR!
getValsOrMeths(curCls).flatMap {
case Left((m, t)) => Nil
//Vals / Vars
getAllRoutesForClass(
pickleType,
target,
m.typeSignature,
outerPath,
innerPath :+ m.name.toString,
innerPathOnly = innerPathOnly)
case Right((m, t)) =>
//Methods
Seq(extractMethod(
pickleType,
t,
outerPath,
innerPath :+ m.name.toString,
target,
curCls,
innerPathOnly = innerPathOnly))
}
}
}
def subProxy[Trait, SubTrait, PickleType, Reader[_], Writer[_]]
(c: Context)
(path: c.Expr[Trait => SubTrait])
(implicit strt: c.WeakTypeTag[SubTrait],
pt: c.WeakTypeTag[PickleType],
rt: c.WeakTypeTag[Reader[_]],
wt: c.WeakTypeTag[Writer[_]]
)
:
c.Expr[ClientProxy[SubTrait, PickleType, Reader, Writer]] = {
import c.universe._
val pathElems = {
def getMempath(tree: Tree, path: List[String]): List[String] = {
tree match {
case Select(t, n) =>
getMempath(t, n.toTermName.toString :: path)
case _ =>
path
}
}
getMempath(path.tree.children(1), Nil)
}
val res = q"""new autowire.ClientProxy[$strt, $pt, ${rt.tpe.typeConstructor}, ${wt.tpe.typeConstructor}](${c.prefix}, $pathElems)"""
c.Expr(res)
}
def autoProxy[Trait, SubTrait, PickleType, Reader[_], Writer[_]]
(c: Context)
(path: c.Expr[Trait => SubTrait])
(implicit
trt: c.WeakTypeTag[Trait],
strt: c.WeakTypeTag[SubTrait],
pt: c.WeakTypeTag[PickleType],
rt: c.WeakTypeTag[Reader[_]],
wt: c.WeakTypeTag[Writer[_]]
)
:
c.Expr[SubTrait] = {
import c.universe._
val help = new MacroHelp[c.type](c)
val pathElems = {
def getMempath(tree: Tree, path: List[String]): List[String] = {
tree match {
case Select(t, n) =>
getMempath(t, n.toTermName.toString :: path)
case _ =>
path
}
}
getMempath(path.tree.children(1), Nil)
}
val mems = help.getValsOrMeths(strt.tpe)
val meths = mems.map {
case Left((s, vs)) =>
c.abort(c.enclosingPosition, "Autoproxying of vals not yet supported!")
case Right((s, ms)) =>
val vparamss = ms.paramss.map(_.map {
paramSymbol => ValDef(
Modifiers(Flag.PARAM, tpnme.EMPTY, List()),
paramSymbol.name.toTermName,
TypeTree(paramSymbol.typeSignature),
EmptyTree)
})
val flattenedArgLists = ms.paramss.flatten
val nameNames: Seq[TermName] = flattenedArgLists.map(x => x.name.toTermName)
val memSel = (pathElems:+ms.name.toString).foldLeft(q"${c.prefix}[$trt]") { (cur, nex) =>
q"$cur.${c.universe.newTermName(nex)}"
}
val body = q"$memSel(..$nameNames).call()"
if (!ms.returnType.toString.contains("scala.concurrent.Future")) {
c.abort(c.enclosingPosition, s"All methods in autoproxied traits must return Futures! $ms did not")
}
DefDef(
Modifiers(),
s.name.toTermName,
Nil,
vparamss,
tq"${ms.returnType}",
body
)
}.toList
val res = q"""{
import autowire._
new $strt {
..$meths
}
}
"""
c.Expr(res)
}
def clientMacro[Result]
(c: Context)
()
(implicit r: c.WeakTypeTag[Result])
: c.Expr[Future[Result]] = {
import c.universe._
object Pkg {
def unapply(t: Tree): Option[Tree] = {
if (Seq("autowire.this", "autowire").contains(t.toString())) Some(t)
else None
}
}
val res = for {
q"${Pkg(_)}.`package`.$callableName[$t]($contents)" <- Win(c.prefix.tree,
"You can only use .call() on the Proxy returned by autowire.Client.apply, not " + c.prefix.tree
)
if Seq("clientFutureCallable", "clientCallable").contains(callableName.toString)
// If the tree is one of those default-argument containing blocks or
// functions, pry it apart such that the main logic can operate on the
// inner tree, and leave instructions on how
(unwrapTree: Tree, methodName: TermName, args: Seq[Tree], prelude: Seq[Tree], deadNames: Seq[String]@unchecked) = (contents: Tree) match {
case x@q"$unwrapTree.$methodName(..$args)" =>
//Normal tree
(unwrapTree, methodName, args, Nil, Nil)
case t@q"..${statements: List[ValDef]@unchecked}; $thing.$call(..$args)"
if statements.forall(_.isInstanceOf[ValDef]) =>
//Default argument tree
val (liveStmts, deadStmts) = statements.tail.partition {
case ValDef(mod, _, _, Select(singleton, name))
if name.toString.contains("$default") => false
case _ => true
}
val ValDef(_, _, _, rhs) = statements.head
(rhs, call, args, liveStmts, deadStmts.map(_.name))
case x =>
c.abort(x.pos, s"You can't call the .call() method on $x, only on autowired function calls.")
}
(oTree, innerPath) = {
def getMempath(tree: Tree, path: List[String]): (Tree, List[String]) = {
tree match {
case Select(t, n) =>
getMempath(t, n.toTermName.toString :: path)
case _ =>
tree -> path
}
}
getMempath(unwrapTree, List(methodName.toString))
}
q"${Pkg(_)}.`package`.unwrapClientProxy[$trt, $pt, $rb, $wb]($proxy)" <- Win(oTree,
s"XX You can't call the .call() method on $contents, only on autowired function calls"
)
trtTpe: Type = trt.tpe
outerPath =
trtTpe
.widen
.typeSymbol
.fullName
.toString
.split('.')
.toSeq
method = {
// Look for method in the trait and in its base classes.
def findMember(tpe: Type, name: TermName): Option[Symbol] = {
(Iterator.single(tpe.declaration(name)) ++
tpe.baseClasses.iterator.map(_.asClass.toType.declaration(name)))
.find(_ != NoSymbol)
}
def loop(path: List[String], tpe: Type, lastMem: Option[Symbol]): MethodSymbol = {
path match {
case Nil =>
lastMem match {
case Some(mem) if mem.isMethod => mem.asMethod
case Some(mem) => c.abort(c.enclosingPosition, s"Error while creating route proxy, expect method, $mem in $tpe")
case None => c.abort(c.enclosingPosition, s"Error while creating route proxy, expect missing member in $tpe")
}
case name :: rem =>
findMember(tpe, newTermName(name)) match {
case None =>
c.abort(c.enclosingPosition, s"Error while creating route proxy, unable to find $name in $tpe")
case Some(mem) =>
val memTpe = mem.typeSignature.widen
loop(rem, memTpe, Some(mem))
}
}
}
loop(innerPath, trtTpe, None)
}
pickled = args
.zip(method.paramss.flatten)
.filter {
case (Ident(name: TermName), _) => !deadNames.contains(name)
case (q"$thing.$name", _) if name.toString.contains("$default$") => false
case _ => true
}
.map { case (t, param: Symbol) => q"${param.name.toString} -> $proxy.self.write($t)"}
} yield {
q"""{
..$prelude;
$proxy.self.doCall(
autowire.Core.Request(Seq(..$outerPath), $proxy.prefixPath ++ Seq(..$innerPath), Map(..$pickled))
).map($proxy.self.read[${r}](_))
}"""
}
res match {
case Win(tree, s) => c.Expr[Future[Result]](tree)
case Luz(s) => c.abort(c.enclosingPosition, s)
}
}
private def routeMacroCommon[Trait, PickleType, C <: Context]
(c: C)
(target: c.Expr[Trait], innerPathOnly: Boolean)
(implicit t: c.WeakTypeTag[Trait], pt: c.WeakTypeTag[PickleType])
: c.Expr[Router[PickleType]] = {
import c.universe._
val help = new MacroHelp[c.type](c)
val topClass = weakTypeOf[Trait]
val routes = help.getAllRoutesForClass(
pt,
target,
topClass,
outerPath = topClass.typeSymbol.fullName.toString.split('.').toSeq,
innerPath = Nil,
innerPathOnly = innerPathOnly
).toList
val res = q"{case ..$routes}: autowire.Core.Router[$pt]"
// println("RES", res)
c.Expr(res)
}
def routeMacro[Trait, PickleType]
(c: Context)
(target: c.Expr[Trait])
(implicit t: c.WeakTypeTag[Trait], pt: c.WeakTypeTag[PickleType])
: c.Expr[Router[PickleType]] = {
routeMacroCommon[Trait, PickleType, c.type](c)(target, innerPathOnly = false)
}
def innerRouteMacro[Trait, PickleType]
(c: Context)
(target: c.Expr[Trait])
(implicit t: c.WeakTypeTag[Trait], pt: c.WeakTypeTag[PickleType])
: c.Expr[Router[PickleType]] = {
routeMacroCommon[Trait, PickleType, c.type](c)(target, innerPathOnly = true)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy