Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Macro return type depends on the arguments

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.

like image 858
Alexey Romanov Avatar asked Oct 21 '25 04:10

Alexey Romanov


1 Answers

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.

like image 152
Travis Brown Avatar answered Oct 23 '25 20:10

Travis Brown



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!