宏(更准确地说,宏注释 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/