Search code examples
scalascala-quasiquotes

Variable length "select"s with quasiquotes


With Scala's quasiquotes you can build trees of selects easily, like so:

> tq"a.b.MyObj"
res: Select(Select(Ident(TermName("a")), TermName("b")), TermName("MyObj"))

My question is, how do I do this if the list of things to select from (a,b,...,etc) is variable length (and therefore in a variable that needs to be spliced in)?

I was hoping lifting would work (e.g. tq"""..${List("a","b","MyObj")}""" but it doesn't. Or maybe even this tq"""${List("a","b","MyObj").mkString(".")}""", but no luck.

Is there a way to support this with quasiquotes? Or do I just need to construct the tree of selects manually in this case?


Solution

  • I don't think there is a way to do this outright with quasiquotes. I'm definitely sure that anything like tq"""${List("a","b","MyObj").mkString(".")}""" will not work. My understanding of quasiquotes is that they are just sugar for extractors and apply.

    However, building on that idea, we can define a custom extractor to do what you want. (By the way, I'm sure there is a nicer way to express this, but you get the idea...)

    object SelectTermList {
      def apply(arg0: String, args: List[String]): universe.Tree =
        args.foldLeft(Ident(TermName(arg0)).asInstanceOf[universe.Tree])
                     ((s,arg) => Select(s, TermName(arg)))
    
      def unapply(t: universe.Tree): Option[(String,List[String])] = t match {
        case Ident(TermName(arg0)) => Some((arg0, List()))
        case Select(SelectTermList(arg0,args),TermName(arg)) =>
          Some((arg0, args ++ List(arg)))
        case _ => None
      }
    }
    

    Then, you can use this to both construct and extract expressions of the form a.b.MyObj.

    Extractor tests:

    scala> val SelectTermList(obj0,selectors0) = q"a.b.c.d.e.f.g.h"
    obj0: String = a
    selectors0: List[String] = List(b, c, d, e, f, g, h)
    
    scala> val q"someObject.method(${SelectTermList(obj1,selectors1)})" = q"someObject.method(a.b.MyObj)"
    obj1: String = a
    selectors1: List[String] = List(b, MyObj)
    

    Corresponding apply tests:

    scala> SelectTermList(obj0,selectors0)
    res: universe.Tree = a.b.c.d.e.f.g.h
    
    scala> q"someObject.method(${SelectTermList(obj1,selectors1)})"
    res: universe.Tree = someObject.method(a.b.MyObj)
    

    As you can see, there is no problem with nesting the extractors deep inside quasi quotes, both when constructing and extracting.