是否可以使用宏来修改结构类型实例调用的生成代码?

2023-12-26

例如如下代码:

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 宏,建议和代码示例表示赞赏。谢谢!


宏(更准确地说,宏注释 https://docs.scala-lang.org/overviews/macros/annotations.html因为定义宏 https://docs.scala-lang.org/overviews/macros/overview.html与此任务无关)还不够。您想要重写的不是类(特征、对象)或其参数或成员,而是本地表达式。你可以这样做编译器插件 https://docs.scala-lang.org/overviews/plugins/index.html (see also https://dotty.epfl.ch/docs/reference/changed-features/compiler-plugins.html) 在编译时或使用斯卡拉梅塔 https://scalameta.org/编译前生成代码。

如果你选择 Scalameta 那么实际上你想在语义上而不是在语法上重写你的表达式,因为你想从本地表达式开始new Class...到定义trait Class...并检查那里是否有合适的成员。所以你需要 Scalameta +语义数据库 https://scalameta.org/docs/semanticdb/guide.html。更方便的是使用Scalameta + SemanticDBScalafix https://scalacenter.github.io/scalafix/docs/developers/setup.html(另请参阅部分对于用户 https://scalacenter.github.io/scalafix/docs/users/installation.html).

您可以创建自己的重写规则。然后您可以使用它就地重写代码或生成代码(见下文)。

规则/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_management/main/scala/Test.scala (after 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/olafurpg/scalafix-codegen

https://github.com/DmytroMitin/scalafix-codegen https://github.com/DmytroMitin/scalafix-codegen

https://github.com/DmytroMitin/scalameta-demo https://github.com/DmytroMitin/scalameta-demo

Scala 条件编译 https://stackoverflow.com/questions/61796570/scala-conditional-compilation

覆盖Scala函数toString的宏注释 https://stackoverflow.com/questions/35338239/macro-annotation-to-override-tostring-of-scala-function/

如何在scala中合并多个导入? https://stackoverflow.com/questions/58562229/how-to-merge-multiple-imports-in-scala/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

是否可以使用宏来修改结构类型实例调用的生成代码? 的相关文章

随机推荐

  • Twilio 在打电话时说动词

    基于这个问题 https stackoverflow com questions 6863305 twilio say verb nested into a dial verb 它非常接近我想要的 当双方建立连接时 我们尝试在通话期间触发
  • 从 Apache Velocity 模板访问常量值?

    是否可以从 Velocity 模板访问常量值 即 Java 类中定义的公共静态最终变量 我希望能够写出这样的东西 if a lt Long MAX VALUE 但这显然不是正确的语法 有很多种方法 1 您可以将值直接放入上下文中 2 您可以
  • 如何优化 MySQL 更新查询?

    我有一个包含 300 000 条记录的表 在此表中有重复的行 我想更新列 flag TABLE number flag more column ABCD 0 ABCD 0 ABCD 0 BCDE 0 BCDE 0 我使用此查询
  • 限制 Django 模型中的递归外键深度

    我有一个 Django 模型 其中 self 的外键可为空 我有兴趣对递归深度引入硬限制 例如 10 哪里是检查这个的正确位置以及我应该在那里抛出什么样的异常 我的意思的伪示例 def limit recursion depth self
  • Apache mod_rewrite 未将表达式映射到参数

    我有一个网站使用apache mod rewrite并且在过去 6 个月里一直工作 没有出现任何错误 我有以下重写规则 RewriteRule products a z products php category 1 NC L 这是我页面中
  • WooCommerce:将自定义 Metabox 添加到管理订单页面

    我目前已成功向我的 WooCommerce 产品页面添加一个字段 该字段显示值 在购物车 前端 中 在结账页面 前端 在订单页面 前端 并在管理个人订单页面 后端 中 问题 它没有在管理订单 自定义字段 Metabox 中显示为自定义字段及
  • 如何在无限行模型中设置初始起始页?

    我使用的是带有无限行模型和分页功能的 Ag Grid 版本 20 0 0 我希望能够设置第一次加载的初始页面 但我没有看到任何允许我自定义它的属性 他们只提供方法paginationGoToPage更改页面 但此方法要求首先加载网格 这迫使
  • 如何在sql server management studio 2012中创建复合外键

    我可以通过选择两列 OrderId CompanyId 并右键单击并设置为主键 在 sql server management studio 2012 中成功创建复合主键 但我不知道如何使用 sql server management st
  • Firebase 测试实验室对 Appium 的支持

    有谁知道 Firebase 测试实验室是否支持 Appium 测试框架 至少在路线图中吗 由于 Firebase 由 Google 提供支持 因此您很难指望它会优先支持开源社区驱动的框架 而 Appium 正是如此 目前 Firebase
  • 共享指针如何工作?

    共享指针如何知道有多少指针指向该对象 在本例中为shared ptr 基本上 shared ptr有两个指针 一个指向共享对象的指针和一个指向包含两个引用计数的结构体的指针 一个用于 强引用 即具有所有权的引用 另一个用于 弱引用 即不具有
  • Google Play Install Referrer API 与 INSTALL_REFERRER 广播

    最近Google发布了Google Play Install Referrer API 公告 https android developers googleblog com 2017 11 google play referrer api
  • 类库项目文件未编译为 .dll 或调试

    在我的解决方案中 我有一个编译成 dll 的类库项目 我有一个网络项目 我有多个具有不同网络项目但具有相同类库的解决方案 类项目中的文件之一 utilities cs 突然不会编译到 dll 中 我对此文件进行了更改 但更改不会显示在网站上
  • 在 Swift 3 中获取 GCD 标签

    我有一些代码可以获取当前 GCD 队列的标签以用于日志记录 在 Swift 2 中如下所示 if let queueName String UTF8String dispatch queue get label DISPATCH CURRE
  • 调用对象数组时出现空引用异常[重复]

    这个问题在这里已经有答案了 我早些时候试图获得一些帮助 但我认为我没有提供足够的信息 尽管我很感谢所有的建议 目标只是将对象室的新实例添加到数组中并打印到列表框 当用户尝试输入已经存在的房间名称时 它应该简单地显示在数组中已经存在的房间的规
  • JQuery Datepicker - 仅选择星期一和星期四

    我有以下代码 仅允许用户从 jquery datepicker 选择星期一 我想对此进行调整 以便能够选择星期一和星期四 有任何想法吗 beforeShowDay function date return date getDay 1 您可以
  • 如何在引导列之间添加边距而不换行[重复]

    这个问题在这里已经有答案了 我的布局目前看起来像这样 在中心列中 我想在每个之间添加一个小边距Server分区但是 如果我向 CSS 添加边距 它最终会换行并看起来像这样 div class row info panel div class
  • 如何在不运行两个单独的进程调用的情况下获取 DBI 中的行计数?

    我正在 Perl 中运行 DBI 但无法弄清楚当我运行准备好的语句时如何确定返回的行计数是否为 0 我意识到我可以在 while 循环中设置一个计数器来获取行 但我希望有一种不那么丑陋的方法来做到这一点 基于快速浏览here http ww
  • 网站阻止 Selenium:有办法绕过吗?

    该网页手动打开正常 但使用 Selenium 时直接出现 维护 错误消息 from selenium import webdriver driver webdriver Chrome executable path chromedriver
  • Godot 监听来自同一场景的多个实例的信号

    我有以下场景 玩家 敌人 攻击 当攻击与敌人发生碰撞时 敌人会发出 onHit 信号 播放器监听该信号并反弹 这一切都运行良好 但现在如果我复制敌人 因此有多个敌人场景 我如何收听所有敌人的信号 有没有办法获取场景的所有实例并连接到它们的所
  • 是否可以使用宏来修改结构类型实例调用的生成代码?

    例如如下代码 object Test extends App trait Class val f1 Int val c new Class val f1 Int 1 val f2 String Class println c f1 prin