在编译时,有没有人能想到一种简单的方法来验证编译器是否实际上从一个复杂的算术表达式创建了一个编译时常量?我猜这可能是某种注释或宏,但也许有更简单的东西。例如,可能是:
@CompileTime final val HALF_INFINITY = Int.MaxValue / 2
是可能的。
答案 0 :(得分:10)
幸运的是,宏被连接到类型检查(在宏扩展之前宏观参数被强调了),并且类型检查会折叠常量,因此看起来它应该足以检查宏中的Literal(Constant(_))
确保宏的参数是常量。
请注意。在宏观天堂中实现的宏注释在对注释进行类型检查之前进行扩展,这意味着它们的参数在扩展期间不会被包围,使得宏注释成为执行此任务的不太方便的工具。
这是使用Scala 2.11.0-M8语法为def宏编写的代码。对于2.11.0-M7,将导入替换为import scala.reflect.macros.{BlackboxContext => Context}
。对于2.10.x,请将导入替换为import scala.reflect.macros.Context
,将impl
的签名重写为def impl[T](c: Context)(x: c.Expr[T]) = ...
,将ensureConstant
的签名重写为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
答案 1 :(得分:3)
我想即使使用宏也不可能:
import scala.reflect.macros.Context
import scala.language.experimental.macros
def showMacroImpl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
val inputs = annottees.map(_.tree).toList
println(inputs.map{showRaw(_)})
c.Expr[Any](Block(inputs, Literal(Constant(()))))
}
import scala.annotation.StaticAnnotation
class showMacro extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro showMacroImpl
}
object Test {
@showMacro final val i = 1+1
@showMacro final val j = util.Random.nextInt()
@showMacro final val k = Int.MaxValue / 2
}
// List(ValDef(Modifiers(FINAL), newTermName("i"), TypeTree(), Apply(Select(Literal(Constant(1)), newTermName("$plus")), List(Literal(Constant(1))))))
// List(ValDef(Modifiers(FINAL), newTermName("j"), TypeTree(), Apply(Select(Select(Ident(newTermName("util")), newTermName("Random")), newTermName("nextInt")), List())))
// List(ValDef(Modifiers(FINAL), newTermName("k"), TypeTree(), Apply(Select(Select(Ident(newTermName("Int")), newTermName("MaxValue")), newTermName("$div")), List(Literal(Constant(2))))))
此处i
,j
和k
之间没有区别。
即使使用scalac -Xprint:cleanup test.scala
,您也无法获得任何信息:
final <stable> <accessor> def i(): Int = 2;
final <stable> <accessor> def j(): Int = Test.this.j;
final <stable> <accessor> def k(): Int = 1073741823;
您只能从.icode
文件(scalac -Xprint:all test.scala; cat Test\$.icode
)获取此信息:
def i(): Int(2) {
locals:
startBlock: 1
blocks: [1]
1:
2 CONSTANT(2)
2 RETURN(INT)
}
def k(): Int(1073741823) {
locals:
startBlock: 1
blocks: [1]
1:
4 CONSTANT(1073741823)
4 RETURN(INT)
}
或者从java字节码(javap -c Test\$.class
):
public final int i();
Code:
0: iconst_2
1: ireturn
public final int k();
Code:
0: ldc #21 // int 1073741823
2: ireturn
答案 2 :(得分:2)
你说你的问题是关于确定一个值是否在参考点上在线扩展,但看起来你实际上正在寻找一种方法来保证它。这是对的吗?
如果你为def
注明了内联扩展(@inline
),你可能会得到你想要的。
@inline def TwentyTwo = 22