Let's suppose I've defined a macro as below. It essentially types an expression of type T and returns an object of type MyType[T] (the actual types involved don't really matter).
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'd like to determine if the expression passed is constant or not. By that I mean, I want to know if the expression, once evaluated at runtime, could ever subsequently evaluate to a different value. If it is constant, I can apply certain optimizations that are very useful.
I know an expression is constant if it is:
For example, the expressions passed in the first five calls to callMacro below should be considered 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 code for the first two cases (which is rather 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 whether something like this already exists, or if its even possible to detect whether the member being called on an object is a val or not.
Is it possible to implement the fourth criteria? Or, does anything like this already exist in the API?
I figured out a solution. It basically boiled down to me not knowing about Symbols in scale's reflection system.
I ended up adding a fifth criteria to handle the case in which 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
  }
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With