Given a code which takes a type, takes it's known direct subclasses, filters the ones that are case classes and then takes the companion of that case class:
def firstSubclassWithCompanion[T: TypeTag]: String = {
val superclass = implicitly[TypeTag[T]].tpe.typeSymbol.asClass
val caseClass = superclass.knownDirectSubclasses.map(_.asClass).filter(_.isCaseClass).head
s"case class $caseClass has companion ${caseClass.companion}"
}
With a simple example
sealed trait With
case class WithCase() extends With
It gives the expected return
> firstSubclassWithCompanion[With]
"class WithCase has companion object WithCase"
As With
trait has a WithCase
subclass, which is case class that has a companion object (defined by compiler).
However, given the following example, where the subclass is defined in the companion object of the inheriting trait:
sealed trait Without
object Without {
case class WithoutCase() extends Without
}
It doesn't return the companion object
> firstSubclassWithCompanion[Without]
"class WithoutCase has companion <none>"
It works fine if it's defined in other object.
Bugs should be reported at https://github.com/scala/bug/issues
A workaround is to use caseClass.owner.typeSignature.decl(caseClass.name)
instead of caseClass.companion
.
Another workaround is to translate this runtime-reflection code into a macro (compile-time reflection). Since all the classes here are defined at compile time it makes sense to use a macro.
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def firstSubclassWithCompanion[T]: String = macro firstSubclassWithCompanionImpl[T]
def firstSubclassWithCompanionImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
import c.universe._
val superclass = weakTypeOf[T].typeSymbol.asClass
val caseClass = superclass.knownDirectSubclasses.map(_.asClass).filter(_.isCaseClass).head
val res = s"case class $caseClass has companion ${caseClass.companion}"
q"$res"
}