0

例如如下代码:

object Test extends App
{
    trait Class
    {
        val f1: Int
    }

    val c = new Class {
        val f1: Int = 1
        val f2: String = "Class"
    }

    println(c.f1)
    println(c.f2)
}

我用反编译器查看字节码,并注意到编译生成一个 java 接口“Test.Class”作为伪代码:

trait Class
{
    val f1: Int
}

和一个实现'Test.Class'的类'Test$$anon$1',伪代码为:

class Test$$anon$1 extends Class
{
    val f1: Int = 1
    val f2: String = "Class"
}

然后编译器将变量“c”初始化为:

c = new Test$$anon$1()

然后调用成员'f1'作为正常调用:

println(c.f1)

但它使用反射调用'f2':

println(reflMethod(c, f2))

在这里,由于匿名类'Test$$anon$1'的定义在同一范围内可见,是否可以使用宏更改生成的代码以调用'f2'作为普通字段避免反射?

我只想更改同一范围内的调用代码,不想跨范围更改反射代码,例如将结构类型实例作为函数调用中的参数。所以我觉得理论上是可以的。但我不熟悉 scala 宏,建议和代码示例表示赞赏。谢谢!

4

1 回答 1

1

宏(更准确地说,宏注释,因为def 宏与此任务无关)是不够的。您想重写的不是类(特征,对象)或其参数或成员,而是局部表达式。您可以在编译时使用编译器插件(另请参阅或在编译时使用Scalameta代码生成来执行此操作。

如果您选择 Scalameta,那么实际上您想在语义上而不是在语法上重写您的表达式,因为您想从本地表达式new Class...转到定义trait Class...并检查那里是否有适当的成员。所以你需要 Scalameta + SemanticDB。更方便的是使用 Scalameta + 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.collect {
      case tree @ q"new { ..$stats } with ..$inits { $self => ..$stats1 }" =>
        val symbols = stats1.collect {
          case q"..$mods val ..${List(p"$name")}: $tpeopt = $expr" =>
            name.syntax
        }

        val symbols1 = inits.headOption.flatMap(_.symbol.info).flatMap(_.signature match {
          case ClassSignature(type_parameters, parents, self, declarations) =>
            Some(declarations.map(_.symbol.displayName))
          case _ => None
        })

        symbols1 match {
          case None => Patch.empty
          case Some(symbols1) if symbols.forall(symbols1.contains) => Patch.empty
          case _ =>
            val anon = Type.fresh("anon$meta$")
            val tree1 =
              q"""
                class $anon extends ${template"{ ..$stats } with ..$inits { $self => ..$stats1 }"}
                new ${init"$anon()"}
              """
            Patch.replaceTree(tree, tree1.syntax)
        }
    }.asPatch
  }
}

在/src/main/scala/Test.scala

object Test extends App
{
  trait Class
  {
    val f1: Int
  }

  val c = new Class {
    val f1: Int = 1
    val f2: String = "Class"
  }

  println(c.f1)
  println(c.f2)
}

out/target/scala-2.13/src_managed/main/scala/Test.scala(之后sbt out/compile

object Test extends App
{
  trait Class
  {
    val f1: Int
  }

  val c = {
  class anon$meta$2 extends Class {
    val f1: Int = 1
    val f2: String = "Class"
  }
  new anon$meta$2()
}

  println(c.f1)
  println(c.f2)
}

构建.sbt

name := "scalafix-codegen-demo"

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

lazy val rules = project
  .settings(
    libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % "0.9.16"
  )

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" ProcedureSyntax --out-from=$outFrom --out-to=$outTo")
          .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中合并多个导入?

于 2020-06-08T14:13:27.497 回答