宏(更准确地说,宏注释,因为def 宏与此任务无关)是不够的。您想重写的不是类(特征,对象)或其参数或成员,而是局部表达式。您可以在编译时使用编译器插件(另请参阅)或在编译时使用Scalameta代码生成来执行此操作。
如果您选择 Scalameta,那么实际上您想在语义上而不是在语法上重写您的表达式,因为您想从本地表达式new Class...
转到定义trait Class...
并检查那里是否有适当的成员。所以你需要 Scalameta + SemanticDB。更方便的是使用 Scalameta + SemanticDB 和Scalafix(另见用户部分)。
您可以创建自己的重写规则。然后,您可以使用它就地重写代码或生成代码(见下文)。
规则/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)
}
构建.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/DmytroMitin/scalafix-codegen
https://github.com/DmytroMitin/scalameta-demo
Scala条件编译
用于覆盖 Scala 函数的 toString 的宏注释
如何在scala中合并多个导入?