9

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)
tribbloid
  • 4,026
  • 14
  • 64
  • 103
  • Even before actually having read your question. Throughout my whole experience with scala, one of its core concepts is to delegate as much as you can to the compiler. So reflection is not one of those things you would do in scala. However you can use Macros or context bounds(Manifests and ClassManifests) – caeus Jul 07 '16 at 16:52
  • Is Manifest in the process of being deprecated? And ClassManifest renamed to ClassTag? I hope I can at least get the erased ClassTag from method, but still it is unable to do so. – tribbloid Jul 19 '16 at 18:17

2 Answers2

13

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
Avseiytsev Dmitriy
  • 1,160
  • 9
  • 19
  • I'm afraid in most of my cases GenericExample is unknown and buried deep in an arbitrary graph of inheritance, so its not feasible to write a companion Detector, but I'll try groking the first solution, thanks a lot for your answer! – tribbloid Jul 06 '16 at 18:28
  • Doesn't work: Error:(72, 20) not found: value internal val baseType = internal.thisType(classType).baseType(baseClassType). I'm using Scala 2.10.6 for compatibility with a library – tribbloid Jul 07 '16 at 16:16
  • I think I have a clue now, MethodSymbol from super generic class always erase type parameters, to circumvent it I have to use MethodType instead. – tribbloid Jul 07 '16 at 18:54
1

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

tribbloid
  • 4,026
  • 14
  • 64
  • 103
  • That being said, I still hope method in Scala and Java reflection can be made more interoperable, as they both have their advantage: java method is much faster to invoke, but its runtime type check in JVM is quite loose, there is no perfection here :- – tribbloid Jul 19 '16 at 19:40