4

I'm trying to use Scala reflection to define traits case classes and their companions could implement to become "exportable" (e.g. to Map[String,Any]) and "importable" from the same. It's working nicely for top level case classes, but I can't make it work for inner classes. I would know how to instantiate an inner class reflectively if I already had a handle on the enclosing instance, which i could reflect to an InstanceMirror, but for now I am writing a trait that will be implemented later, by top-level or inner classes.

I should be able to make this work, as long as the companion and the constructed instances will share the same enclosing instance. But I've not been able to figure out how to determine the companion's enclosing instance reflectively.

Here's a way simplified example of what I am trying to do, and the problem that occurs:

import scala.reflect.runtime.universe._;

trait CompanionOfReflectiveConstructable[T] {
  def tType : Type;

  lazy val mirror       : Mirror       = runtimeMirror( this.getClass.getClassLoader );
  lazy val ctorDecl     : Symbol       = tType.declaration(nme.CONSTRUCTOR);
  lazy val ctor         : MethodSymbol = ctorDecl.asMethod;
  lazy val tClass       : ClassSymbol  = tType.typeSymbol.asClass;
  lazy val tClassMirror : ClassMirror  = mirror.reflectClass( tClass );
  lazy val ctorF        : MethodMirror = tClassMirror.reflectConstructor( ctor );

  // in real-life, i'd derive arguments from ctor.paramss
  // but to highlight our issue, we'll assume a no arg constructor
  def createInstance : T = ctorF().asInstanceOf[T]; 
}

trait ReflectiveConstructable;

object Test1 extends CompanionOfReflectiveConstructable[Test1] {
  def tType = typeOf[Test1];
}
class Test1 extends ReflectiveConstructable;

class Outer {
  object Test2 extends CompanionOfReflectiveConstructable[Test2] {
    def tType = typeOf[Test2];
  }
  class Test2 extends ReflectiveConstructable;
}

Here's what happens.

scala> Test1.createInstance
res0: Test1 = Test1@2b52833d

scala> (new Outer).Test2.createInstance
scala.ScalaReflectionException: class Test2 is an inner class, use reflectClass on an InstanceMirror to obtain its ClassMirror
        at scala.reflect.runtime.JavaMirrors$JavaMirror.ErrorInnerClass(JavaMirrors.scala:126)
...

Test1 works great. The problem with Test2 is clear -- I need to to get my ClassMirror via an InstanceMirror rather than via my top level mirror. (See here, here, and here.) But, from within CompanionOfReflectiveConstructable, I don't know how to check whether I am inner or who my enclosing instance is, to conditionally perform the appropriate work. Does anyone know how to do this?

Many thanks!

Community
  • 1
  • 1
Steve Waldman
  • 13,689
  • 1
  • 35
  • 45

1 Answers1

1

The main problem that I see here is that you're using reflection inside of the companion object of an inner class. The class Test2 will have a field defined called $outer that contains the outer class, however, from the Test2 companion object, you cannot get the instance of Outer that you need to create Test2. The only work around that I can see, is to pass an optional instance to you're companion trait:

trait CompanionOfReflectiveConstructable[T] {
  def tType: Type

  def outer: Option[AnyRef] = None

  val mirror = runtimeMirror(this.getClass.getClassLoader)
  val tClassMirror = outer
    .map(a => mirror.reflect(a).reflectClass(tType.typeSymbol.asClass))
    .getOrElse(mirror.reflectClass(tType.typeSymbol.asClass))

  val ctorF = tClassMirror.reflectConstructor(tType.declaration(nme.CONSTRUCTOR).asMethod)

  // in real-life, i'd derive arguments from ctor.paramss
  // but to highlight our issue, we'll assume a no arg constructor
  def createInstance: T = ctorF().asInstanceOf[T]
}

trait ReflectiveConstructable

class Test1 extends ReflectiveConstructable

object Test1 extends CompanionOfReflectiveConstructable[Test1] {
  def tType = typeOf[Test1]
}

class Outer {

  object Test2 extends CompanionOfReflectiveConstructable[Test2] {
    def tType = typeOf[Test2]
    override def outer = Option(Outer.this)
  }

  class Test2 extends ReflectiveConstructable
}

It would be nice if there was a way to get the Outer instance from the Test2 companion object, but sadly it's not available at runtime.

Noah
  • 13,821
  • 4
  • 36
  • 45
  • So this is a fine workaround, and it might well be the best that can be done. But it's not a solution, a means of reflectively finding the enclosing instance :( It looks to me like Scala reflection just doesn't handle path-dependent types correctly. in my example, for an instance of Test2, ctor.owner.owner should be represent an instance somehow, but is instead a class. It looks like reflection can see the type Outer#Test2, but not the type o.Test2, where o is an instance of outer. – Steve Waldman Apr 06 '13 at 10:28
  • So, when the inner companion object is compiled, it doesn't get a reference to the outer class like the inner class does (it's part of the default constructor). If you look at the Outer instance, it has a volatile ref to the module (companion object), but the module has no reference to the outer class. So now your trait has no way to get the reference to the outer class since it's applied on the inner companion object. You might be able to use scala macros to get around this, otherwise I don't think it's possible to get the outer class instance reference via any sort of reflection. – Noah Apr 06 '13 at 17:41
  • so, i haven't tried to follow the bytecode, but it can't be _unconditionally_ true that inner modules don't reference the enclosing instance? what if you refer to enclosing instance members? there's got to be a path back then. regardless, it would mean that reflection can't do what it really ought to do for inner modules and path-dependent types. (i'm sorry not to accept the answer; i hope you get the half-bounty for top-rated. but the problem remains unsolved, perhaps because it's insoluble.) – Steve Waldman Apr 07 '13 at 06:02