我的项目中有一个方法,包含宏(whitebox),尝试从MethodSymbol
的返回类型验证并提取类型参数。
这是代码(放在一些带有import c.universe._
的类中):
private object ImplMethod {
def apply(m: MethodSymbol): ImplMethod = {
println(m.returnType)
val respType = m.returnType match {
case tq"scala.concurrent.Future[scala.util.Either[String, ${resp}]]" => resp
case _ =>
c.abort(c.enclosingPosition, s"Method ${m.name} in type ${m.owner} does not have required result type Future[Either[String, ?]]")
}
???
}
}
在编译期间,它说Warning:scalac: scala.concurrent.Future[Either[String, Int]]
是正确的,但是在它之后它会在c.abort
调用停止,这意味着模式与类型不匹配。
我甚至尝试在REPL中调试它,但这是我得到的:
val tq"scala.concurrent.Future[$a]" = typeOf[scala.concurrent.Future[Int]]
scala.MatchError: scala.concurrent.Future[Int] (of class
scala.reflect.internal.Types$ClassArgsTypeRef)
... 28 elided
我已经尝试了很多次但最终总是将这些类型作为String
处理,这是非常不清楚的。
谢谢你的回复!
答案 0 :(得分:1)
如果有人展示了使用quasiquotes和模式匹配来解构Type
的例子,我将不胜感激。目前在我看来,Type
和quasiquotes是不同宇宙的一部分(不在Scala内部意义上)并且不能相互作用。我知道做这样的事情的最好方法就是这样的代码:
val string = typeOf[String].dealias
val future = typeOf[scala.concurrent.Future[_]].typeConstructor
val either = typeOf[scala.util.Either[_, _]].typeConstructor
val respType = (for {
f <- Some(m.returnType.dealias) if f.typeConstructor == future // Future[_]
e <- f.typeArgs.headOption if e.typeConstructor == either // Future[Either[_,_]]
ea <- Some(e.typeArgs) if ea.head.dealias == string // Future[Either[String,_]]
} yield ea(1))
.getOrElse(c.abort(c.enclosingPosition, s"Method ${m.name} in type ${m.owner} does not have required result type Future[Either[String, ?]]"))
我使用Some
将Type
包装到Option
中,并使用for-comprehension语法,恕我直言,让您更容易理解正在发生的事情,比您尝试过的更容易在Type
上使用常规(基于非基于quququotes的)模式匹配。
更新:tq""
哪里有效?
根据我的经验,您可以使用tq""
解构类型的唯一上下文位于Macro annotations,可用于注释整个类或方法定义。请考虑以下示例:
import scala.concurrent.Future
import scala.util.Either
class Test {
@CheckReturnTypeMacroAnnotation
def foo1(): scala.concurrent.Future[scala.util.Either[String, Short]] = ???
@CheckReturnTypeMacroAnnotation
def foo2(): Future[Either[String, Int]] = ???
@CheckReturnTypeMacroAnnotation
def foo3(): scala.concurrent.Future[Either[String, Long]] = ???
@CheckReturnTypeMacroAnnotation
def foo4(): Future[scala.util.Either[String, Double]] = ???
@CheckReturnTypeMacroAnnotation
def fooBad() = scala.concurrent.Future.failed[scala.util.Either[String, Short]](new RuntimeException("Fake"))
}
我们希望CheckReturnTypeMacroAnnotation
确保返回类型的格式为scala.concurrent.Future[scala.util.Either[String, ?]]
。我们可以将CheckReturnTypeMacroAnnotation
实施为
import scala.language.experimental.macros
import scala.annotation.{StaticAnnotation, compileTimeOnly}
@compileTimeOnly("enable macro to expand macro annotations")
class CheckReturnTypeMacroAnnotation extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro CheckReturnTypeMacro.process
}
object CheckReturnTypeMacro {
import scala.reflect.macros._
import scala.reflect.macros.whitebox.Context
def process(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val methodDef = annottees.map(_.tree).toList match {
case x :: Nil => x
case _ => c.abort(c.enclosingPosition, "Method definition is expected")
}
//c.warning(c.enclosingPosition, s"methodDef ${methodDef.getClass} => $methodDef")
val returnType = methodDef match {
case q"$mods def $name[..$tparams](...$paramss): $tpt = $body" => tpt
case _ => c.abort(c.enclosingPosition, "Method definition is expected")
}
//c.warning(NoPosition, s"returnType ${returnType.getClass} => $returnType")
val respType = returnType match {
case tq"scala.concurrent.Future[scala.util.Either[String, ${resp}]]" =>
c.warning(c.enclosingPosition, s"1 resp ${resp.getClass} => $resp")
resp
case tq"Future[Either[String, ${resp}]]" =>
c.warning(c.enclosingPosition, s"2 resp ${resp.getClass} => $resp")
resp
case tq"scala.concurrent.Future[Either[String, ${resp}]]" =>
c.warning(c.enclosingPosition, s"3 resp ${resp.getClass} => $resp")
resp
case tq"Future[scala.util.Either[String, ${resp}]]" =>
c.warning(c.enclosingPosition, s"4 resp ${resp.getClass} => $resp")
resp
case _ =>
c.abort(c.enclosingPosition, s"Method does not have required result type Future[Either[String, ?]]")
}
c.Expr[Any](methodDef) //this is in fact a no-op macro. it only does verification of return type
}
}
但请注意,您必须如何处理具有不同但相似tq""
模式的各种情况,并且未明确指定返回类型的fooBad
无论如何都将失败。尝试使用此宏编译Test
的输出将生成如下输出:
Warning:(18, 8) 1 resp class scala.reflect.internal.Trees$Ident => Short
@CheckReturnTypeMacroAnnotation
Warning:(21, 8) 2 resp class scala.reflect.internal.Trees$Ident => Int
@CheckReturnTypeMacroAnnotation
Warning:(24, 8) 3 resp class scala.reflect.internal.Trees$Ident => Long
@CheckReturnTypeMacroAnnotation
Warning:(27, 8) 4 resp class scala.reflect.internal.Trees$Ident => Double
@CheckReturnTypeMacroAnnotation
Error:(31, 8) Method does not have required result type Future[Either[String, ?]]
@CheckReturnTypeMacroAnnotation
注意输出中实际存在的所有4个案例以及fooBad
是否失败。这个问题似乎来自于宏在wavepechecker之前运行的事实。不幸的是,我不知道如何使这种模式匹配真正起作用。我调查了reify
和c.typecheck
,但没有运气。