In Scala Reflection, why do constructor params hide getters?

Viewed 694

vars in a scala class automatically get getters & setters you can see through scala reflection via members

import scala.reflect.runtime.{universe => ru}
class A(var x: Int)

scala> ru.typeOf[A].members.filter{_.name.toString.contains("x")}
res22: Iterable[reflect.runtime.universe.Symbol] = SynchronizedOps(variable x, method x_=, method x)

However, if you create a subclass, which re-uses the var name in the constructor, the getter is gone:

class B(x:Int, var y: Int) extends A(x)
scala> ru.typeOf[B].members.filter{_.name.toString.contains("x")}
res23: Iterable[reflect.runtime.universe.Symbol] = SynchronizedOps(value x, method x_=)
scala> res23.head.asTerm.isVal
res25: Boolean = true

This seems a little misleading ... after all, B still does have a getter for x (and its not a val)

scala> val b = new B(5,6)
b: B = B@270288ed

scala> b.x
res26: Int = 5

scala> b.x = 7
b.x: Int = 7

scala> b.x
res27: Int = 7

If I try to pretend that the value x I got from members is a getter, I get an error:

scala> val xGetter = res23.head.asTerm
xGetter: reflect.runtime.universe.TermSymbol = value x

scala> val objMirror = ru.runtimeMirror(getClass.getClassLoader).reflect(b)
objMirror: reflect.runtime.universe.InstanceMirror = instance mirror for B@270288ed

scala> val getterMirror = objMirror.reflectField(xGetter)
scala.ScalaReflectionException: Scala field x isn't represented as a Java field, neither it has a Java accessor method
note that private parameters of class constructors don't get mapped onto fields and/or accessors,
unless they are used outside of their declaring constructors.

What is the right workaround here? Is it completely wrong to have a subclass name its constructor args the same as the names in the parent args? Or instead of calling members, do I need to work my up all super-classes to get all getters & setters?

Note that members gives me the inherited getter as long as the subclass doesn't create a constructor w/ the same name:

class Y(var x: Int)
class Z(q:Int, z: Int) extends Y(q)
scala> ru.typeOf[Z].members.filter{_.name.toString.contains("x")}
res28: Iterable[reflect.runtime.universe.Symbol] = SynchronizedOps(method x_=, method x)

EDIT In case its unclear, I'm really asking: 1) is this a bug in scala reflection? 2) if not, should I: (a) never have classes use names of constructor fields be the same as the name of fields in base classes? (if so, I'm probably defining all my classes wrong ...) or (b) to get all getters & setters, should I just go through the list of all parent classes and use declarations, rather than relying on members to do the right thing, since it doesn't work in this one case?

EDIT 2 in response to @som-snytt's answer, the visible methods of x are really on x in A, not the param in the constructor to B. Eg.:

class A(var x: Int){def showMeX {println(x)}}
class B(x:Int, var y: Int) extends A(x)
scala> val b = new B(5,10)
scala> b.showMeX
5
scala> b.x = 17
b.x: Int = 17
scala> b.showMeX
17

so I don't really think that the either the getter or setter for x has been shadowed, from the perspective of normal user code. Its only been shadowed for reflection code ... and it doesn't make sense to me that there would be two different versions of shadowing.

3 Answers
Related