简体   繁体   English

有没有办法在编译时测试一个常量是编译时常量?

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

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 ...鉴于知道算术final val表达式是否将被编译为编译时常量是多么困难,以及意外破坏编译时性是多么容易......

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.幸运的是,宏被连接到类型检查中(在宏扩展之前对宏参数进行类型检查的意义上),并且类型检查折叠常量,所以看起来应该足以检查宏中的Literal(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.在宏天堂中实现的宏注释在注释者的类型检查之前扩展,这意味着它们的参数在扩展过程中不会被constfold,使得宏注释不太方便执行此任务。

Here's the code written with Scala 2.11.0-M8 syntax for def macros.这是使用 Scala 2.11.0-M8 语法为 def 宏编写的代码。 For 2.11.0-M7, replace the import with import scala.reflect.macros.{BlackboxContext => Context} .对于 2.11.0-M7,将导入替换为 import 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] .对于 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

I guess it's impossible even with macros:我想即使使用宏也是不可能的:

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

There is no difference between i , j and k here.这里ijk之间没有区别。

You'll get no information even with scalac -Xprint:cleanup test.scala :即使使用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;

You could get this information only from .icode file ( scalac -Xprint:all test.scala; cat Test\\$.icode ):您只能从.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)

}

Or from java bytecode ( javap -c Test\\$.class ):或者来自 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       

You phrased your question as being about determining whether a value is being in-line expanded at points of reference, but it seems though you're actually looking for a way to guarantee it.您将问题表述为关于确定值是否在参考点处内联扩展,但似乎您实际上正在寻找一种方法来保证它。 Is that correct?那是对的吗?

If you make it a def that's annotated for in-line expansion ( @inline ) you might get what you want.如果您将其设置为针对内联扩展 ( @inline ) 进行注释的def ,您可能会得到您想要的。

@inline def TwentyTwo = 22

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM