Проверка приписывания типа varargs в макросах Scala

Предположим, у меня есть такой макрос:

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)
}

Это работает, как и ожидалось, с "настоящими" varargs:

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: _*) в моем первом примере выдает ошибку времени компиляции?


person Travis Brown    schedule 01.12.2012    source источник
comment
Когда вы пишете, я ожидал здесь ошибки времени компиляции, не могли бы вы пояснить? Вы ожидаете, что это будет ошибка, потому что это требование вашего домена? Или это должно быть ошибкой всяких макросов vararg?   -  person Eugene Burmako    schedule 03.12.2012
comment
@EugeneBurmako: Меня беспокоит, что в случае приписывания xs.head на самом деле вовсе не c.Expr[A] - это больше похоже на c.Expr[Seq[A]]. Вот несколько примеров.   -  person Travis Brown    schedule 03.12.2012


Ответы (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!")
    }
  }
}
person idonnie    schedule 01.12.2012
comment
Спасибо, но по сути это то же самое, что и мой BarExample, и в общем случае работать не будет. - person Travis Brown; 02.12.2012