DANG Fan
DANG Fan

Reputation: 864

Quasiquotes and Generics in Scala

I'm using Play Framework and trying to write an action that can parse protobuf request as following:

def AsyncProtoAction(block: ProtoRequestA => Future[Result]): Action[AnyContent] = {
    Action.async { request =>
        request.contentType match {
            case Some("application/protobuf") => request.body.asRaw match {
                case Some(raw) => raw.asBytes(1024 * 1024) match {
                    case Some(rawBytes) =>
                        val proto = ProtoRequestA.parseFrom(rawBytes)
                        block(proto)
                    case _ => ...
                }
            }
        }
    }
}

Here, ProtoRequestA is a generated object using ScalaPB. It works, however, I have a lot of protobuf request objects. Then I try to rewrite it using macro:

object Actions {
  def AsyncProtoAction[T](block: T => Future[Result]): 
        Action[AnyContent] = macro asyncProtoAction_impl[T]

  def asyncProtoAction_impl[T: c.WeakTypeTag](c: blackbox.Context)
       (block: c.Tree) = { import c.universe._
    val protoType = weakTypeOf[T]
    q"""import play.api.mvc._
        Action.async { request =>
          request.contentType match {
            case Some("application/protobuf") =>
              request.body.asRaw match {
                case Some(raw) =>
                  raw.asBytes(1024 * 1024) match {
                    case Some(rawBytes) =>
                      val proto = $protoType.parseFrom(rawBytes)
                      $block(proto)
                    case _ => ...
                  }
              }
          }
        }"""
  }
}

This doesn't work. The compilation error is value parseFrom is not a member of packageName.ProtoRequestA.

I tried showRaw(tq"$protoType") in REPL, which output String = TypeTree(). I guess the correct output should be String = Select(Ident(TermName("packageName")), TypeName("ProtoRequestA")). So what should I do?

Upvotes: 2

Views: 200

Answers (1)

Travis Brown
Travis Brown

Reputation: 139028

You've identified the problem, which is that when you interpolate the ProtoRequestA type you get something different from the ProtoRequestA companion object. One easy solution is to interpolate the symbol of the companion object, which you can get from the type's symbol. Here's a simplified example:

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

def companionObjectBarImpl[A: c.WeakTypeTag](c: Context): c.Tree = {
  import c.universe._
  q"${ symbolOf[A].companion }.bar"
}

def companionObjectBar[A]: Int = macro companionObjectBarImpl[A]

This macro will work on any type with a companion object with a bar method that returns an Int. For example:

scala> class Foo; object Foo { def bar = 10 }
defined class Foo
defined object Foo

scala> companionObjectBar[Foo]
res0: Int = 10

You should be able to do something similar.

Upvotes: 3

Related Questions