Since you only know that statically enumType
has type just EnumType
and want to match based on runtime value/runtime class of enumType
, you'll have to use some kind of reflection:
// libraryDependencies += scalaOrganization.value % "scala-compiler" % scalaVersion.value
import scala.reflect.runtime.{currentMirror => cm}
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox
val tb = cm.mkToolBox()
def getTypeClass(enumType: EnumType): Operation[EnumType] =
tb.eval(q"_root_.scala.Predef.implicitly[Operation[${cm.moduleSymbol(enumType.getClass)}]]")
.asInstanceOf[Operation[EnumType]]
or
def getTypeClass(enumType: EnumType): Operation[EnumType] =
tb.eval(tb.untypecheck(tb.inferImplicitValue(
appliedType(
typeOf[Operation[_]].typeConstructor,
cm.moduleSymbol(enumType.getClass).moduleClass.asClass.toType
),
silent = false
))).asInstanceOf[Operation[EnumType]]
or
def getTypeClass(enumType: EnumType): Operation[EnumType] = {
val cases = typeOf[EnumType].typeSymbol.asClass.knownDirectSubclasses.map(subclass => {
val module = subclass.asClass.module
val pattern = pq"`$module`"
cq"$pattern => _root_.scala.Predef.implicitly[Operation[$module.type]]"
})
tb.eval(q"(et: EnumType) => et match { case ..$cases }")
.asInstanceOf[EnumType => Operation[EnumType]]
.apply(enumType)
}
- or a macro (automating the pattern matching)
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def getTypeClass(enumType: EnumType): Operation[EnumType] = macro getTypeClassImpl
def getTypeClassImpl(c: blackbox.Context)(enumType: c.Tree): c.Tree = {
import c.universe._
val cases = typeOf[EnumType].typeSymbol.asClass.knownDirectSubclasses.map(subclass => {
val module = subclass.asClass.module
val pattern = pq"`$module`"
cq"$pattern => _root_.scala.Predef.implicitly[Operation[$module.type]]"
})
q"$enumType match { case ..$cases }"
}
//scalac: App.this.enumType match {
// case Add10 => _root_.scala.Predef.implicitly[Macro.Operation[Add10.type]]
// case Add50 => _root_.scala.Predef.implicitly[Macro.Operation[Add50.type]]
//}
Since all the objects are defined at compile time I guess that a macro is better.
Covariant case class mapping to its base class without a type parameter and back
Getting subclasses of a sealed trait
Iteration over a sealed trait in Scala?
You can even make a macro whitebox, then using runtime reflection in the macro you can have Add50
type statically (if the class is known during macro expansion)
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
def getTypeClass(enumType: EnumType): Operation[EnumType] = macro getTypeClassImpl
def getTypeClassImpl(c: whitebox.Context)(enumType: c.Tree): c.Tree = {
import c.universe._
val clazz = c.eval(c.Expr[EnumType](c.untypecheck(enumType))).getClass
val rm = scala.reflect.runtime.currentMirror
val symbol = rm.moduleSymbol(clazz)
//q"_root_.scala.Predef.implicitly[Operation[${symbol.asInstanceOf[ModuleSymbol]}.type]]" // implicit not found
//q"_root_.scala.Predef.implicitly[Operation[${symbol/*.asInstanceOf[ModuleSymbol]*/.moduleClass.asClass.toType.asInstanceOf[Type]}]]" // implicit not found
// "migrating" symbol from runtime universe to macro universe
c.parse(s"_root_.scala.Predef.implicitly[Operation[${symbol.fullName}.type]]")
}
object App {
val enumType: EnumType = Add50
}
val operation = getTypeClass(App.enumType)
operation: Operation[Add50.type] // not just Operation[EnumType]
operation.op(10) // 60
How to accept only a specific subtype of existential type?
In a scala macro, how to get the full name that a class will have at runtime?