Search code examples
scalareflectionstaticmirrorscala-reflect

Invoke static method with access only to the containing object's type


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.


Solution

  • 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