I'm trying to write a generalised function that takes a Type
of SomeSealedClass
and uses reflection to return a list of values overridden by the trait's child classes/objects.
sealed abstract class Sealed(val name: String)
object Sealed {
case object One extends Sealed("first one")
case object Two extends Sealed("second one")
case object Three extends Sealed("third one")
}
def expand(sealed: Type): List[String] = ???
I need the function expand
to return List("first one", "second one", "third one")
but only if Sealed has a field called name
.
I don't have access to the type because the caller is building the type object from a string using Class.forName()
. So, I cannot create instance mirrors to fetch the value of the name
field from each of the subclasses.
I'm starting to think that this is not actually possible but cannot figure out why. Those values are fixed. So, it should be simple to extract them without the type, isn't it?
Try
// libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value
import scala.reflect.runtime.universe._
import scala.reflect.runtime
import scala.reflect.internal.Symbols
val runtimeMirror = runtime.currentMirror
def expand(`sealed`: Type): List[String] =
`sealed`.typeSymbol.asClass.knownDirectSubclasses.toList.map(classSymbol => {
val moduleSymbol = classSymbol.asClass.module.asModule // see (*)
val instance = runtimeMirror.reflectModule(moduleSymbol).instance
val fieldSymbol = classSymbol.typeSignature.member(TermName("name")).asTerm
runtimeMirror.reflect(instance).reflectField(fieldSymbol).get.asInstanceOf[String]
})
or
// libraryDependencies += scalaOrganization.value % "scala-compiler" % scalaVersion.value
import scala.tools.reflect.ToolBox
val toolbox = runtimeMirror.mkToolBox()
def expand(`sealed`: Type): List[String] = {
val trees = `sealed`.typeSymbol.asClass.knownDirectSubclasses.toList.map(classSymbol => {
val moduleSymbol = classSymbol.asClass.module // see (*)
q"$moduleSymbol.name"
})
toolbox.eval(q"List.apply(..$trees)").asInstanceOf[List[String]]
}
Then
val clazz = Class.forName("App$Sealed")
val typ = runtimeMirror.classSymbol(clazz).toType
expand(typ) // List(first one, third one, second one)
(*) Get the module symbol, given I have the module class, scala macro