Can anyone think of an easy way to verify, at compile-time, that the compiler has actually created a compile-time constant from, say, a complex arithmetic expression? I'm guessing this might be some kind of annotation or macro, but maybe there's something simpler. For example, maybe something like:
@CompileTime final val HALF_INFINITY = Int.MaxValue / 2
would be possible.
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