Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Scala Reflection, why do constructor params hide getters?

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.

like image 827
Imran Rashid Avatar asked Dec 19 '13 22:12

Imran Rashid


1 Answers

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?

Since they wouldn't let me fix this, that's exactly what I do. I try to give all constructor parameters new names distinct from all inherited names. Here's a typical example within the compiler.

class PackageClassSymbol protected[Symbols] (owner0: Symbol, pos0: Position, name0: TypeName)

Yes, it's ridiculous.

  • https://groups.google.com/forum/#!topic/scala-language/9jLsT_RRQR0
  • https://issues.scala-lang.org/browse/SI-3194
  • https://issues.scala-lang.org/browse/SI-4762
  • https://issues.scala-lang.org/browse/SI-6880

Oh boy, don't keep pulling that thread...

  • https://issues.scala-lang.org/browse/SI-7475
  • https://issues.scala-lang.org/browse/SI-2568
  • https://issues.scala-lang.org/browse/SI-6794

It shouldn't be hard to see that the odds of any of it being addressed are nil. It's a perfect example of why I quit.

By the way, if you use -Xlint it warns you about this. That's mentioned in SI-4762.

% cat a.scala
class A(var x: Int)
class B(x:Int, var y: Int) extends A(x) {
  def z = x
}

% scalac -Xlint a.scala
a.scala:3: warning: private[this] value x in class B shadows mutable x inherited from class A.
Changes to x will not be visible within class B - you may want to give them distinct names.
  def z = x
          ^
one warning found
like image 199
psp Avatar answered Nov 04 '22 06:11

psp