質問

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.

役に立ちましたか?

解決

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.

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.

Here's the code written with Scala 2.11.0-M8 syntax for def macros. For 2.11.0-M7, replace the import with 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].

// 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.

You'll get no information even with 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):

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

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 TwentyTwo = 22
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top