How do you determine if an expression passed to a macro will always have the same value?

Suppose I have defined a macro as shown below. It essentially names an expression of type T and returns an object of type MyType [T] (the actual types are irrelevant).

object MyMacro {
  def macroImpl[T : context.WeakTypeTag, U : context.WeakTypeTag](context : scala.reflect.macros.blackbox.Context) (expression : context.Expr[T]) : context.Expr[U] =
}

object MyObj {
  def callMacro[T](expression : T) : MyType[T] = macro MyMacro.macroImpl[T, MyType[T]]
}

      

In my macro, I would like to determine if the passed expression is constant or not. By this I mean that I want to know if an expression, ever evaluated at runtime, can subsequently evaluate to a different value. If it is persistent, I can apply certain optimizations that are very useful.

I know an expression is constant if it is:

  • literal expression.
  • a 'this' expression.
  • reference to val or parameter.
  • element invocation where object expression is constant and element invoked is val or lazy val.

For example, expressions passed in the first five calls to callMacro below should be treated as a constant:

class MyClass {
  val i = 0

  val myLiteral = callMacro("Hi!") //constant - literal expression

  val myThis = callMacro(this) //constant - this expression

  val myInt = callMacro(i) //constant - reference to a val

  def myMethod(p : MyOtherClass) {
    val myParameter = callMacro(p) //constant - reference to a parameter
    val myValMember = callMacro(p.x) //constant - invocation of val member
    val myVarMember = vallMacro(p.y) //NOT constant - invocation of var member
    val myVarMember = vallMacro(p.z) //NOT constant - invocation of def member
  }
}

class MyOtherClass(val x : Int, var y : Int) {
  def z = x + y
}

      

I've already implemented the code for the first two cases (which is pretty trivial).

def isConstant[T](context : scala.reflect.macros.blackbox.Context) (expression : context.Expr[T]) = {
  import context.universe._
    expression.tree match {
      case This(_) =>
        true
      case Literal(_) =>
        true
      /*...put additional cases here...*/
      case _ =>
        false
  }
}

      

However, I'm not sure if something like this exists, or even it can be detected if the element called by the object is val or not.

Is it possible to implement the fourth criterion? Or, does something like this already exist in the API?

+3


source to share


1 answer


I figured out the solution. It basically came down to me not knowing about the symbols in the scale reflection system.

I ended up adding a fifth criterion to handle the case where an implicit parameter or object is referenced.



implicit class extendSymbol(symbol : scala.reflect.macros.blackbox.Context#Symbol) {
  def isStable =
    (symbol.isTerm && symbol.asTerm.isStable) || (symbol.isMethod && symbol.asMethod.isStable)
}

def isConstant[T](context : scala.reflect.macros.blackbox.Context) (tree : context.Tree) : Boolean = {
  import context.universe._
  tree match {
    case This(_) =>
      true
    case Literal(_) =>
      true
    case ident @ Ident(_) =>
      ident.symbol.isStable
    case select @ Select(objExpr, term) =>
      isConstant(context) (objExpr) && select.symbol.isStable
    //for implicit values
    case Apply(TypeApply(Select(Select(This(TypeName("scala")), TermName("Predef")), TermName("implicitly")), _), _) =>
      true
    case _ =>
      false
  }
}

      

+1


source







All Articles