【问题标题】:scala.meta parent of parent of Defn.ObjectDefn.Object 的父级的 scala.meta 父级
【发布时间】:2020-10-31 18:25:01
【问题描述】:

让它成为以下层次结构:

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

我解析包含X 的scala 文件和

我想知道它的父母或祖父母是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 的类,所以我没有看到一种简单且高效的方法来创建包含所有中间类的图形,以便以正确的顺序加载它们并检查它们的父级。

【问题讨论】:

    标签: scala scalameta


    【解决方案1】:

    您似乎希望Scalameta 处理您的来源not syntactically but semantically。然后你需要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(语义)

    https://github.com/DmytroMitin/scalafix-codegen(语义)

    https://github.com/DmytroMitin/scalameta-demo(句法)

    Is it possible to using macro to modify the generated code of structural-typing instance invocation?(语义)

    Scala conditional compilation(句法)

    Macro annotation to override toString of Scala function(句法)

    How to merge multiple imports in 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
    )
    

    【讨论】:

    • 感谢您提供快速详细的 Dmytro!我发现了一些关于 scalafix 的东西,我想我不需要它。我会试一试,然后告诉你。
    • @Chris 好吧,重要的是 SemanticDB,而不是 Scalafix。但是 Scalafix 提供了方便的 API 来处理符号、类型等(即不仅是树、令牌)。
    • @Chris 您可以避免使用 Scalafix,但您必须手动使用 SemanticDB。查看更新。
    • 感谢 Dmytro,我得到“无法解析符号信息和签名”。使用 Scala 2.11.12。我需要这个用于开发人员的实用程序,其中所有相关的依赖项只能在测试范围内使用,所以我尽量保持 build.sbt 干净
    • @Chris 无法解析符号信息和签名 尝试 Scalafix 0.9.16 和 Scala 2.11.12(或 2.13.2)。刚刚尝试过,它在两个版本中都有效。 (以防 sbt 1.3.8。)
    猜你喜欢
    • 2013-04-27
    • 1970-01-01
    • 1970-01-01
    • 2013-07-07
    • 2019-11-19
    • 1970-01-01
    • 2017-04-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多