宏(更准确地说,macro annotations 因为def macros 与此任务无关)是不够的。您想要重写的不是类(特征,对象)或其参数或成员,而是局部表达式。您可以在编译时使用compiler plugin(请参阅also)或在编译时使用Scalameta 代码生成来执行此操作。
如果您选择 Scalameta,那么实际上您希望在语义上而不是在语法上重写您的表达式,因为您想从本地表达式 new Class... 转到定义 trait Class... 并检查那里是否有适当的成员。所以你需要 Scalameta + SemanticDB。更方便的是使用 Scalameta + SemanticDB 和 Scalafix(另见 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
}
}
在/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)
}
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
Macro annotation to override toString of Scala function
How to merge multiple imports in scala?