6

I've always understood the constructor arguments of case classes as defining public vals.

However, when I reflect the fields, the isPublic method comes up false. Any ideas why?

scala> class Test( val name : String, val num : Int )
defined class Test

scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._

scala> val tpe = typeOf[Test]
tpe: reflect.runtime.universe.Type = Test

scala> def checkValVisibility( t : Type ) = {
     | t.members
     |   .filter( _.isTerm )
     |   .map( _.asTerm )
     |   .filter( _.isVal )
     |   .map( memb => "Val " + memb.name.toString.trim + " is public? " + memb.isPublic )
     |   .mkString("\n")
     | }
checkValVisibility: (t: reflect.runtime.universe.Type)String

scala> checkValVisibility( tpe )
res2: String = 
Val num is public? false
Val name is public? false
Steve Waldman
  • 13,689
  • 1
  • 35
  • 45
  • 1
    Doesn't Scala autogenerate public accessor methods around private instance variables? I'm not sure that implementation detail should get revealed by reflection, but perhaps looking in the methods would get you further. – copumpkin Mar 31 '13 at 15:51

1 Answers1

12

The reason is that the actual values that you queried for num and name are indeed private. For case classes (and classes with public class parameters), class parameters are implemented as private fields with public accessors (which are generated automatically for you).

If you want to use Scala Reflection to obtain a symbol representing the public accessor for a given field, you can simply do:

scala> tpe.member("name": TermName)
res02: reflect.runtime.universe.Symbol = value name

You can see that it's a public accessor if you do:

scala> tpe.member("name": TermName).isPublic
res03: Boolean = true

scala> tpe.member("name": TermName).isMethod
res04: Boolean = true

In your case, you filtered out the accessors, leaving only the actual (private) fields. You can change your code from above to achieve what you want by instead checking with isAccessor (or isGetter) instead of isVal.

scala> def checkValVisibility( t : Type ) = {
     |   t.members
     |    .filter( _.isTerm )
     |    .map( _.asTerm )
     |    .filter( _.isAccessor )
     |    .map( memb => "Val " + memb.name.toString.trim + " is public? " + memb.isPublic )
     |    .mkString("\n")
     | }
checkValVisibility: (t: reflect.runtime.universe.Type)String

scala> checkValVisibility(tpe)
res05: String = 
Val num is public? true
Val name is public? true
Heather Miller
  • 3,901
  • 1
  • 23
  • 19
  • One thing that Eugene Burmako told me yesterday is that Scala reflection is trying to save users from being aware of "implementation details" of this nature. It makes sense to me that it's implemented this way, but is this the correct level of abstraction for Scala reflection to be dealing with? Although perhaps Steve Waldman's code was trying to be too specific, and the easy accessors you propose at the beginning of your answer are doing exactly what I propose... – copumpkin Mar 31 '13 at 21:54
  • 1
    Playing around, is it fair to conclude that vals are NEVER public, only their autogenerated accessors may be? Even in non-case classes, pub vals reflect as nonpublic while accessors reflects as public. This gets at copumpkin's question: Am reflecting on an implementation detail? on val as pvt Java field + automatically generated accessor, not native Scala construct? I'm glad to live w/notion that reflected "vals" are underlying fields, that visibility & access should hang off accessor. But that might be better documented. Thank you again for work, and for the great response! – Steve Waldman Mar 31 '13 at 23:34