简体   繁体   中英

In Scala Reflection, How to get generic type parameter of a concrete subclass?

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)

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

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 (aka 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM