scala.meta родительский объект Defn.Object

Пусть это будет следующая иерархия:

object X extends Y{
...
}
trait Y extends Z {
...
}
trait Z {
  def run(): Unit
}

Я разбираю файл scala, содержащий X а также

Я хочу знать, является ли его родитель или дедушка или бабушка Z.

Я могу проверить родителя следующим образом: Учитывая, что x: Defn.Object это X класс, который я проанализировал,

x
.children.collect { case c: Template => c }
.flatMap(p => p.children.collectFirst { case c: Init => c }

дам Y.

Вопрос: Любая идея, как я могу получить родительский элемент родительского элементаX (который Z в приведенном выше примере)?

Загрузка Y (так же, как я загрузил X), и найти его родителя не кажется хорошей идеей, поскольку описанное выше является частью процедуры сканирования, когда среди всех файлов в src/main/scala Я пытаюсь найти все классы, которые расширяют Z и реализовать run, поэтому я не вижу простого и производительного способа создать граф со всеми промежуточными классами, чтобы загрузить их в правильном порядке и проверить их родителей.

1 ответ

Похоже, вы хотите, чтобы Scalameta обрабатывал ваши источники не синтаксически, а семантически. Тогда вам понадобится SemanticDB. Наверное, самый удобный способ работы с SemanticDB - это Scalafix.

правила /src/main/scala/MyRule.scala

import scalafix.v1._
import scala.meta._

class MyRule extends SemanticRule("MyRule") {
  override def isRewrite: Boolean = true
  override def description: String = "My Rule"

  override def fix(implicit doc: SemanticDocument): Patch = {
    doc.tree.traverse {
      case q"""..$mods object $ename extends ${template"""
        { ..$stats } with ..$inits { $self => ..$stats1 }"""}""" =>
        val initsParents = inits.collect(_.symbol.info.map(_.signature) match {
          case Some(ClassSignature(_, parents, _, _)) => parents
        }).flatten
        println(s"object: $ename, parents: $inits, grand-parents: $initsParents")
    }

    Patch.empty
  }
}

в /src/main/scala/App.scala

object X extends Y{
  override def run(): Unit = ???
}

trait Y extends Z {
}

trait Z {
  def run(): Unit
}

Выход sbt out/compile

object: X, parents: List(Y), grand-parents: List(AnyRef, Z)

build.sbt

name := "scalafix-codegen"

inThisBuild(
  List(
    //scalaVersion := "2.13.2",
    scalaVersion := "2.11.12",
    addCompilerPlugin(scalafixSemanticdb),
    scalacOptions ++= List(
      "-Yrangepos"
    )
  )
)

lazy val rules = project
  .settings(
    libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % "0.9.16",
    organization := "com.example",
    version := "0.1",
  )

lazy val in = project

lazy val out = project
  .settings(    
    sourceGenerators.in(Compile) += Def.taskDyn {
      val root = baseDirectory.in(ThisBuild).value.toURI.toString
      val from = sourceDirectory.in(in, Compile).value
      val to = sourceManaged.in(Compile).value
      val outFrom = from.toURI.toString.stripSuffix("/").stripPrefix(root)
      val outTo = to.toURI.toString.stripSuffix("/").stripPrefix(root)
      Def.task {
        scalafix
          .in(in, Compile)
          .toTask(s" --rules=file:rules/src/main/scala/MyRule.scala --out-from=$outFrom --out-to=$outTo")
          .value
        (to ** "*.scala").get
      }
    }.taskValue
  )

проект / plugins.sbt

addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.16")

Другие примеры:

https://github.com/olafurpg/scalafix-codegen (семантический)

https://github.com/DmytroMitin/scalafix-codegen (семантический)

https://github.com/DmytroMitin/scalameta-demo (синтаксис)

Можно ли с помощью макроса изменить сгенерированный код вызова экземпляра структурной типизации?(семантический)

Условная компиляция Scala (синтаксическая)

Аннотации макроса для переопределения toString функции Scala (синтаксис)

Как объединить несколько операций импорта в scala?(синтаксический)


Вы можете избежать Scalafix, но тогда вам придется работать с внутренними компонентами SemanticDB вручную.

import scala.meta._
import scala.meta.interactive.InteractiveSemanticdb
import scala.meta.internal.semanticdb.{ClassSignature, Range, SymbolInformation, SymbolOccurrence, TypeRef}

val source: String =
  """object X extends Y{
    |  override def run(): Unit = ???
    |}
    |
    |trait Y extends Z
    |
    |trait Z {
    |  def run(): Unit
    |}""".stripMargin

val textDocument = InteractiveSemanticdb.toTextDocument(
  InteractiveSemanticdb.newCompiler(List(
    "-Yrangepos"
  )),
  source
)

implicit class TreeOps(tree: Tree) {
  val occurence: Option[SymbolOccurrence] = {
    val treeRange = Range(tree.pos.startLine, tree.pos.startColumn, tree.pos.endLine, tree.pos.endColumn)
    textDocument.occurrences
      .find(_.range.exists(occurrenceRange => treeRange == occurrenceRange))
  }

  val info: Option[SymbolInformation] = occurence.flatMap(_.symbol.info)
}

implicit class StringOps(symbol: String) {
  val info: Option[SymbolInformation] = textDocument.symbols.find(_.symbol == symbol)
}

source.parse[Source].get.traverse {
  case tree@q"""..$mods object $ename extends ${template"""
    { ..$stats } with ..$inits { $self => ..$stats1 }"""}""" =>
    val initsParents = inits.collect(_.info.map(_.signature) match {
      case Some(ClassSignature(_, parents, _, _)) =>
        parents.collect {
          case TypeRef(_, symbol, _) => symbol
        }
    }).flatten
    println(s"object = $ename = ${ename.info.map(_.symbol)}, parents = $inits = ${inits.map(_.info.map(_.symbol))}, grand-parents = $initsParents")
}

Выход:

object = X = Some(_empty_/X.), parents = List(Y) = List(Some(_empty_/Y#)), grand-parents = List(scala/AnyRef#, _empty_/Z#)

build.sbt

//scalaVersion := "2.13.3"
scalaVersion := "2.11.12"

lazy val scalametaV = "4.3.18"
libraryDependencies ++= Seq(
  "org.scalameta" %% "scalameta" % scalametaV,
  "org.scalameta" % "semanticdb-scalac" % scalametaV cross CrossVersion.full
)
Другие вопросы по тегам