Question

I want to write a macro where the return type depends on arguments. Simplified example:

def fun[T](methodName: String) = macro funImpl[T]

def funImpl[T: WeakTypeTag](c: Context)(methodName: c.Expr[String]): /* c.Expr[T => return type of T.methodName] */ = {
  // return x => x.methodName
}

Obviously the commented-out return type of funImpl is illegal. I've tried simply returning a Tree, but this produces an error:

[error] macro implementation has wrong shape:
[error]  required: (c: scala.reflect.macros.Context): c.Expr[Any]
[error]  found   : (context: scala.reflect.macros.Context): context.Tree
[error] type mismatch for return type: c.universe.Tree does not conform to c.Expr[Any]
[error]     def fun[T] = macro PrivateMethodMacro.funImpl[T]
[error]                                          ^

Is it possible to write a macro like this? Obviously it's possible if the return type is passed as another type argument, as in the answer to Is it possible to write a scala macro whose returntype depends on argument? but this isn't what I want.

Was it helpful?

Solution

Yes, this is possible, thanks to the magic of whitebox macros: you can just tell the compiler that the return type is c.Expr[Any] and it'll infer the more precise type.

This behavior shocked me when I first ran into it—it's very, very powerful and very, very scary—but it definitely is intended, and will continue to be supported, although 2.11 will make a distinction between whitebox and blackbox macros, and the former are likely to remain in experimental status longer (if they ever leave it at all).

For example, the following is a quick sketch of what you're asking for (I'm using quasiquotes here via the macro paradise plugin for 2.10, but it would only be a little more verbose without quasiquotes):

import scala.language.experimental.macros
import scala.reflect.macros.Context

def funImpl[T: c.WeakTypeTag](c: Context)(
  method: c.Expr[String]
): c.Expr[Any] = {
  import c.universe._

  val T = weakTypeOf[T]

  val methodName: TermName = method.tree match {
    case Literal(Constant(s: String)) => newTermName(s)
    case _ => c.abort(c.enclosingPosition, "Must provide a string literal.")
  }

  c.Expr(q"(t: $T) => t.$methodName")
}

def fun[T](method: String) = macro funImpl[T]

And then:

scala> fun[String]("length")
res0: String => Int = <function1>

You can see that the inferred type is exactly what you want, not Any. You could (and probably should) set the return type of funImpl to c.Expr[T => Any] and return something like c.Expr[T => Any](q"_.$methodName"), but that's essentially just documentation—it doesn't have any effect on how the return type of the macro is inferred in this case.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top