Using attachments with macros in Scala 2.10

2020-06-17 07:21发布

问题:

Update: I suspect that what I want might not be possible, and I've written up a blog post with my reasoning (and some alternatives) here. I'd be very happy to be told that I'm wrong.


Suppose I want to create a instance of a trait using a factory method with a macro implementation. This method will take as an argument a path to a resource that the macro will read and parse (at compile time) into a map from strings to strings.

That part is all fairly straightforward. Now suppose I want to associate the resulting map with the instance I'm creating so that I can use it in subsequent macro calls involving that instance.

I crucially do not want the map to be a member of the instance, or to exist in any form at runtime. I also don't want to parse the same resource more than once. Here's a sketch of the kind of thing I'm aiming for:

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

trait Foo {
  def lookup(key: String): String = macro Foo.lookup_impl
}

object Foo {
  def parseResource(path: String): Map[String, String] = ???

  def apply(path: String): Foo = macro apply_impl

  def apply_impl(c: Context)(path: c.Expr[String]): c.Expr[Foo] = {
    import c.universe._

    val m = path.tree match {
      case Literal(Constant(s: String)) => parseResource(s)
    }

    val tree = reify(new Foo {}).tree
    // Somehow associate the map with this tree here.
    c.Expr(tree)
  }

  def lookup_impl(c: Context)(key: c.Expr[String]): c.Expr[String] =
    /* Somehow read off the map and look up this key. */ ???
}

This seems to be the sort of thing that attachments are designed to help with (thanks to Eugene Burmako for the pointer), and I've got an attachment-based implementation that allows me to write the following:

Foo("whatever.txt").lookup("x")

Where apply_impl attaches the map to the tree and lookup_impl reads that attachment off the same tree, which it sees as its prefix. Unfortunately this is more or less useless, though, since it doesn't work in the foo.lookup("x") case, where the prefix tree is just the variable foo.

(Note that in my real use case Foo extends Dynamic, and I'm trying to give a macro implementation for selectDynamic instead of lookup, but that shouldn't be relevant here—I'm interested in the general case.)

Is there some way that I can use attachments to get what I want? Is there some other approach that would be more appropriate?

回答1:

UPDATE I should read the question again after fiddling... :( I just reproduced what you have.


You were quite there I think. This works for me:

object Foo {

  // Attachment.get somehow needs a wrapper class.
  // Probably because of generics
  implicit class LookupMap(m: Map[String, String]) {
    def get(s: String) = m.get(s) 
  }

  def parseResource(path: String): LookupMap = Map("test" -> "a")

  def apply_impl(c: Context)(path: c.Expr[String]): c.Expr[Foo] = {
    import c.universe._

    val m = path.tree match {
      case Literal(Constant(s: String)) => parseResource(s)
    }

    val tree = reify(new Foo {}).tree.updateAttachment(m)

    c.Expr(tree)
  }

  def lookup_impl(c: Context { type PrefixType = Foo })
                 (key: c.Expr[String]): c.Expr[String] = {
    import c.universe._

    val m = c.prefix.tree.attachments.get[LookupMap].get

    val s = key.tree match {
      case Literal(Constant(s: String)) => m.get(s).get
    }

    c.Expr(Literal(Constant(s)))

  }

}