scala .meta родительского элемента родительского объекта Defn.Object - PullRequest
0 голосов
/ 11 июля 2020

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

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 Ответ

0 голосов
/ 11 июля 2020

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

rules / 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
  )

project / plugins.sbt

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

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

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

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

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

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

Scala условная компиляция (синтаксис c)

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

Как для объединения нескольких импортов в scala? (syntacti c)

Вы можете избежать 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
)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...