Jus12 Jus12 - 3 months ago 28
Scala Question

How to get return type string of a method using Scala reflection?

consider the following code:

object Foo{def foo(a:Int):List[(String, Int)] = ???}

class Bar{def bar(a:Int, b:Any):Option[(String, Long)] = ???}


Given either the object or class, I need to first find the method names (does not seem to be that difficult).

After that, for each method, I want to find a string description of the Scala return types (not the Java ones) . So for instance, for
Foo.foo
, I would need the String
List[(String, Int)]
and for
Bar.bar
, I would need the String
Option[(String, Long)]
.

I saw this and this tutorial but could not figure it out.

EDIT: Here is what I tried based on the comments:

class RetTypeFinder(obj:AnyRef) {
import scala.reflect.runtime.{universe => ru}
val m = ru.runtimeMirror(getClass.getClassLoader)
val im = m.reflect(obj)
def getRetType(methodName:String) = {
ru.typeOf[obj.type].declaration(ru.TermName(methodName)).asMethod.returnType
}
}
object A { def foo(a:Int):String = ??? } // define dummy object
class B { def bar(a:Int):String = ??? } // define dummy class
val a = new RetTypeFinder(A)
a.getRetType("foo") // exception here
val b = new RetTypeFinder(new B)
b.getRetType("bar") // exception here


The error I get is:

scala.ScalaReflectionException: <none> is not a method
at scala.reflect.api.Symbols$SymbolApi$class.asMethod(Symbols.scala:228)
at scala.reflect.internal.Symbols$SymbolContextApiImpl.asMethod(Symbols.scala:84)
at cs.reflect.Test.getRetCls(Test.scala:11)
...


However, this works (tried in REPL):

import scala.reflect.runtime.{universe => ru}
val m = ru.runtimeMirror(getClass.getClassLoader)

object A { def foo(a:Int):String = ??? } // define dummy object

val im = m.reflect(A)
ru.typeOf[A.type].declaration(ru.TermName("foo")).asMethod.returnType

class B { def bar(a:Int):String = ??? } // define dummy class

val im = m.reflect(new B)
ru.typeOf[B].declaration(ru.TermName("bar")).asMethod.returnType


I need to use it in the first way, where I don't know in advance what objects/classes will be passed. Any help will be appreciated.

Answer

Once you have a universe.Type you can use the way from the comments to get the return type of one of its methods:

import scala.reflect.runtime.{universe => ru}

def getRetTypeOfMethod(tpe: ru.Type)(methodName: String) =
  tpe.member(ru.TermName(methodName)).asMethod.returnType

To get a universe.Type the easiest way is to capture in an implicit TypeTag:

class RetTypeFinder[T <: AnyRef](obj: T)(implicit tag: ru.TypeTag[T]) {
  def getRetType(methodName: String) = {
    val tpe = tag.tpe
    getRetTypeOfMethod(tpe)(methodName)
  }
}

But if you don't have a TypeTag, but just an object of type AnyRef, you can go through a mirror to reflect it. The resulting Type will have some information lost due to Java's type erasure, but it would still be enough to get the return type of a method by name, because that's supported by JVM reflection:

class RetTypeFinder2(obj: AnyRef) {
  def getRetType(methodName: String) = {
    val mirror = ru.runtimeMirror(getClass.getClassLoader)
    val tpe = mirror.reflect(obj).symbol.info
    getRetTypeOfMethod(tpe)(methodName)
  }
}

Both methods work fine for your problem:

scala> new RetTypeFinder(A).getRetType("foo")
res0: reflect.runtime.universe.Type = String

scala> new RetTypeFinder2(A).getRetType("foo")
res1: reflect.runtime.universe.Type = String

scala> new RetTypeFinder(new B).getRetType("bar")
res2: reflect.runtime.universe.Type = String

scala> new RetTypeFinder2(new B).getRetType("bar")
res3: reflect.runtime.universe.Type = String
Comments