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

后端 未结 3 701
礼貌的吻别
礼貌的吻别 2020-12-19 02:24

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

相关标签:
3条回答
  • 2020-12-19 02:47

    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
    
    0 讨论(0)
  • 2020-12-19 02:48

    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
    
    0 讨论(0)
  • 2020-12-19 02:52

    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       
    
    0 讨论(0)
提交回复
热议问题