Search code examples
scalareflectionenumerationscala-reflectsealed-class

How do I fetch the overridden member of a sealed trait via reflection?


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?


Solution

  • 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