
com.avsystem.commons.macros.rpc.RPCMacros.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of commons-macros_2.12.0-RC2 Show documentation
Show all versions of commons-macros_2.12.0-RC2 Show documentation
AVSystem commons library for Scala
The newest version!
package com.avsystem.commons
package macros.rpc
import com.avsystem.commons.macros.AbstractMacroCommons
import scala.reflect.macros.blackbox
/**
* Author: ghik
* Created: 01/12/15.
*/
class RPCMacros(ctx: blackbox.Context) extends AbstractMacroCommons(ctx) {
import c.universe._
val RpcPackage = q"$CommonsPackage.rpc"
val FrameworkObj = c.prefix.tree
val RunNowEC = q"$CommonsPackage.concurrent.RunNowEC"
val RawRPCCls = tq"$FrameworkObj.RawRPC"
val AsRawRPCObj = q"$FrameworkObj.AsRawRPC"
val AsRawRPCCls = tq"$FrameworkObj.AsRawRPC"
val AsRealRPCObj = q"$FrameworkObj.AsRealRPC"
val AsRealRPCCls = tq"$FrameworkObj.AsRealRPC"
val AllRPCTypeClassesObj = q"$RpcPackage.AllRPCTypeClasses"
val RawValueCls = tq"$FrameworkObj.RawValue"
val ArgListsCls = tq"$ListCls[$ListCls[$RawValueCls]]"
val RealInvocationHandlerCls = tq"$FrameworkObj.RealInvocationHandler"
val RawInvocationHandlerCls = tq"$FrameworkObj.RawInvocationHandler"
val RPCMetadataObj = q"$RpcPackage.RPCMetadata"
val RPCMetadataCls = tq"$RpcPackage.RPCMetadata"
lazy val RPCFrameworkType = getType(tq"$RpcPackage.RPCFramework")
lazy val RPCNameType = getType(tq"$RpcPackage.RPCName")
lazy val RPCType = getType(tq"$RpcPackage.RPC")
lazy val MetadataAnnotationType = getType(tq"$RpcPackage.MetadataAnnotation")
lazy val RawValueType = getType(RawValueCls)
lazy val RawValueLLType = getType(ArgListsCls)
lazy val RawRPCType = getType(RawRPCCls)
lazy val RawRPCSym = RawRPCType.typeSymbol
def allAnnotations(tpe: Type) = {
val ts = tpe.typeSymbol
if (ts.isClass) ts.asClass.baseClasses.flatMap(_.annotations)
else Nil
}
def hasRpcAnnot(tpe: Type) =
allAnnotations(tpe).exists(_.tree.tpe <:< RPCType)
case class Variant(rawMethod: MethodSymbol, returnType: Type)
lazy val variants = RawRPCType.members.filter(s => s.isTerm && s.isAbstract)
.map { s =>
if (s.isMethod) {
val m = s.asMethod
val sig = m.typeSignatureIn(RawRPCType)
if (sig.typeParams.nonEmpty) {
abort(s"Bad signature ($m): RPC variant cannot be generic")
}
val returnType = sig.paramLists match {
case List(List(rpcNameParam, argListsParam))
if rpcNameParam.typeSignature =:= typeOf[String] && argListsParam.typeSignature =:= RawValueLLType =>
sig.finalResultType
case _ =>
abort(s"Bad signature ($m): RPC variant must take two parameters of types String and List[List[RawValue]]")
}
Variant(m, returnType)
} else {
abort("All abstract members in RawRPC must be methods that take two parameters of types String and List[List[RawValue]]")
}
}.toList
case class ProxyableMember(method: MethodSymbol, signature: Type) {
val returnType = signature.finalResultType
val typeParams = signature.typeParams
val paramLists = signature.paramLists
val rpcName = (method :: method.overrides).flatMap(_.annotations).find(_.tree.tpe <:< RPCNameType).map { annot =>
annot.tree.children.tail match {
case List(Literal(Constant(name: String))) => TermName(name)
case _ => c.abort(annot.tree.pos, "The argument of @RPCName must be a string literal.")
}
}.getOrElse(method.name)
def rpcNameString = rpcName.decodedName.toString
}
def checkRpc(tpe: Type): Unit = {
if (!hasRpcAnnot(tpe) || !tpe.typeSymbol.isClass || tpe <:< typeOf[AnyVal] || tpe <:< typeOf[Null]) {
abort(s"RPC type must be a trait or abstract class annotated as @RPC, $tpe is not.")
}
}
def proxyableMethods(tpe: Type) = {
checkRpc(tpe)
val proxyables = tpe.members.filter(m => m.isTerm && m.isAbstract).map { m =>
val signature = m.typeSignatureIn(tpe)
if (!m.isMethod || signature.typeParams.nonEmpty) {
abort(s"All abstract members in RPC interface must be non-generic methods, $m in $tpe is not.")
}
ProxyableMember(m.asMethod, signature)
}.toList
proxyables.groupBy(_.rpcName).foreach {
case (rpcName, members) if members.size > 1 =>
error(s"Multiple RPC methods have the same RPC name: $rpcName, you need to properly disambiguate them with @RPCName annotation")
case _ =>
}
if (proxyables.isEmpty) {
warning(s"$tpe has no abstract members that could represent remote methods.")
}
proxyables
}
def reifyList(args: List[Tree]) = q"$ListObj(..$args)"
def reifyListPat(args: List[Tree]) = pq"$ListObj(..$args)"
def asRawImpl[T: c.WeakTypeTag]: c.Tree = {
val rpcTpe = weakTypeOf[T]
inferOrMaterialize(getType(tq"$AsRawRPCCls[$rpcTpe]")) {
val proxyables = proxyableMethods(rpcTpe)
val implName = c.freshName(TermName("impl"))
def methodCase(variant: Variant, member: ProxyableMember): CaseDef = {
val paramLists = member.signature.paramLists
val matchedArgs = reifyListPat(paramLists.map(paramList => reifyListPat(paramList.map(ps => pq"${ps.name.toTermName}"))))
val methodArgs = paramLists.map(_.map(ps => q"$FrameworkObj.read[${ps.typeSignature}](${ps.name.toTermName})"))
val badMethodError = s"${member.method} cannot be handled by raw ${variant.rawMethod}"
val onFailure = q"throw new Exception($badMethodError)"
val realInvocation = q"$implName.${member.method}(...$methodArgs)"
cq"""
(${member.rpcNameString}, $matchedArgs) =>
$FrameworkObj.tryToRaw[${member.returnType},${variant.returnType}]($realInvocation,$onFailure)
"""
}
def defaultCase(variant: Variant): CaseDef =
cq"_ => fail(${rpcTpe.toString}, ${variant.rawMethod.name.toString}, methodName, args)"
def methodMatch(variant: Variant, methods: Iterable[ProxyableMember]) = {
Match(q"(methodName, args)", (methods.map(m => methodCase(variant, m)) ++ Iterator(defaultCase(variant))).toList)
}
def rawImplementation(variant: Variant) =
q"def ${variant.rawMethod.name}(methodName: String, args: $ListCls[$ListCls[$RawValueCls]]) = ${methodMatch(variant, proxyables)}"
// Thanks to `Materialized` trait, the implicit "self" has more specific type than a possible implicit
// that this macro invocation is assigned to.
q"""
new $AsRawRPCCls[$rpcTpe] with $FrameworkObj.RawRPCUtils with $MaterializedCls {
implicit def ${c.freshName(TermName("self"))}: $AsRawRPCCls[$rpcTpe] with $MaterializedCls = this
def asRaw($implName: $rpcTpe) =
new $RawRPCCls {
..${variants.map(rawImplementation)}
}
}
"""
}
}
def tryToRaw[Real: c.WeakTypeTag, Raw: c.WeakTypeTag](real: Tree, onFailure: Tree): Tree = {
val realTpe = weakTypeOf[Real]
val rawTpe = weakTypeOf[Raw]
val handlerType = getType(tq"$RealInvocationHandlerCls[$realTpe,_]")
val expectedHandlerType = getType(tq"$RealInvocationHandlerCls[$realTpe,$rawTpe]")
c.inferImplicitValue(handlerType) match {
case EmptyTree => q"implicitly[$expectedHandlerType].toRaw($real)" //force normal compilation error
case handler if handler.tpe <:< expectedHandlerType => q"$handler.toRaw($real)"
case _ => onFailure
}
}
def asRealImpl[T: c.WeakTypeTag]: c.Tree = {
val rpcTpe = weakTypeOf[T]
inferOrMaterialize(getType(tq"$AsRealRPCCls[$rpcTpe]")) {
val proxyables = proxyableMethods(rpcTpe)
val rawRpcName = c.freshName(TermName("rawRpc"))
val implementations = proxyables.map { m =>
val methodName = m.method.name
val paramLists = m.paramLists
val params = paramLists.map(_.map { ps =>
val implicitFlag = if (ps.isImplicit) Flag.IMPLICIT else NoFlags
ValDef(Modifiers(Flag.PARAM | implicitFlag), ps.name.toTermName, TypeTree(ps.typeSignature), EmptyTree)
})
val args = reifyList(paramLists.map(paramList =>
reifyList(paramList.map(ps => q"$FrameworkObj.write[${ps.typeSignature}](${ps.name.toTermName})"))))
q"def $methodName(...$params) = implicitly[$RawInvocationHandlerCls[${m.returnType}]].toReal($rawRpcName, ${m.rpcNameString}, $args)"
}
// Thanks to `Materialized` trait, the implicit "self" has more specific type than a possible implicit
// that this macro invocation is assigned to.
q"""
new $AsRealRPCCls[$rpcTpe] with $MaterializedCls {
implicit def ${c.freshName(TermName("self"))}: $AsRealRPCCls[$rpcTpe] with $MaterializedCls = this
def asReal($rawRpcName: $RawRPCCls) = new $rpcTpe { ..$implementations; () }
}
"""
}
}
def allRpcTypeClassesImpl[F: c.WeakTypeTag, T: c.WeakTypeTag]: Tree = {
val ftpe = weakTypeOf[F]
val frameworkObj = singleValueFor(ftpe).getOrElse(abort(s"Could not find singleton value for $ftpe"))
val tpe = weakTypeOf[T]
q"$AllRPCTypeClassesObj[$ftpe,$tpe]($frameworkObj.materializeAsReal[$tpe], $frameworkObj.materializeAsRaw[$tpe], $RPCMetadataObj.materialize[$tpe])"
}
def isRPC[T: c.WeakTypeTag]: Tree = {
checkRpc(weakTypeOf[T])
q"null"
}
def getterRealHandler[T: c.WeakTypeTag](ev: Tree): Tree = {
val tpe = weakTypeOf[T]
checkRpc(tpe)
q"new $FrameworkObj.GetterRealHandler[$tpe]"
}
def getterRawHandler[T: c.WeakTypeTag](ev: Tree): Tree = {
val tpe = weakTypeOf[T]
checkRpc(tpe)
q"new $FrameworkObj.GetterRawHandler[$tpe]"
}
def materializeMetadata[T: c.WeakTypeTag]: Tree = {
val rpcTpe = weakTypeOf[T]
inferOrMaterialize(getType(tq"$RPCMetadataCls[$rpcTpe]")) {
val proxyables = proxyableMethods(rpcTpe)
def reifyAnnotations(s: Symbol) = {
val trees = allAnnotations(s).iterator.map(_.tree).collect {
case tree if tree.tpe <:< MetadataAnnotationType => c.untypecheck(tree)
}.toList
q"$ListObj(..$trees)"
}
def reifyParamMetadata(s: Symbol) =
q"$RpcPackage.ParamMetadata(${s.name.decodedName.toString}, ${reifyAnnotations(s)})"
def reifySignature(ms: MethodSymbol) =
q"""
$RpcPackage.Signature(
${ms.name.decodedName.toString},
$ListObj(..${ms.paramLists.map(ps => q"$ListObj(..${ps.map(reifyParamMetadata)})")}),
${reifyAnnotations(ms)}
)
"""
q"""
new $RPCMetadataCls[$rpcTpe] with $MaterializedCls {
implicit def ${c.freshName(TermName("self"))}: $RPCMetadataCls[$rpcTpe] with $MaterializedCls = this
def name = ${rpcTpe.typeSymbol.name.decodedName.toString}
lazy val annotations = ${reifyAnnotations(rpcTpe.typeSymbol)}
lazy val signatures = $MapObj(..${proxyables.map(pm => q"${pm.rpcNameString} -> ${reifySignature(pm.method)}")})
lazy val getterResults = $MapObj[String,$RPCMetadataCls[_]](
..${proxyables.filter(pm => hasRpcAnnot(pm.returnType)).map(pm => q"${pm.rpcNameString} -> implicitly[$RPCMetadataCls[${pm.returnType}]]")}
)
}
"""
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy