我正在嘗試case class從給定case class的Option欄位中生成一個。它需要遞回,所以如果欄位本身是 acase class那么它也必須Option從它的欄位中洗掉。
到目前為止,我設法在沒有欄位不是case class. 但是對于遞回,ClassTag如果它是一個case class. 但我不知道我該怎么做。似乎我只能訪問型別檢查之前的語法樹(考慮到最終的源代碼尚未形成,我想這是有道理的)。但我想知道是否有可能以某種方式實作這一目標。
這是我的代碼和缺少的部分作為注釋。
import scala.annotation.StaticAnnotation
import scala.collection.mutable
import scala.reflect.macros.blackbox.Context
import scala.language.experimental.macros
import scala.annotation.compileTimeOnly
class RemoveOptionFromFields extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro RemoveOptionFromFields.impl
}
object RemoveOptionFromFields {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
import c.universe._
def modifiedClass(classDecl: ClassDef, compDeclOpt: Option[ModuleDef]) = {
val result = classDecl match {
case q"case class $className(..$fields) extends ..$parents { ..$body }" =>
val fieldsWithoutOption = fields.map {
case ValDef(mods, name, tpt, rhs) =>
tpt.children match {
case List(first, second) if first.toString() == "Option" =>
// Check if `second` is a case class?
// Get it's fields if so
val innerType = tpt.children(1)
ValDef(mods, name, innerType, rhs)
case _ =>
ValDef(mods, name, tpt, rhs)
}
}
val withOptionRemovedFromFieldsClassDecl = q"case class WithOptionRemovedFromFields(..$fieldsWithoutOption)"
val newCompanionDecl = compDeclOpt.fold(
q"""
object ${className.toTermName} {
$withOptionRemovedFromFieldsClassDecl
}
"""
) {
compDecl =>
val q"object $obj extends ..$bases { ..$body }" = compDecl
q"""
object $obj extends ..$bases {
..$body
$withOptionRemovedFromFieldsClassDecl
}
"""
}
q"""
$classDecl
$newCompanionDecl
"""
}
c.Expr[Any](result)
}
annottees.map(_.tree) match {
case (classDecl: ClassDef) :: Nil => modifiedClass(classDecl, None)
case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => modifiedClass(classDecl, Some(compDecl))
case _ => c.abort(c.enclosingPosition, "This annotation only supports classes")
}
}
}
uj5u.com熱心網友回復:
不知道我理解你需要什么樣的遞回。假設我們有兩個案例類:第一個注釋(指第二個)和第二個未注釋
@RemoveOptionFromFields
case class MyClass1(mc: Option[MyClass2])
case class MyClass2(i: Option[Int])
結果應該是什么?
當前注釋轉換為
case class MyClass1(mc: Option[MyClass2])
object MyClass1 {
case class WithOptionRemovedFromFields(mc: Class2)
}
case class MyClass2(i: Option[Int])
如果欄位本身是一個案例類,那么它也必須從它的欄位中洗掉 Option。
宏注解只能重寫類及其伴侶,不能重寫不同的類。在我的帶有 2 個類的示例中,注釋可以修改 MyClass1及其伴侶,但不能重寫MyClass2或其伴侶。因為那MyClass2應該自己注釋。
在范圍內,宏注釋在對該范圍進行型別檢查之前被擴展。所以在重寫樹是無型別的。如果您需要輸入一些樹(以便您可以找到它們的符號),您可以使用c.typecheck
Scala 宏:型別化(又稱型別檢查)和非型別化樹有什么區別
要檢查某個類是否為案例類,您可以使用symbol.isClass && symbol.asClass.isCaseClass
如何在Scala的編譯時檢查某些T是否是案例類?
幾乎不需要ClassTags。
另一個復雜情況是何時MyClass1和MyClass2在同一范圍內
@RemoveOptionFromFields
case class MyClass1(mc: Option[MyClass2])
case class MyClass2(i: Option[Int])
然后在擴展MyClass1范圍的宏注釋時還沒有進行型別檢查,因此不可能對欄位定義樹進行型別檢查mc: Option[MyClass2](類MyClass2還不知道)。如果課程在不同的范圍內,那沒關系
{
@RemoveOptionFromFields
case class MyClass1(mc: Option[MyClass2])
}
case class MyClass2(i: Option[Int])
這是您的代碼的修改版本(我只是列印第二類的欄位)
import scala.annotation.StaticAnnotation
import scala.reflect.macros.blackbox
import scala.language.experimental.macros
import scala.annotation.compileTimeOnly
@compileTimeOnly("enable macro annotations")
class RemoveOptionFromFields extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro RemoveOptionFromFields.impl
}
object RemoveOptionFromFields {
def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._
def modifiedClass(classDecl: ClassDef, compDeclOpt: Option[ModuleDef]) = {
classDecl match {
case q"$mods class $className[..$tparams] $ctorMods(..$fields) extends { ..$earlydefns } with ..$parents { $self => ..$body }"
if mods.hasFlag(Flag.CASE) =>
val fieldsWithoutOption = fields.map {
case field@q"$mods val $name: $tpt = $rhs" =>
tpt match {
case tq"$first[..${List(second)}]" =>
val firstType = c.typecheck(tq"$first", mode = c.TYPEmode, silent = true) match {
case EmptyTree => println(s"can't typecheck $first while expanding @RemoveOptionFromFields for $className"); NoType
case t => t.tpe
}
if (firstType <:< typeOf[Option[_]].typeConstructor) {
val secondSymbol = c.typecheck(tq"$second", mode = c.TYPEmode, silent = true) match {
case EmptyTree => println(s"can't typecheck $second while expanding @RemoveOptionFromFields for $className"); NoSymbol
case t => t.symbol
}
if (secondSymbol.isClass && secondSymbol.asClass.isCaseClass) {
val secondClassFields = secondSymbol.typeSignature.decls.toList.filter(s => s.isMethod && s.asMethod.isCaseAccessor)
secondClassFields.foreach(s =>
c.typecheck(q"$s", silent = true) match {
case EmptyTree => println(s"can't typecheck $s while expanding @RemoveOptionFromFields for $className")
case t => println(s"field ${t.symbol} of type ${t.tpe}, subtype of Option: ${t.tpe <:< typeOf[Option[_]]}")
}
)
}
q"$mods val $name: $second = $rhs"
} else field
case _ =>
field
}
}
val withOptionRemovedFromFieldsClassDecl = q"case class WithOptionRemovedFromFields(..$fieldsWithoutOption)"
val newCompanionDecl = compDeclOpt.fold(
q"""
object ${className.toTermName} {
$withOptionRemovedFromFieldsClassDecl
}
"""
) {
compDecl =>
val q"$mods object $obj extends { ..$earlydefns } with ..$bases { $self => ..$body }" = compDecl
q"""
$mods object $obj extends { ..$earlydefns } with ..$bases { $self =>
..$body
$withOptionRemovedFromFieldsClassDecl
}
"""
}
q"""
$classDecl
$newCompanionDecl
"""
}
}
annottees match {
case (classDecl: ClassDef) :: Nil => modifiedClass(classDecl, None)
case (classDecl: ClassDef) :: (compDecl: ModuleDef) :: Nil => modifiedClass(classDecl, Some(compDecl))
case _ => c.abort(c.enclosingPosition, "This annotation only supports classes")
}
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/512177.html
上一篇:使用PySpark時獲取py4j.protocol.Py4JJavaError:java.lang.NoClassDefFoundError:scala/Product$class
