是否可以使用宏来修改结构类型实例调用的生成代码?
Is it possible to using macro to modify the generated code of structural-typing instance invocation?
例如如下代码:
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
}
和一个class 'Test$$anon'实现'Test.Class',伪代码为:
class Test$$anon extends Class
{
val f1: Int = 1
val f2: String = "Class"
}
然后编译器将变量 'c' 初始化为:
c = new Test$$anon()
然后像正常调用一样调用成员 'f1':
println(c.f1)
但它使用反射调用 'f2':
println(reflMethod(c, f2))
这里,由于匿名class 'Test$$anon'的定义在同一范围内可见,是否可以使用宏将生成的代码更改为正常调用'f2'场避免反射?
我只想在同一个范围内更改调用代码,不想跨范围更改反射代码,例如结构类型实例作为函数调用中的参数。所以我觉得理论上是可以的。但我不熟悉 scala 宏,建议和代码示例表示赞赏。谢谢!
宏(更准确地说,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 编译前的代码生成。
如果您选择 Scalameta 那么实际上您想要从语义上而不是句法上重写您的表达式,因为您想要从本地表达式 new Class...
到定义 trait Class...
并检查那里是否有适当的成员.所以你需要 Scalameta + SemanticDB. More convenient is to use Scalameta + SemanticDB with Scalafix (see also section for users).
您可以创建自己的重写规则。然后您可以使用它来就地重写代码或用于代码生成(见下文)。
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(在sbt out/compile
之后)
object Test extends App
{
trait Class
{
val f1: Int
}
val c = {
class anon$meta extends Class {
val f1: Int = 1
val f2: String = "Class"
}
new anon$meta()
}
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")
其他示例:
https://github.com/olafurpg/scalafix-codegen
https://github.com/DmytroMitin/scalafix-codegen
https://github.com/DmytroMitin/scalameta-demo
Scala conditional compilation
How to merge multiple imports in 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)
}
我用反编译器查看字节码,注意到编译生成了一个 java 接口 'Test.Class' 作为伪代码:
trait Class
{
val f1: Int
}
和一个class 'Test$$anon'实现'Test.Class',伪代码为:
class Test$$anon extends Class
{
val f1: Int = 1
val f2: String = "Class"
}
然后编译器将变量 'c' 初始化为:
c = new Test$$anon()
然后像正常调用一样调用成员 'f1':
println(c.f1)
但它使用反射调用 'f2':
println(reflMethod(c, f2))
这里,由于匿名class 'Test$$anon'的定义在同一范围内可见,是否可以使用宏将生成的代码更改为正常调用'f2'场避免反射?
我只想在同一个范围内更改调用代码,不想跨范围更改反射代码,例如结构类型实例作为函数调用中的参数。所以我觉得理论上是可以的。但我不熟悉 scala 宏,建议和代码示例表示赞赏。谢谢!
宏(更准确地说,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 编译前的代码生成。
如果您选择 Scalameta 那么实际上您想要从语义上而不是句法上重写您的表达式,因为您想要从本地表达式 new Class...
到定义 trait Class...
并检查那里是否有适当的成员.所以你需要 Scalameta + SemanticDB. More convenient is to use Scalameta + SemanticDB with Scalafix (see also section for users).
您可以创建自己的重写规则。然后您可以使用它来就地重写代码或用于代码生成(见下文)。
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(在sbt out/compile
之后)
object Test extends App
{
trait Class
{
val f1: Int
}
val c = {
class anon$meta extends Class {
val f1: Int = 1
val f2: String = "Class"
}
new anon$meta()
}
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")
其他示例:
https://github.com/olafurpg/scalafix-codegen
https://github.com/DmytroMitin/scalafix-codegen
https://github.com/DmytroMitin/scalameta-demo
Scala conditional compilation
How to merge multiple imports in scala?