scalascala-reflect

Get TypeTag[A] from Class[A]


I have createOld method that I need to override and I cannot change it. I would like to use TypeTag to pattern match provided type in createNew. The goal is to find out how to call createNew from createOld. My current understanding is that compiler doesn't have enough type information about A in createOld method if it doesn't already come with TypeTag[A].

object TypeTagFromClass {
  class C1
  class C2

  // How to get TypeTag[A] needed by createNew?
  def createOld[A](c: Class[A]): A = createNew ???

  def createNew[A : TypeTag]: A = {
    val result = typeOf[A] match {
      case a if a =:= typeOf[C1] => new C1()
      case a if a =:= typeOf[C2] => new C2()
    }
    result.asInstanceOf[A]
  }
}

Solution

  • It is possible to create a TypeTag from a Class using Scala reflection, though I'm not sure if this implementation of TypeCreator is absolutely correct:

    import scala.reflect.runtime.universe._
    
    def createOld[A](c: Class[A]): A = createNew {
      val mirror = runtimeMirror(c.getClassLoader)  // obtain runtime mirror
      val sym = mirror.staticClass(c.getName)  // obtain class symbol for `c`
      val tpe = sym.selfType  // obtain type object for `c`
      // create a type tag which contains above type object
      TypeTag(mirror, new TypeCreator {
        def apply[U <: Universe with Singleton](m: api.Mirror[U]) =
          if (m eq mirror) tpe.asInstanceOf[U # Type]
          else throw new IllegalArgumentException(s"Type tag defined in $mirror cannot be migrated to other mirrors.")
      })    
    }
    

    However, you don't really need full TypeTag if you don't need to inspect generic parameters and full Scala type information. You can use ClassTags for that:

    def createNew[A: ClassTag]: A = {
      val result = classTag[A].runtimeClass match {
        case a if a.isAssignableFrom(classOf[C1]) => new C1()
        case a if a.isAssignableFrom(classOf[C2]) => new C2()
      }
      result.asInstanceOf[A]
    }
    

    Or with some implicit sugar:

    implicit class ClassTagOps[T](val classTag: ClassTag[T]) extends AnyVal {
      def <<:(other: ClassTag[_]) = classTag.runtimeClass.isAssignableFrom(other.runtimeClass)
    }
    
    def createNew[A: ClassTag]: A = {
      val result = classTag[A] match {
        case a if a <<: classTag[C1] => new C1()
        case a if a <<: classTag[C2] => new C2()
      }
      result.asInstanceOf[A]
    }
    

    You can simplify that even further by using plain old Java newInstance() method:

    def createNew[A: ClassTag]: A = classTag[A].runtimeClass.newInstance().asInstanceOf[A]
    

    This, of course, would only work if you don't need different constructor parameters for different classes.

    Calling this createNew from createOld is much simpler than the one with TypeTags:

    def createOld[A](c: Class[A]): A = createNew(ClassTag[A](c))