Найти все возможные создания Sequences в дереве с помощью макросов Scala

Я хочу найти все возможные создания Sequences в дереве, используя макросы Scala.

val l = List(1, 2)
val v = Vector(1, 2)
val ab = ArrayBuffer(1, 2)
val s = Seq(1, 2)

но следующее совпадение не работает:

case Apply(TypeApply(Select(path, Name("apply")), _), args) if path.tpe <:< weakTypeOf[SeqFactory[Any]] => ...

Точно так же мне нужно найти все обращения по индексу в последовательности:

val v = Vector(1, 2)
val one = v(0)

или все вызовы метода «применить» к пути, который path.tpe ‹:‹ ????[Seq[_]

Как выписать этот чек? Это не компилируется:

case Apply(Select(path, Name("apply")), List(idx)) if path.tpe <:< typeOf[Seq[_]]

person nau    schedule 31.07.2013    source источник


Ответы (1)


Я не уверен, что именно вы пробовали, но на первый взгляд кажется, что вам не хватает как минимум двух частей: вам нужно использовать Traverser для обхода всех потомков дерева, и вам нужно проверить тип каждого дерева-кандидата, чтобы убедиться, что оно обезличено до точки что вы можете сказать, что у вас есть приложение SeqFactory.

Например, вот быстрая реализация, которая печатает все деревья создания последовательности в классе во время компиляции:

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

object SeqSearch {
  def printCreatesInClass = macro printCreatesInClass_impl

  def printCreatesInClass_impl(c: Context) = {
    import c.universe._
    import scala.collection.generic.SeqFactory

    val factorySym = c.typeOf[SeqFactory[Seq]].typeSymbol

    def isCreation(tree: Tree) = c.typeCheck(tree) match {
      case Apply(TypeApply(Select(factory, name), _), _) if
        factory.tpe.baseClasses.contains(factorySym) &&
        name == newTermName("apply") => true
      case _ => false
    }

    object printCreates extends Traverser {
      override def traverse(tree: Tree) = tree match {
        case application @ Apply(_, args) if isCreation(application) =>
          println("Matched create: " + application)
          super.traverseTrees(args)
        case _ => super.traverse(tree)
      }
    }

    printCreates(c.enclosingClass)

    c.literalUnit
  }
}

Это работает следующим образом:

scala> class Foo {
     |   SeqSearch.printCreatesInClass
     |   val l = List(1, 2)
     |   val v = Vector(1, 2)
     |   val ab = collection.mutable.ArrayBuffer(1, 2)
     |   val s = Seq(1, 2)
     | }
Matched create: List(1, 2)
Matched create: Vector(1, 2)
Matched create: collection.mutable.ArrayBuffer(1, 2)
Matched create: Seq(1, 2)
defined class Foo

Поиск доступа аналогичен — просто добавьте следующие методы к объекту выше:

  def printAccessesInClass = macro printAccessesInClass_impl

  def printAccessesInClass_impl(c: Context) = {
    import c.universe._

    def isAccess(tree: Tree) = c.typeCheck(tree) match {
      case Apply(Select(seq, name), _) if
        seq.tpe <:< typeOf[Seq[Any]] &&
        name == newTermName("apply") => true
      case _ => false
    }

    object printAccesses extends Traverser {
      override def traverse(tree: Tree) = tree match {
        case application @ Apply(_, args) if isAccess(application) =>
          println("Matched access: " + application)
          super.traverseTrees(args)
        case _ => super.traverse(tree)
      }
    }

    printAccesses(c.enclosingClass)

    c.literalUnit
  }

А потом:

scala> class Foo {
     |   SeqSearch.printCreatesInClass
     |   SeqSearch.printAccessesInClass
     |   val xs = List(1, 2, 3)
     |   val xh = xs(0)
     | }
Matched create: List(1, 2, 3)
Matched access: xs(0)
defined class Foo

Должно быть довольно легко настроить побочные эффекты Traverser для захвата любой интересующей вас информации вместо того, чтобы просто печатать деревья во время компиляции.

person Travis Brown    schedule 31.07.2013
comment
Спасибо, Трэвис, твой ответ действительно помог. Однако еще один вопрос. Не могли бы вы привести пример, когда потребуется c.typeCheck(factory).tpe.baseClasses.contains(factorySym). Спрашиваю, потому что у меня factory.tpe.baseClasses.contains(factorySym) работает нормально. - person nau; 31.07.2013
comment
Ах, верно. Нет необходимости дважды проверять тип — подойдет тот, который находится вне совпадения. Я отредактировал ответ, спасибо. - person Travis Brown; 31.07.2013