1

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.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Random42
  • 8,989
  • 6
  • 55
  • 86

1 Answers1

1

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"
}
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66