【问题标题】:Is there a way to test at compile-time that a constant is a compile-time constant?有没有办法在编译时测试常量是编译时常量?
【发布时间】:2014-02-10 02:53:11
【问题描述】:

给定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...

谁能想出一种简单的方法来在编译时验证编译器实际上已经从一个复杂的算术表达式中创建了一个编译时常量?我猜这可能是某种注释或宏,但也许有更简单的东西。例如,可能是这样的:

   @CompileTime final val HALF_INFINITY = Int.MaxValue / 2

有可能。

【问题讨论】:

    标签: scala scala-macros compile-time-constant


    【解决方案1】:

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

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

    @inline def TwentyTwo = 22
    

    【讨论】:

    • 1.你可以在这里使用val@inline val TwentyTwo = 22。 2. 不,它不会使TwentyTwo 成为java 字节码的常量。试试@inline val j = util.Random.nextInt() - 这绝对不是编译时间常数。
    • 是的,谢谢,我想要一种方法来保证它。我正在对需要极高性能的大型集合进行一些算术密集型工作。所以我想确保编译器在编译时可以访问常量的值。
    • 很明显,如果方法体中有参数或副作用,它不会是任何你称之为常量的东西,但我认为提问者不会这样做,它使整个前提无效问题。
    【解决方案2】:

    我想即使使用宏也不可能:

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

    这里ijk没有区别。

    即使使用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       
    

    【讨论】:

    • 努力的 A。我想也许一种有点丑陋的做法可能是一个将常量作为参数的 following 注释。据我了解,至少在 Java 中,注解的整数参数必须是编译时常量。丑陋的部分是,AFAIK,注释必须有某种虚拟的“主题”。
    • @EdStaub:注意Test.i不是常量,它是一个返回常量的方法。
    • 哎呀,是的,谢谢,我错过了(Test.i 是一种方法)。我想我需要查看 val 的潜在编译时用途,以查看该方法是否被调用或编译器是否将其短路并插入常量值。如果不跳过,我的问题毫无意义。
    • 1 + 1 在进行类型检查时会被 const 折叠。宏注释不会对它们的注释进行类型检查,因此 1 + 1 保持原样。如果您使用 def 宏,例如final val i = ensureConstant(1 + 1),然后你会看到Literal(Constant(2))
    • 你知道吗,看起来类型检查器可以对 OP 提供的代码进行 constfold 处理,所以 def 宏看起来对于手头的任务是可行的!
    【解决方案3】:

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

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

    这是使用 Scala 2.11.0-M8 语法编写的 def 宏代码。对于 2.11.0-M7,将导入替换为 import scala.reflect.macros.{BlackboxContext =&gt; 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-11
      • 2011-03-19
      • 2018-11-26
      • 2011-11-25
      • 2011-12-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多