如何在Scala 3宏中动态创建枚举值?

kmb7vmvb  于 6个月前  发布在  Scala
关注(0)|答案(1)|浏览(68)

我正在动态创建一个枚举值。我有一个枚举类型(不是对象本身)和一个表示有效值的字符串,例如“Red”代表Color.Red。

// T is Color (enum).  I've discovered the valueOf() method lives in the companion object
val sym = TypeRepr.of[T].classSymbol.get.companionClass

// Try to invoke the valueOf() method on the enum with the value, enumValue ("Red")
Apply(Select.unique(New(TypeIdent(sym)), "valueOf"), List('{ enumValue }.asTerm)).asExprOf[T]

字符串
它可以编译,但运行时非常不开心,不喜欢New,这是有道理的,因为我们不“new”对象。
在宏中动态调用valueOf的正确方法是什么?

7d7tgy0s

7d7tgy0s1#

enum Test:
  case A

object TestMacros: {
  def printAST(using quotes: scala.quoted.Quotes): Unit = {
    import quotes.reflect.*
    println('{ Test.valueOf("A") }.asTerm.show(using Printer.TreeStructure))
  }
}

// call printAST insome inline def

字符串
打印:

Inlined(Some(TypeIdent("TestMacros$")), Nil, Apply(Select(Ident("Test"), "valueOf"), List(Literal(StringConstant("A")))))


删除Inline garbadge后:

Apply(Select(Ident("Test"), "valueOf"), List(Literal(StringConstant("A"))))


要构造这棵树(你的问题似乎是Ident),我想我们可以使用:用途:

val sym = TypeRepr.of[Test].typeSymbol
Apply(Select.unique(Ref(sym), "valueOf"), List(...))
//                  ^ Ref can be used to turn a symbol of singleton type to Term


无论是动态构造还是在编译时构造,Ref的用法都是一样的。
当我需要“静态”处理密封的层次结构时,我使用以下方法:
1.获取所有子类型的列表

def extractRecursively(sym: Symbol): List[Symbol] =
  if sym.flags.is(Flags.Sealed) then sym.children.flatMap(extractRecursively)
  else if sym.flags.is(Flags.Enum) then List(sym.typeRef.typeSymbol)
  else if sym.flags.is(Flags.Module) then List(sym.typeRef.typeSymbol.moduleClass)
  else List(sym)

extractRecursively(TypeRepr.of[A].typeSymbol).distinct.map { subtype =>
  subtype.primaryConstructor.paramSymss match {
    // subtype takes type parameters
    case typeParamSymbols :: _ if typeParamSymbols.exists(_.isType) =>
      // we have to figure how subtypes type params map to parent type params
      val appliedTypeByParam: Map[String, TypeRepr] =
        subtype.typeRef
          .baseType(TypeRepr.of[A].typeSymbol)
          .typeArgs
          .map(_.typeSymbol.name)
          .zip(TypeRepr.of[A].typeArgs)
          .toMap
         
      val typeParamReprs: List[TypeRepr] = typeParamSymbols.map(_.name).map(appliedTypeByParam)
      subtype.typeRef.appliedTo(typeParamReprs).asType
    // subtype is monomorphic
    case _ =>
      subtype.typeRef.asType
}


1.将Type s转换为Expr s

// subtype: Type[?]
//
// put the subtype into implicit scope with some name, e.g.:
//
// type B <: A
// given B: Type[B] = subtype.asInstanecOf[Type[B]]

val B = TypeRepr.of[B] // type of A's subtype
val sym = B.typeSymbol

// case object B extends A
def isScala2Enum = sym.flags.is(Flags.Case | Flags.Module)
// Scala 3 enum's parametersless case is NOT case object
// but: val B = new A {}
def isScala3Enum = sym.flags.is(Flags.Case | Flags.Enum | Flags.JavaStatic)
// Java: enum A { B; }
def isJavaEnumValue: Boolean = TypeRepr.of[B] <:< TypeRepr.of[java.lang.Enum[?]] && !sym.isAbstract

if (isScala3Enum || isJavaEnumValue) then Some(Ref(sym).asExprOf[B])
else if isScala2Enum then Some(Ref(sym.companionModule).asExprOf[B])
else None


1.处理某些子类型为case class es的情况(为上面的代码创建None)
(实际上,Scala 3宏处理Java枚举与Scala 3枚举相同,没有任何括号,所以|| isJavaEnumValue有点多余-您可以删除它,它仍然可以与Scala 2的sealed层次结构case object s,Scala 3的enum s和Java enum s一起工作)。
正如你在这两种情况下所看到的,动态和静态,当我需要在Scala 3的enum/Java的enum/companion object的方法中访问case对象/无参数case时,我使用Ref <: TermSymbol转换为Term(内部将是Ident)。

相关问题