scala 如何获取类型化案例类的默认字段值列表?

w1jd8yoj  于 2022-11-09  发布在  Scala
关注(0)|答案(1)|浏览(154)

有一个现有的宏可以获取默认字段的列表及其值https://github.com/lampepfl/dotty-macro-examples/blob/main/defaultParamsInference/src/macro.scala,但是,如果您尝试使用它来获取类型化类的默认字段,则会抛出Assert错误:

[error]     |Exception occurred while executing macro expansion.
[error]     |java.lang.AssertionError: assertion failed
[error]     |   at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:11)
[error]     |   at scala.quoted.runtime.impl.QuotesImpl$reflect$Ref$.apply(QuotesImpl.scala:435)
[error]     |   at scala.quoted.runtime.impl.QuotesImpl$reflect$Ref$.apply(QuotesImpl.scala:434)
[error]     |   at defaultParamsImpl(Decoder.scala:126)

如何修复它?它在一行中抛出:val mod = Ref(sym.companionModule)

oprakyz7

oprakyz71#

假设我们有

case class B(i: Int = 1, s: String = "a")
case class A[T](i: Int = 1, s: String = "a", l: List[T] = Nil)

首先,TypeTree.of[T].symbolB生成class B,而为一般A生成val <none>。因此,将TypeTree.of[T].symbol替换为TypeRepr.of[T].typeSymbol
其次,对于其伴随对象中的泛型A<init>$default$N方法也是泛型的。因此,您需要将mod.select(deff.symbol)应用于类型参数(否则将部分应用术语mod.select(deff.symbol))。
完整的代码:

import scala.annotation.experimental
import scala.quoted.*
import scala.deriving.*

inline def defaultParams[T]: Map[String, Any] = ${ defaultParmasImpl[T] }

@experimental // because .typeArgs is @experimental
def defaultParmasImpl[T](using quotes: Quotes, tpe: Type[T]): Expr[Map[String, Any]] =
  import quotes.reflect.*
  val typ = TypeRepr.of[T]
  val sym = typ.typeSymbol
  val typeArgs = typ.typeArgs

  val comp = sym.companionClass
  val mod = Ref(sym.companionModule)
  val names =
    for p <- sym.caseFields if p.flags.is(Flags.HasDefault)
      yield p.name
  val namesExpr: Expr[List[String]] =
    Expr.ofList(names.map(Expr(_)))

  val body = comp.tree.asInstanceOf[ClassDef].body
  val idents: List[Term] =
    for case deff @ DefDef(name, _, _, _) <- body
    if name.startsWith("$lessinit$greater$default")
    yield mod.select(deff.symbol).appliedToTypes(typeArgs)

  val identsExpr: Expr[List[Any]] =
    Expr.ofList(idents.map(_.asExpr))

  '{ $namesExpr.zip($identsExpr).toMap }

测试:

defaultParams[B] // Map(i -> 1, s -> a)
defaultParams[A[Double]] // Map(i -> 1, s -> a, l -> List())

Scala 3.1.3。
另请参阅Type Class Derivation accessing default values

相关问题