问题
I'm trying to build a simple typeclass IsEnum[T]
using a macro.
I use knownDirectSubclasses
to get all the direct subclasses if T
, ensure T
is a sealed trait, and that all subclasses are of case objects (using subSymbol.asClass.isModuleClass && subSymbol.asClass.isCaseClass
).
Now I'm trying to build a Seq
with the case objects referred by the subclasses.
It's working, using a workaround:
Ident(subSymbol.asInstanceOf[scala.reflect.internal.Symbols#Symbol].sourceModule.asInstanceOf[Symbol])
But I copied that from some other question, yet it seems hacky and wrong. Why does that work? and is there a cleaner way to achieve that?
回答1:
In 2.13 you can materialize scala.ValueOf
val instanceTree = c.inferImplicitValue(appliedType(typeOf[ValueOf[_]].typeConstructor, subSymbol.asClass.toType))
q"$instanceTree.value"
Tree will be different
sealed trait A
object A {
case object B extends A
case object C extends A
}
//scalac: Seq(new scala.ValueOf(A.this.B).value, new scala.ValueOf(A.this.C).value)
but at runtime it's still Seq(B, C)
.
In 2.12 shapeless.Witness
can be used instead of ValueOf
val instanceTree = c.inferImplicitValue(appliedType(typeOf[Witness.Aux[_]].typeConstructor, subSymbol.asClass.toType))
q"$instanceTree.value"
//scalac: Seq(Witness.mkWitness[App.A.B.type](A.this.B.asInstanceOf[App.A.B.type]).value, Witness.mkWitness[App.A.C.type](A.this.C.asInstanceOf[App.A.C.type]).value)
libraryDependencies += "com.chuusai" %% "shapeless" % "2.4.0-M1" // in 2.3.3 it doesn't work
In Shapeless they use kind of
subSymbol.asClass.toType match {
case ref @ TypeRef(_, sym, _) if sym.isModuleClass => mkAttributedQualifier(ref)
}
https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/singletons.scala#L230
or in our case simply
mkAttributedQualifier(subSymbol.asClass.toType)
but their mkAttributedQualifier
also uses downcasting to compiler internals and the tree obtained is like Seq(A.this.B, A.this.C)
.
Also
Ident(subSymbol.companionSymbol)
seems to work (tree is Seq(B, C)
) but .companionSymbol
is deprecated (in scaladocs it's written "may return unexpected results for module classes" i.e. for objects).
Following approach similar to the one used by @MateuszKubuszok in his library enumz you can try also
val objectName = symbol.fullName
c.typecheck(c.parse(s"$objectName"))
and the tree is Seq(App.A.B, App.A.C)
.
Finally, if you're interested in the tree Seq(B, C)
(and not some more complicated tree) it seems you can replace
Ident(subSymbol.asInstanceOf[scala.reflect.internal.Symbols#Symbol].sourceModule.asInstanceOf[Symbol])
with more conventional
Ident(subSymbol.owner.info.decl(subSymbol.name.toTermName))
来源:https://stackoverflow.com/questions/64121612/get-the-module-symbol-given-i-have-the-module-class-scala-macro