0

I'm using Scala 2.10.1 and I'm trying the define a method which will retrieve all the vals (including the inherited ones) from an object.

I have the following:

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

object Reflection {

  val mirror = ru.runtimeMirror(this.getClass.getClassLoader)

  def findVals(x: Any): Iterable[String] = {
    val theType = mirror.classSymbol(x.getClass).toType
    theType.members.collect({case x if x.isTerm => x.asTerm}).filter(_.isVal).map(_.name.toString)
  }

}

I am testing on these two classes:

class Base {
  val x = 10
}

class Child extends Base {
  val y = 20
}

When calling the following code:

val x = new Child
val vs = Reflection.findVals(x)
println(vs)

The result is List(y)

For some reason, the isVal method returns false for the term corresponding to the x field from the Base class.

Can someone tell me what's the problem here? Am I doing something wrong?

kiritsuku
  • 52,967
  • 18
  • 114
  • 136
Marius Danila
  • 10,311
  • 2
  • 34
  • 37

3 Answers3

4

Per Why don't Scala case class fields reflect as public? you should use isAccessor instead of isVal.

I'm actually using isGetter and setter to properly filter vars per your comment:

  def findVals(x: Any): Iterable[String] = {
    val theType = mirror.classSymbol(x.getClass).toType
    val xtm = theType.members.collect({case x if x.isTerm => x.asTerm})
    xtm.filter(m => m.isGetter && !xtm.exists(m.setter == _)).map(_.name.toString)
  }

Results:

scala> class Base {
     | var x = 10
     | val xx = 2
     | }
defined class Base

scala> class Child extends Base {
     | val y = 3
     | }
defined class Child

scala> val x = new Child
x: Child = Child@1c0026e

scala> val vs = Reflection.findVals(x)
vs: Iterable[String] = List(y, xx)

scala> println(vs)
List(y, xx)
Community
  • 1
  • 1
sourcedelica
  • 23,940
  • 7
  • 66
  • 74
  • The problem is that this will also return vars. For instance, if `x` in `Base` becomes a var, it will also get returned, together with `x_$eq`. – Marius Danila Apr 03 '13 at 15:01
  • Yes, this will do, thank you. It does seem strange, though, that isVal doesn't return true for inherited members. It looks like a bug – Marius Danila Apr 03 '13 at 18:27
2

Using SMirror:

scala> implicit val mirror = scala.reflect.runtime.currentMirror
mirror: reflect.runtime.universe.Mirror = JavaMirror with scala.tool…

scala> import net.fwbrasil.smirror._
import net.fwbrasil.smirror._

scala> class Base {
  val x = 10
}   
defined class Base

scala> class Child extends Base {
  val y = 20
}   
defined class Child

scala> val x = new Child
x: Child = Child@448593d0

scala> x.reflect.vals
res5: List[net.fwbrasil.smirror.SInstanceVal[Child]] = List(val x: scala.Int (bound to Child@448593d0), val y: scala.Int (bound to Child@448593d0))

scala> x.reflect.vals.head.get
res7: Any = 10
0

So, this is terribly inelegant, but it seems to work:

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

object Reflection {

  val mirror = ru.runtimeMirror(this.getClass.getClassLoader)

  val ObjectClass = classOf[java.lang.Object];

  def findVals(x: Any) : Iterable[String] = findVals( x.getClass, List.empty );

  def findVals(clz: Class[_], accum : Iterable[String]): Iterable[String] = {

    clz match {
      case  ObjectClass => accum;
      case _ => {
        val theType = mirror.classSymbol(clz).toType
        val newVals = theType.members.collect({case x if x.isTerm => x.asTerm}).filter(_.isVal).map(_.name.toString)
        findVals( clz.getSuperclass, accum ++ newVals )
      }
    }
  }

}

Then...

scala> class Base {
     |   val x = 10
     |   var z = 20
     | }
defined class Base

scala> class Child extends Base {
     | val y = 20
     | var a = 9
     | }
defined class Child

scala> val x = new Child
x: Child = Child@3093266d

scala> val vs = Reflection.findVals(x)
vs: Iterable[String] = List("y ", "x ")

scala> println(vs)
List(y , x )

It seems that, at least for now, Scala reflection looks at the Java field to determine the presence of a val, so I guess you just have to climb the class hierarchy... I'm guessing it looks for the presence of a setter to distinguish val from var. Again, not so lovely, but functional.

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