In Scala Reflection, How to get generic type param

2019-02-16 21:11发布

问题:

Assuming that I have a Generic superclass:

class GenericExample[T](
                         a: String,
                         b: T
                       ) {

  def fn(i: T): T = b
}

and a concrete subclass:

case class Example(
                    a: String,
                    b: Int
                  ) extends GenericExample[Int](a, b)

I want to get the type parameter of function "fn" by scala reflection, so I select and filter through its members:

import ScalaReflection.universe._

val baseType = typeTag[Example]

val member = baseType
  .tpe
  .member(methodName: TermName)
  .asTerm
  .alternatives
  .map(_.asMethod)
  .head

    val paramss = member.paramss
    val actualTypess: List[List[Type]] = paramss.map {
      params =>
        params.map {
          param =>
            param.typeSignature
        }
    }

I was expecting scala to give me the correct result, which is List(List(Int)), instead I only got the generic List(List(T))

Crunching through the document I found that typeSignature is the culprit:

 *  This method always returns signatures in the most generic way possible, even if the underlying symbol is obtained from an
 *  instantiation of a generic type.

And it suggests me to use the alternative:

def typeSignatureIn(site: Type): Type

However, since class Example is no longer generic, there is no way I can get site from typeTag[Example], can anyone suggest me how to get typeOf[Int] given only typeTag[Example]? Or there is no way to do it and I have to revert to Java reflection?

Thanks a lot for your help.

UPDATE: After some quick test I found that even MethodSymbol.returnType doesn't work as intended, the following code:

member.returnType

also yield T, annd it can't be corrected by asSeenFrom, as the following code doesn't change the result:

member.returnType.asSeenFrom(baseType.tpe, baseType.tpe.typeSymbol.asClass)

回答1:

There are two approaches which I can suggest:

1) Reveal generic type from base class:

import scala.reflect.runtime.universe._

class GenericExample[T: TypeTag](a: String, b: T) {
  def fn(i: T) = "" + b + i
}

case class Example(a: String, b: Int) extends GenericExample[Int](a, b) {}

val classType = typeOf[Example].typeSymbol.asClass
val baseClassType = typeOf[GenericExample[_]].typeSymbol.asClass
val baseType = internal.thisType(classType).baseType(baseClassType)

baseType.typeArgs.head // returns reflect.runtime.universe.Type = scala.Int

2) Add implicit method which returns type:

import scala.reflect.runtime.universe._

class GenericExample[T](a: String, b: T) {
  def fn(i: T) = "" + b + i
}

case class Example(a: String, b: Int) extends GenericExample[Int](a, b)

implicit class TypeDetector[T: TypeTag](related: GenericExample[T]) {
  def getType(): Type = {
    typeOf[T]
  }
}

new Example("", 1).getType() // returns reflect.runtime.universe.Type = Int


回答2:

I'm posting my solution: I think there is no alternative due to Scala's design:

The core difference between methods in Scala reflection & Java reflection is currying: Scala method comprises of many pairs of brackets, calling a method with arguments first merely constructs an anonymous class that can take more pairs of brackets, or if there is no more bracket left, constructs a NullaryMethod class (a.k.a. call-by-name) that can be resolved to yield the result of the method. So types of scala method is only resolved at this level, when method is already broken into Method & NullaryMethod Signatures.

As a result it becomes clear that the result type can only be get using recursion:

  private def methodSignatureToParameter_ReturnTypes(tpe: Type): (List[List[Type]], Type) = {
    tpe match {
      case n: NullaryMethodType =>
        Nil -> n.resultType
      case m: MethodType =>
        val paramTypes: List[Type] = m.params.map(_.typeSignatureIn(tpe))
        val downstream = methodSignatureToParameter_ReturnTypes(m.resultType)
        downstream.copy(_1 = List(paramTypes) ++ methodSignatureToParameter_ReturnTypes(m.resultType)._1)
      case _ =>
        Nil -> tpe
    }
  }

  def getParameter_ReturnTypes(symbol: MethodSymbol, impl: Type) = {

    val signature = symbol.typeSignatureIn(impl)
    val result = methodSignatureToParameter_ReturnTypes(signature)
    result
  }

Where impl is the class that owns the method, and symbol is what you obtained from Type.member(s) by scala reflection