0

I have a TypeTag for one of several objects and I know that they extend a base trait with a known method signature. I also have access to the method's MethodSymbol if I need it. What I want to do is:

def invokeMyMethod[T: TypeTag](myMethodSymbol: MethodSymbol): String = {
  // I know T has structural type { def myMethod: String }
  // so I want the result of calling T.myMethod but I don't have
  // access to the actual object T, only its type.
}

Since I know the type represents an object, I know that the method is static and so I just need access to the singleton instance to be able to invoke the method. Unfortunately I can't find a way to get from the type to the instance.

I know I can obtain a RuntimeClass instance from a runtimeMirror, but I'm not sure what to do with that class once I have it. It seems to essentially have type AnyRef, so I tried casting it with no luck:

def invokeMyMethod[T: TypeTag]: Any = {
  val runtimeT = runtimeMirror(getClass.getClassLoader).runtimeClass(T)
  runtimeT.asInstanceOf[{ def myMethod: String }].myMethod
  // Error invoking method 'myMethod'
}

I also know I can get a ClassMirror from my ClassSymbol but that only seems to give access to the constructor MethodMirror which doesn't help if my item is an object and not a class:

def invokeMyMethod[T: TypeTag](myMethodSymbol: MethodSymbol): Any = {
  val mirror = runtimeMirror(getClass.getClassLoader)
  val runtimeT = mirror.runtimeClass(T)
  val mirrorT = mirror.reflect(runtimeT)
  mirrorT.reflectMethod(myMethodSymbol)()
  // Expected a member of class Class, you provided value T.myMethod
}

And I know if I had the actual runtime instance of T it would be easy with an InstanceMirror but I can't figure out how to get the InstanceMirror of my object type.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Ian
  • 5,704
  • 6
  • 40
  • 72

1 Answers1

1

Try

import scala.reflect.runtime.universe._
import scala.reflect.runtime

trait BaseTrait {
  def myMethod: String
}

object MyObject extends BaseTrait {
  override def myMethod: String = "MyObject.myMethod"
}

def invokeMyMethod[T: TypeTag]: String = {
  val typ = typeOf[T]
  val moduleSymbol   = typ.termSymbol.asModule
  val methodSymbol   = typ.decl(TermName("myMethod")).asMethod
  val runtimeMirror  = runtime.currentMirror
  val moduleMirror   = runtimeMirror.reflectModule(moduleSymbol)
  val instance       = moduleMirror.instance
  val instanceMirror = runtimeMirror.reflect(instance)
  val methodMirror   = instanceMirror.reflectMethod(methodSymbol)
  methodMirror().asInstanceOf[String]
}

invokeMyMethod[MyObject.type] // MyObject.myMethod

If the object is nested into a class try

class Outer {
  object `_` extends BaseTrait {
    override def myMethod: String = "_.myMethod"
  }
}

def invokeMyMethod[T: TypeTag]: String = {
  val typ = typeOf[T]
  val runtimeMirror          = runtime.currentMirror
  val moduleSymbol           = typ.termSymbol.asModule
  val outerClassSymbol       = moduleSymbol.owner.asClass
  val outerClassType         = outerClassSymbol.typeSignature 
  val outerConstructorSymbol = outerClassType.decl(termNames.CONSTRUCTOR).asMethod
  val outerClassMirror       = runtimeMirror.reflectClass(outerClassSymbol)
  val outerConstructorMirror = outerClassMirror.reflectConstructor(outerConstructorSymbol)
  val outerInstance          = outerConstructorMirror() // if class Outer has no-arg constructor
  val outerInstanceMirror    = runtimeMirror.reflect(outerInstance)
  val moduleMirror           = outerInstanceMirror.reflectModule(moduleSymbol)
  val methodSymbol           = typ.decl(TermName("myMethod")).asMethod
  val instance               = moduleMirror.instance
  val instanceMirror         = runtimeMirror.reflect(instance)
  val methodMirror           = instanceMirror.reflectMethod(methodSymbol)
  methodMirror().asInstanceOf[String]
}

val outer = new Outer
invokeMyMethod[outer.`_`.type] // _.myMethod

If Outer is a trait (abstract class) rather than class you can use Toolbox

trait Outer {
  object `_` extends BaseTrait {
    override def myMethod: String = "_.myMethod"
  }
}

def invokeMyMethod[T: TypeTag]: String = {
  val typ = typeOf[T]
  val runtimeMirror  = runtime.currentMirror
  val toolbox = runtimeMirror.mkToolBox()
  val outerClassSymbol = toolbox.define(
    q"class OuterImpl extends com.example.Outer".asInstanceOf[ClassDef]
  ).asClass
  toolbox.eval(q"(new $outerClassSymbol).`_`.myMethod").asInstanceOf[String]
}

val outer = new Outer {}
invokeMyMethod[outer.`_`.type] // _.myMethod

or

def invokeMyMethod[T: TypeTag]: String = {
  val typ = typeOf[T]
  val runtimeMirror  = runtime.currentMirror
  val toolbox = runtimeMirror.mkToolBox()
  val toolboxMirror  = toolbox.mirror
  val moduleSymbol   = typ.termSymbol.asModule
  val outerClassSymbol = toolbox.define(
    q"class OuterImpl extends com.example.Outer".asInstanceOf[ClassDef]
  ).asClass
  val outerClassType = outerClassSymbol.typeSignature
  val outerConstructorSymbol = outerClassType.decl(termNames.CONSTRUCTOR).asMethod
  val outerClassMirror = toolboxMirror.reflectClass(outerClassSymbol)
  val outerConstructorMirror = outerClassMirror.reflectConstructor(outerConstructorSymbol)
  val outerInstance = outerConstructorMirror()
  val outerInstanceMirror = runtimeMirror.reflect(outerInstance)
  val moduleMirror = outerInstanceMirror.reflectModule(moduleSymbol)
  val methodSymbol   = typ.decl(TermName("myMethod")).asMethod
  val instance       = moduleMirror.instance
  val instanceMirror = toolboxMirror.reflect(instance)
  val methodMirror   = instanceMirror.reflectMethod(methodSymbol)
  methodMirror().asInstanceOf[String]
}

val outer = new Outer {}
invokeMyMethod[outer.`_`.type] // _.myMethod

Or if you can use existing instance of the outer class/trait try

val outer = new Outer

def invokeMyMethod[T: TypeTag]: String = {
  val typ = typeOf[T]
  val runtimeMirror       = runtime.currentMirror
  val moduleSymbol        = typ.termSymbol.asModule
  val outerInstanceMirror = runtimeMirror.reflect(outer)
  val moduleMirror        = outerInstanceMirror.reflectModule(moduleSymbol)
  val methodSymbol        = typ.decl(TermName("myMethod")).asMethod
  val instance            = moduleMirror.instance
  val instanceMirror      = runtimeMirror.reflect(instance)
  val methodMirror        = instanceMirror.reflectMethod(methodSymbol)
  methodMirror().asInstanceOf[String]
}

invokeMyMethod[outer.`_`.type] // _.myMethod

Actually you can use outer deconstructing the input type

def invokeMyMethod[T: TypeTag]: String = {
  val typ = typeOf[T]
  val outerSymbol = typ match {
    case SingleType(pre, _) => pre.termSymbol
  }
  val runtimeMirror  = runtime.currentMirror
  val toolbox = runtimeMirror.mkToolBox()
  toolbox.eval(q"$outerSymbol.`_`.myMethod").asInstanceOf[String]
}

val outer = new Outer
invokeMyMethod[outer.`_`.type] // _.myMethod
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • thanks for the info! I feel like this is really close, but I'm running into an error `object _ is an inner module, use reflectModule on an InstanceMirror to obtain its ModuleMirror`. Any idea what this means? It seems as though I have to have the `InstanceMirror` to obtain the `ModuleMirror` but also vice versa... – Ian Nov 13 '20 at 19:32
  • @Ian I guess I managed to reproduce your error. See update. Looks like the object is nested and object name is `_` (with backticks). – Dmytro Mitin Nov 15 '20 at 13:37
  • Thanks @Dmytro! That works for me, although I think there's a few too many edge cases to make this work the way I wanted it to, so I'll probably just try to find another way to do what I want and avoid this (calling a method on a type) altogether. – Ian Nov 16 '20 at 20:48