0

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?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Turbo123
  • 35
  • 1
  • 6

1 Answers1

0

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

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66