假设我有这个宏:
import language.experimental.macros
import scala.reflect.macros.Context
object FooExample {
def foo[A](xs: A*): Int = macro foo_impl[A]
def foo_impl[A](c: Context)(xs: c.Expr[A]*) = c.literal(xs.size)
}
这与"真实"可变参数:
scala> FooExample.foo(1, 2, 3)
res0: Int = 3
但是对于varargs类型的序列行为让我感到困惑(在Scala 2.10.0-RC3中):
scala> FooExample.foo(List(1, 2, 3): _*)
res1: Int = 1
并且表明推断类型没有任何可疑之处:
scala> FooExample.foo[Int](List(1, 2, 3): _*)
res2: Int = 1
我原本期望编译时错误,这就是我想要的。我已经在我编写的大多数宏中使用了以下方法:
object BarExample {
def bar(xs: Int*): Int = macro bar_impl
def bar_impl(c: Context)(xs: c.Expr[Int]*) = {
import c.universe._
c.literal(
xs.map(_.tree).headOption map {
case Literal(Constant(x: Int)) => x
case _ => c.abort(c.enclosingPosition, "bar wants literal arguments!")
} getOrElse c.abort(c.enclosingPosition, "bar wants arguments!")
)
}
}
这会在编译时发现问题:
scala> BarExample.bar(3, 2, 1)
res3: Int = 3
scala> BarExample.bar(List(3, 2, 1): _*)
<console>:8: error: bar wants literal arguments!
BarExample.bar(List(3, 2, 1): _*)
这对我来说感觉就像是一个黑客,但是它将一点验证(检查参数是文字)与另一个(确认我们真的有varargs)混合在一起。我还可以想象我不需要将参数作为文字(或者我希望它们的类型是通用的)的情况。
我知道我可以做到以下几点:
object BazExample {
def baz[A](xs: A*): Int = macro baz_impl[A]
def baz_impl[A](c: Context)(xs: c.Expr[A]*) = {
import c.universe._
xs.toList.map(_.tree) match {
case Typed(_, Ident(tpnme.WILDCARD_STAR)) :: Nil =>
c.abort(c.enclosingPosition, "baz wants real varargs!")
case _ => c.literal(xs.size)
}
}
}
但这是处理一个非常简单(并且我认为非常必要)的一点参数验证的丑陋方式。我在这里缺少一招吗?我能确保第一个示例中的foo(1 :: Nil: _*)
给出编译时错误的最简单方法是什么?
答案 0 :(得分:1)
这是否按预期工作?
object BarExample {
def bar(xs: Int*): Int = macro bar_impl
def bar_impl(c: Context)(xs: c.Expr[Int]*) = {
import c.universe._
import scala.collection.immutable.Stack
Stack[Tree](xs map (_.tree): _*) match {
case Stack(Literal(Constant(x: Int)), _*) => c.literal(x)
case _ => c.abort(c.enclosingPosition, "bar wants integer constant arguments!")
}
}
}