1

Let it be the following hierarchy:

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

I parse the scala file containing the X and

I want to know if its parent or grandparent is Z.

I can check for parent as follows: Given that x: Defn.Object is the X class I parsed,

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

will give Y.

Question: Any idea how I can get the parent of the parent of X (which is Z in the above example) ?

Loading Y (the same way I loaded X) and finding it's parent doesn't seem like a good idea, since the above is part of a scan procedure where among all files under src/main/scala I'm trying to find all classes which extend Z and implement run, so I don't see an easy and performant way to create a graph with all intermediate classes so as to load them in the right order and check for their parents.

4

1 回答 1

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)

构建.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条件编译(句法)

覆盖 Scala 函数的 toString 的宏注释(语法)

如何在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#)

构建.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
)
于 2020-07-11T01:46:54.900 回答