簡體   English   中英

有沒有辦法在編譯時測試一個常量是編譯時常量?

[英]Is there a way to test at compile-time that a constant is a compile-time constant?

鑒於知道算術final val表達式是否將被編譯為編譯時常量是多么困難,以及意外破壞編譯時性是多么容易......

任何人都可以想出一種簡單的方法來在編譯時驗證編譯器是否確實從復雜的算術表達式創建了編譯時常量? 我猜這可能是某種注釋或宏,但也許有更簡單的東西。 例如,也許是這樣的:

   @CompileTime final val HALF_INFINITY = Int.MaxValue / 2

將是可能的。

幸運的是,宏被連接到類型檢查中(在宏擴展之前對宏參數進行類型檢查的意義上),並且類型檢查折疊常量,所以看起來應該足以檢查宏中的Literal(Constant(_))以確保宏的參數是一個常量。

筆記。 在宏天堂中實現的宏注釋在注釋者的類型檢查之前擴展,這意味着它們的參數在擴展過程中不會被constfold,使得宏注釋不太方便執行此任務。

這是使用 Scala 2.11.0-M8 語法為 def 宏編寫的代碼。 對於 2.11.0-M7,將導入替換為 import import scala.reflect.macros.{BlackboxContext => Context} 對於 2.10.x,將 import 替換為 import 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

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

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       

您將問題表述為關於確定值是否在參考點處內聯擴展,但似乎您實際上正在尋找一種方法來保證它。 那是對的嗎?

如果您將其設置為針對內聯擴展 ( @inline ) 進行注釋的def ,您可能會得到您想要的。

@inline def TwentyTwo = 22

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM