NietzscheanAI
NietzscheanAI

Reputation: 966

Compilation issue when accessing parameter value in Scala macro

The calling code for this minimal example appears to compile (Eclipse Indigo SR2, Scala v2.10.20), but the project containing the calling code is marked with a red cross (not clear to me how to get further diagnostics for this). No class files are generated. If I replace param.value with some literal e.g. 1, the calling code compiles.

Is this a known problem? Is there a workaround?

def myMacro( param : Int ): Int = macro myMacroImpl( param )

def myMacroImpl(c: Context)(param: c.Expr[Int]): c.Expr[Int] = {
    import c.universe._

    c.Expr( Literal( Constant( param.value ) ) )         
}

Upvotes: 1

Views: 294

Answers (1)

Travis Brown
Travis Brown

Reputation: 139028

First of all, the syntax of macro definitions doesn't require (or allow) you to specify the parameters to the macro implementation method, so the code you have should never compile, even with a literal in the implementation. See my examples below for the correct syntax.

Next, Expr.value is unfortunately a lie. Think about the case where the argument to the macro method is a variable. The value of the variable isn't (and in the general case couldn't be) known at compile-time, and yet you're trying to make a compile-time literal constant with that value. It's just fundamentally not going to work.

You have a couple of options, which may or may not be applicable depending on what you're trying to do. Suppose we need to add one to the param value and return the result, for example. If we wanted the addition to happen at compile-time, we'd have to throw a (compile-time) exception when we don't get a compile-time literal:

def myMacro(param: Int): Int = macro myMacroImpl

def myMacroImpl(c: Context)(param: c.Expr[Int]): c.Expr[Int] = {
  import c.universe._

  val p = param.tree match {
    case Literal(Constant(p: Int)) => p
    case _ => c.abort(c.enclosingPosition, "param must be a literal value!")
  }

  c.literal(p + 1) // Or, equivalently: c.Expr(Literal(Constant(p + 1))
}

Another option would be to build a tree around the param expression. For example, the following also returns the successor to param, but the addition is performed at runtime, and the method can handle non-literal arguments:

def myMacro(param: Int): Int = macro myMacroImpl

def myMacroImpl(c: Context)(param: c.Expr[Int]): c.Expr[Int] = {
  import c.universe._

  c.Expr(
    Apply(Select(param.tree, newTermName("$plus")), c.literal(1).tree :: Nil)
  )
}

We're just not going to get both compile-time addition and an arbitrary param expression, though.

Upvotes: 4

Related Questions