I'm creating an enum value dynamically. I have a type of the enum (not the object itself) and a string representing a valid value, e.g. "Red" for 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]
It compiles but is deeply unhappy when run. Doesn't like New there, which makes sense because we don't "new" objects.
What's the proper way to invoke valueOf dynamically in a macro?
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
prints:
Inlined(Some(TypeIdent("TestMacros$")), Nil, Apply(Select(Ident("Test"), "valueOf"), List(Literal(StringConstant("A")))))
after removing Inline
garbadge:
Apply(Select(Ident("Test"), "valueOf"), List(Literal(StringConstant("A"))))
To construct this tree (your problem seem to be Ident
) I guess we can use:
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
Usage of Ref
is the same whether you construct anything dynamically or in compile time.
When I need to handle sealed hierarchies "statically" I use the following apprach:
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
}
Type
s into 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
case class
es (create None for the code above)(Actually, Scala 3 macros treat Java enums the same as Scala 3 enums without any parenthesis, so || isJavaEnumValue
is kinda redundant - you could remove it and it should still works with Scala 2's sealed
hierarchy of case object
s, Scala 3's enum
s AND Java enum
s).
As you can see in both cases, dynamical and statical, when I need to access case object/parameterless case in Scala 3's enum/Java's enum/companion object's method I am using Ref <: Term
to turn Symbol
into Term
(and it will be Ident
internally).