Luckily enough, macros are wired into typechecking (in the sense that macro arguments are typechecked prior to macro expansion), and typechecking folds constants, so it looks like it should be sufficient to check for Literal(Constant(_))
in a macro to make sure that macro's argument is a constant.
Note. Macro annotations implemented in macro paradise expand prior to typechecking of the annottees, which means that their arguments won't be constfolded during the expansion, making macro annotations a less convenient vehicle for carrying out this task.
Here's the code written with Scala 2.11.0-M8 syntax for def macros. For 2.11.0-M7, replace the import with import scala.reflect.macros.{BlackboxContext => Context}
. For 2.10.x, replace the import with import scala.reflect.macros.Context
, rewrite the signature of impl
to read def impl[T](c: Context)(x: c.Expr[T]) = ...
and the signature of ensureConstant
to read def ensureConstant[T](x: T): T = macro impl[T]
.
// Macros.scala
import scala.reflect.macros.blackbox._
import scala.language.experimental.macros
object Macros {
def impl(c: Context)(x: c.Tree) = {
import c.universe._
x match {
case Literal(Constant(_)) => x
case _ => c.abort(c.enclosingPosition, "not a compile-time constant")
}
}
def ensureConstant[T](x: T): T = macro impl
}
// Test.scala
import Macros._
object Test extends App {
final val HALF_INFINITY = ensureConstant(Int.MaxValue / 2)
final val HALF_INFINITY_PLUS_ONE = ensureConstant(HALF_INFINITY + 1)
final val notConst = ensureConstant(scala.util.Random.nextInt())
}
00:26 ~/Projects/Master/sandbox (master)$ scalac Macros.scala && scalac Test.scala
Test.scala:6: error: not a compile-time constant
final val notConst = ensureConstant(scala.util.Random.nextInt())
^
one error found
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…