All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 getValsOrMeths(curCls: Type): Iterable[Either[(c.Symbol, MethodSymbol), (c.Symbol, MethodSymbol)]] = {
      def isAMemberOfAnyRef(member: Symbol) = weakTypeOf[AnyRef].members.exists(_.name == member.name)
      val membersOfBaseAndParents: Iterable[Symbol] = curCls.declarations ++ curCls.baseClasses.map(_.asClass.toType.declarations).flatten
      val extractableMembers = for {
        member <- membersOfBaseAndParents
        if !isAMemberOfAnyRef(member)
        if !member.isSynthetic
        if member.isPublic
        if member.isTerm
        memTerm = member.asTerm
        if memTerm.isMethod
      } yield {
        member -> memTerm.asMethod
      }

      extractableMembers 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,
      memPath: Seq[String],
      target: Expr[Any],
      curCls: c.universe.Type): c.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 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 = memPath.foldLeft(q"($target)") { (cur, nex) =>
        q"$cur.${c.universe.newTermName(nex)}"
      }

      val futurized = futurize(q"$memSel(..$nameNames)", meth)

      val frag = cq"""($argName) => autowire.Internal.doValidate($bindings) match {
        case (..$assignment) =>$futurized.map(${c.prefix}.write(_))
        case _ => ???
      }"""

      q"new autowire.Internal.RouteLeaf({case $frag})"
    }

    def getRouteTreeForClass(
      pt: WeakTypeTag[_],
      target: Expr[Any],
      curCls: Type,
      memPath: Seq[String]): c.Tree = {
      val children: Iterable[c.Tree] = getValsOrMeths(curCls).map {
        case Left((m,t)) =>
          val node = getRouteTreeForClass(pt,target,m.typeSignature, memPath :+ m.name.toString)
          q"""(${m.name.toString},$node)"""

        case Right((m,t)) =>
          val leaf = extractMethod(pt,t,memPath :+ m.name.toString,target,curCls)
          q"""(${m.name.toString},$leaf)"""
      }
      q"new autowire.Internal.RouteNode(Map(..$children))"
    }

  }

  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, memPath) = {
        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

      prePath =
      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(memPath, 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 {
      val fullPath = prePath ++ memPath
      q"""{
        ..$prelude;
        $proxy.self.doCall(
          autowire.Core.Request(Seq(..$fullPath), 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)
    }
  }

  def routeMacro[Trait, PickleType]
    (c: Context)
    (target: c.Expr[Trait])
    (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 partial = help.getRouteTreeForClass(pt,target,topClass,Nil)
    val routes = topClass.typeSymbol.fullName.toString.split('.').reverse.foldLeft(partial) {
      case (acc,prefix) => q"new autowire.Internal.RouteNode(Map($prefix->$acc))"
    }
    val res = q"""new autowire.Internal.RouterContext[$pt]($routes).router"""
    c.Expr(res)
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy