2

Assume I have an instance of MethodMirror created for a certain method of an object. By mirror's fields I can easily access return type and parameters of the method. But I actually need to obtain the type this method would have as a function.

Here is a toy code example which will help me explain, what I want to achieve. I'm using Scala 2.11.6.

import scala.reflect.runtime.universe._

object ForStackOverflow {
  object Obj {
    def method(x:String, y:String):Int = 0
    def expectedRetType():((String, String) => Int) = ???
  }

  def main(args: Array[String]) {
    val mirror:Mirror = runtimeMirror(getClass.getClassLoader)
    val instanceMirror = mirror.reflect(Obj)

    val methodSymbol:MethodSymbol = instanceMirror.symbol.toType.decl(TermName("method")).asMethod
    val methodMirror = instanceMirror.reflectMethod(methodSymbol)

    println(methodMirror.symbol.returnType)
    println(methodMirror.symbol.paramLists(0).map { x => x.info.resultType }.mkString(", "))

    val expectedSymbol:MethodSymbol = instanceMirror.symbol.toType.decl(TermName("expectedRetType")).asMethod
    println("I would like to produce from a 'methodMirror' this: "+expectedSymbol.returnType)
  }
}

I want to produce Type instance from the methodMirror which would represent a function. For this example it should be (String, String) => Int. I would prefer a solution that doesn't depend too much on the concrete Scala's FunctionX classes.

Mr 525
  • 75
  • 8
  • What do you mean by "I would prefer a solution that doesn't depend too much on the concrete Scala's FunctionX classes"? `(String, String) => Int` is just another name for `Function2[String, String, Int]`, they really are the very same thing (and as such none of them is more "concrete" than the other). – Régis Jean-Gilles Aug 20 '15 at 11:42
  • @RégisJean-Gilles I think that means, that it is possible to construct the function type from the method type using `universe.appliedType` and passing to it `Function2`, argument types and return type, but OP wants a more generic way of doing eta expansion on type objects. – Kolmar Aug 20 '15 at 11:59
  • Ah yes, you're right it's probably what he meant. Thanks. – Régis Jean-Gilles Aug 20 '15 at 12:30
  • Actually, I was looking for _any_ method solving my problem. Generic way of handling this is of course more elegant, since I cannot predict what will be the arity of the original method. I tried to use `universe.appliedType` before writing this post but I had some cryptic errors and I reached a conclusion that I'm using a wrong tool (scarce Scala's reflection documentation had something to do with it). – Mr 525 Aug 20 '15 at 19:58

2 Answers2

3

The method getEtaExpandedMethodType below does what you asked, and even handles methods with multiple parameter lists.

On the other hand it does not handle generic methods. By example def method[T](x: T) = 123, when eta-expanded, creates a function of type Any => Int, but getEtaExpandedMethodType will report T => Int which is not only incorrect but does not make sense at all (T has no meaning in this context).

def getEtaExpandedMethodType(methodSymbol: MethodSymbol): Type = {
  val typ = methodSymbol.typeSignature
  def paramType(paramSymbol: Symbol): Type = {
    // TODO: handle the case where paramSymbol denotes a type parameter
    paramSymbol.typeSignatureIn(typ)
  }

  def rec(paramLists: List[List[Symbol]]): Type = {
    paramLists match {
      case Nil => methodSymbol.returnType
      case params :: otherParams =>
        val functionClassSymbol = definitions.FunctionClass(params.length)
        appliedType(functionClassSymbol, params.map(paramType) :+ rec(otherParams))
    }
  }
  if (methodSymbol.paramLists.isEmpty) { // No arg method
    appliedType(definitions.FunctionClass(0), List(methodSymbol.returnType))
  } else {
    rec(methodSymbol.paramLists)
  }
}
def getEtaExpandedMethodType(methodMirror: MethodMirror): Type = getEtaExpandedMethodType(methodMirror.symbol)

REPL test:

scala> val mirror: Mirror = runtimeMirror(getClass.getClassLoader)
mirror: reflect.runtime.universe.Mirror = ...

scala> val instanceMirror = mirror.reflect(Obj)
instanceMirror: reflect.runtime.universe.InstanceMirror = instance mirror for Obj$@21b6e507

scala> val tpe = instanceMirror.symbol.toType
tpe: reflect.runtime.universe.Type = Obj.type

scala> getEtaExpandedMethodType(tpe.decl(TermName("method1")).asMethod)
res28: reflect.runtime.universe.Type = (String, String) => scala.Int

scala> getEtaExpandedMethodType(tpe.decl(TermName("method2")).asMethod)
res29: reflect.runtime.universe.Type = () => String

scala> getEtaExpandedMethodType(tpe.decl(TermName("method3")).asMethod)
res30: reflect.runtime.universe.Type = () => scala.Long

scala> getEtaExpandedMethodType(tpe.decl(TermName("method4")).asMethod)
res31: reflect.runtime.universe.Type = String => (scala.Float => scala.Double)

scala> getEtaExpandedMethodType(tpe.decl(TermName("method5")).asMethod)
res32: reflect.runtime.universe.Type = T => scala.Int

scala> getEtaExpandedMethodType(tpe.decl(TermName("method6")).asMethod)
res33: reflect.runtime.universe.Type = T => scala.Int
Régis Jean-Gilles
  • 32,541
  • 5
  • 83
  • 97
  • Thank you for the so versatile solution. Btw having these type arguments markers is important to me - I read them from a declaration of the method and use later to determine, if this method can be used in certain context. But this `compat._` thing might become obsolete in the future versions of Scala. Before I read your answer I wrote my own code (after I learned that `appliedType` was correct tool after all), which I post below. – Mr 525 Aug 20 '15 at 20:24
  • I was not very found of using `compat._` but just could not find the new alternative in less than one minute so I just left it as is (reflection documentation is already very sparse, but when it's out of date it becomes a real pain to find what you need). I replaced `typeRef` with `appliedType` in my code, works like a charm. – Régis Jean-Gilles Aug 21 '15 at 07:31
  • The funny thing is that I realize now that Kolmar had already pointed me to `appliedType` in his comment. Doh, silly me. – Régis Jean-Gilles Aug 21 '15 at 07:49
2

Here is probably the most straightforward solution using universe.appliedType. It doesn't work in the case of multiple parameter lists. I post this to show an alternative way of solving this problem.

def getEtaExpandedMethodType2(methodSymbol: MethodSymbol): Type  = {
  val typesList = methodSymbol.info.paramLists(0).map(x => x.typeSignature) :+ methodSymbol.returnType
  val arity = methodSymbol.paramLists(0).size
  universe.appliedType(definitions.FunctionClass(arity), typesList)
}
Mr 525
  • 75
  • 8
  • The `arityToFunType` functionality is already provided by the standard library through `definitions.FunctionClass`. This removes the main pain point in your code. – Régis Jean-Gilles Aug 21 '15 at 07:34
  • Right. Thank you for pointing this out. Yet another hidden useful Scala reflection utility. I improved my solution which now becames quite short. – Mr 525 Aug 21 '15 at 16:28