是否可以使用宏来修改结构类型实例调用的生成代码? [英] Is it possible to using macro to modify the generated code of structural-typing instance invocation?

查看:38
本文介绍了是否可以使用宏来修改结构类型实例调用的生成代码?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

例如如下代码:

object Test 扩展应用{特质类{val f1: 整数}val c = 新类{val f1: Int = 1val f2: String = "Class"}打印(c.f1)打印(c.f2)}

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

trait 类{val f1: 整数}

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

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

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

c = new Test$$anon$1()

然后像正常调用一样调用成员f1":

println(c.f1)

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

println(reflMethod(c, f2))

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

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

解决方案

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

如果您选择 Scalameta,那么实际上您想在语义上而不是在语法上重写您的表达式,因为您想从本地表达式 new Class... 转到定义 trait Class... 并检查那里是否有适当的成员.所以你需要 Scalameta + SemanticDB.更方便的是将 Scalameta + SemanticDB 与 Scalafix 一起使用(另见部分用户).

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

rules/src/main/scala/MyRule.scala

import scalafix.v1._导入 scala.meta._class MyRule extends SemanticRule("MyRule") {覆盖 def isRewrite: Boolean = true覆盖定义描述:String = "My Rule"覆盖 def 修复(隐式文档:SemanticDocument):补丁 = {doc.tree.collect {case tree @ q"new { ..$stats } with ..$inits { $self => ..$stats1 }" =>val 符号 = stats1.collect {case q"..$mods val ..${List(p"$name")}: $tpeopt = $expr" =>名称.语法}val symbols1 = inits.headOption.flatMap(_.symbol.info).flatMap(_.signature match {case ClassSignature(type_parameters, parents, self, Declarations) =>一些(声明.map(_.symbol.displayName))案例_ =>没有任何})符号 1 匹配 {情况无=>补丁.空case Some(symbols1) if symbols.forall(symbols1.contains) =>补丁.空案例_ =>val anon = Type.fresh("anon$meta$")瓦尔树1 =q"""class $anon 使用 ..$inits { $self => ..$stats1 }"} 扩展 ${template"{ ..$stats }新 ${init"$anon()"}"""Patch.replaceTree(tree, tree1.syntax)}}.asPatch}}

在/src/main/scala/Test.scala

object Test 扩展应用{特质类{val f1: 整数}val c = 新类{val f1: 整数 = 1val f2: String = "Class"}打印(c.f1)打印(c.f2)}

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

object Test 扩展应用{特质类{val f1: 整数}val c = {类 anon$meta$2 扩展类 {val f1: Int = 1val f2: String = "Class"}新的匿名 $meta$2()}打印(c.f1)打印(c.f2)}

build.sbt

name := "scalafix-codegen-demo"在这个构建(列表(ScalaVersion := "2.13.2",添加编译器插件(scalafixSemanticdb),scalacOptions ++= 列表(-Yrangepos")))懒惰的 val 规则 = 项目.设置(libraryDependencies += "ch.epfl.scala" %% "scalafix-core" % "0.9.16")懒惰的 val = 项目懒惰的 val out = 项目.设置(sourceGenerators.in(Compile) += Def.taskDyn {val root = baseDirectory.in(ThisBuild).value.toURI.toStringval from = sourceDirectory.in(in, Compile).valueval to = sourceManaged.in(Compile).valueval outFrom = from.toURI.toString.stripSuffix("/").stripPrefix(root)val outTo = to.toURI.toString.stripSuffix("/").stripPrefix(root)定义任务{scalafix.in(in, 编译)//.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").价值(到***.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

Scala 条件编译

用于覆盖 Scala 函数的 toString 的宏注释

如何在 scala 中合并多个导入?

For example as the following code:

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)
}

I look into the bytecode with a decompiler, and notice that the compile generate a java interface 'Test.Class' as pseudo code:

trait Class
{
    val f1: Int
}

and a class 'Test$$anon$1' implemeting 'Test.Class', pseudo code as:

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

and then the compiler initiaize the variable 'c' as:

c = new Test$$anon$1()

then calls the member 'f1' as normal invocation:

println(c.f1)

but it calls 'f2' using reflection:

println(reflMethod(c, f2))

Here, since the definition of the anonymous class 'Test$$anon$1' is visible in the same scope, is it possible to use macro to change the generated code to invoke 'f2' as normal field avoiding reflection?

I just want to change the invocation code in the same scope, not want to change the reflection code across scopes e.g. structual-typing instance as argument in function call. So I think it is possible in theory. But I am not familiar with scala macro, suggestions and code examples are appreciated. Thanks!

解决方案

Macros (more precisely, macro annotations because def macros are irrelevant to this task) are not enough. You want to rewrite not class (trait, object) or its parameter or member but local expressions. You can do this either with compiler plugin (see also) at compile time or with Scalameta code generation before compile time.

If you choose Scalameta then actually you want to rewrite your expressions semantically rather than syntactically because you want to go from local expression new Class... to the definition trait Class... and check whether there are proper members there. So you need Scalameta + SemanticDB. More convenient is to use Scalameta + SemanticDB with Scalafix (see also section for users).

You can create your own rewriting rule. Then you can use it either for rewriting your code in-place or for code generation (see below).

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.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
  }
}

in/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 (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)
}

build.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
  )

project/plugins.sbt

addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.16")

Other examples:

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

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

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

Scala conditional compilation

Macro annotation to override toString of Scala function

How to merge multiple imports in scala?

这篇关于是否可以使用宏来修改结构类型实例调用的生成代码?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆