1
votes

As part of a macro, I want to manipulate the case definitions of a partial function.

To do so, I use a Transformer to manipulate the case definitions of the partial function and a Traverser to inspect the patterns of the case definitions:

def myMatchImpl[A: c.WeakTypeTag, B: c.WeakTypeTag](c: Context)
    (expr: c.Expr[A])(patterns: c.Expr[PartialFunction[A, B]]): c.Expr[B] = {
  import c.universe._

  val transformer = new Transformer {
    override def transformCaseDefs(trees: List[CaseDef]) = trees map {
      case caseDef @ CaseDef(pattern, guard , body) => {
        // println(show(pattern))
        val traverser = new Traverser {
          override def traverse(tree: Tree) = tree match {
            // match against a specific pattern
          }
        }
        traverser.traverse(pattern)
      }
    }
  }

  val transformedPartialFunction = transformer.transform(patterns.tree)

  c.Expr[B](q"$transformedPartialFunction($expr)")
}

Now let us assume, the interesting data I want to match against is represented by the class Data (which is part of the object Example):

case class Data(x: Int, y: String)

When now invoking the macro on the example below

abstract class Foo
case class Bar(data: Data) extends Foo
case class Baz(string: String, data: Data) extends Foo

def test(foo: Foo) = myMatch(foo){
  case Bar(Data(x,y))    => y
  case Baz(_, Data(x,y)) => y
}    

the patterns of the case definitions of the partial function are transformed by the compiler as following (the Foo, Bar, and Baz classes are members of the object Example, too):

(data: Example.Data)Example.Bar((x: Int, y: String)Example.Data((x @ _), (y @ _)))
(string: String, data: Example.Data)Example.Baz(_, (x: Int, y: String)Example.Data((x @ _), (y @ _)))

This is the result of printing the patterns as hinted in the macro above (using show), the raw abstract syntax trees (printed using showRaw) look like this:

Apply(TypeTree().setOriginal(Select(This(newTypeName("Example")), Example.Bar)), List(Apply(TypeTree().setOriginal(Select(This(newTypeName("Example")), Example.Data)), List(Bind(newTermName("x"), Ident(nme.WILDCARD)), Bind(newTermName("y"), Ident(nme.WILDCARD))))))

Apply(TypeTree().setOriginal(Select(This(newTypeName("Example")), Example.Baz)), List(Ident(nme.WILDCARD), Apply(TypeTree().setOriginal(Select(This(newTypeName("Example")), Example.Data)), List(Bind(newTermName("x"), Ident(nme.WILDCARD)), Bind(newTermName("y"), Ident(nme.WILDCARD))))))

How do I write a pattern-quote which matches against these trees?

2
Try printing the trees with showRaw, it will show you the exact structure of the tree (not just code-like representation).ghik
@ghik I added the output of shawRaw to the question. Unfortunately, this doesn't help much either, since I don't know how to write a pattern for such a tree. I updated the question accordingly.Martin Zuber

2 Answers

2
votes

First of all, there is a special flavor of quasiquotes specifically for CaseDefs called cq:

override def transformCaseDefs(trees: List[CaseDef]) = trees map {
  case caseDef @ cq"$pattern if $guard => $body" => ...
}

Secondly, you should use pq to deconstruct patterns:

pattern match {
   case pq"$name @ $nested" => ...
   case pq"$extractor($arg1, $arg2: _*)" => ...
   ...
}

If you are interested in internals of trees that are used for pattern matching they are created by patvarTransformer defined in TreeBuilder.scala

On the other hand if you're are working with UnApply trees (that are being produced after typechecking) I have bad news for you: quasiquotes currently don't support them. Follow SI-7789 to get notified when this is fixed.

0
votes

After Den Shabalin pointed out, that quasiquotes can't be used in this particular setting, I managed to find a pattern which matches against the patterns of a partial function's case definitions.

The key problem is, that the constructor we want to match against (in our example Data) is stored in the TypeTree of the Apply node. Matching against a tree wrapped up in a TypeTree is a bit tricky, since the only extractor of this class (TypeTree()) isn't very helpful for this particular task. Instead we have to select the wrapped up tree using the original method:

override def transform(tree: Tree) = tree match {
  case Apply(constructor @ TypeTree(), args) => constructor.original match {
    case Select(_, sym) if (sym == newTermName("Data")) => ...
  }
}

In our use case the wrapped up tree is a Select node and we can now check if the symbol of this node is the one we are looking for.