11

Given how difficult it is to know whether an arithmetic final val expression will be compiled to a compile-time constant, and how easy it is to accidentally break compile-time-ness...

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.

4

3 回答 3

11

幸运的是,宏被连接到类型检查中(从某种意义上说,宏参数在宏扩展之前进行类型检查),并且类型检查折叠常量,所以看起来应该足以Literal(Constant(_))在宏中检查以确保宏的参数是持续的。

笔记。在宏天堂中实现的宏注释在对被注释者进行类型检查之前扩展,这意味着它们的参数在扩展期间不会被 constfolded,使得宏注释成为执行此任务的不太方便的工具。

这是使用 Scala 2.11.0-M8 语法为 def 宏编写的代码。对于 2.11.0-M7,将导入替换为import scala.reflect.macros.{BlackboxContext => Context}. 对于 2.10.x,将 import 替换为import scala.reflect.macros.Context,重写implto read 的签名和 to read的def impl[T](c: Context)(x: c.Expr[T]) = ...签名。ensureConstantdef 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
于 2014-01-20T21:27:37.810 回答
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))))))

ij这里没有区别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       
于 2014-01-20T20:24:32.403 回答
2

您将问题表述为关于确定一个值是否在参考点处在线扩展,但似乎您实际上正在寻找一种方法来保证它。那是对的吗?

如果您将其设为带有def注释的内联扩展 ( @inline),您可能会得到您想要的。

@inline def TwentyTwo = 22
于 2014-01-20T20:18:10.047 回答