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
)