Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine if an expression passed to a macro will always result in the same value?

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:

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

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?

like image 453
Nimrand Avatar asked Oct 17 '25 11:10

Nimrand


1 Answers

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
  }
}
like image 94
Nimrand Avatar answered Oct 19 '25 09:10

Nimrand