scalareflectionscala-2.10

How do I access default parameter values via Scala reflection?


Let's say a I have a class:

case class Foo(id: Int, name: String, note: Option[String] = None)

Both the constructor and the apply method in the automatically generated companion object take three parameters. When viewed via reflection, the third parameter (note) is flagged:

p.isParamWithDefault = true

Also, by inspection I can find the method that produces the value in the companion object:

method <init>$default$3

and

method apply$default$3

Which both also have:

m.isParamWithDefault = true

However, I can neither find anything on the TermSymbol for the notes parameter that actually points me at the right methods to obtain the default value nor anything on the above MethodSymbols that point back to the TermSymbol for the parameter.

Is there a straight forward way to link TermSymbol for the parameter with the method that generates its default value? Or do I need to do something kludgey like inspect the names of the methods on the companion object?

I'm interested in this both for the case class constructor example I have here and for regular methods.


Solution

  • There are degrees of kludge.

    Sample code at this answer, pasted below.

    So as I was saying, the form of the name is in the spec at 4.6, 6.6.1. That is not ad-hoc. For every parameter pi , j with a default argument a method named f $default$n is generated which computes the default argument expression.

    The lack of structured ability to access and reconstitute these generated names is a known issue (with a current thread on the ML).

    import reflect._
    import scala.reflect.runtime.{ currentMirror => cm }
    import scala.reflect.runtime.universe._
    
    // case class instance with default args
    
    // Persons entering this site must be 18 or older, so assume that
    case class Person(name: String, age: Int = 18) {
      require(age >= 18)
    }
    
    object Test extends App {
    
      // Person may have some default args, or not.
      // normally, must Person(name = "Guy")
      // we will Person(null, 18)
      def newCase[A]()(implicit t: ClassTag[A]): A = {
        val claas = cm classSymbol t.runtimeClass
        val modul = claas.companionSymbol.asModule
        val im = cm reflect (cm reflectModule modul).instance
        defaut[A](im, "apply")
      }
    
      def defaut[A](im: InstanceMirror, name: String): A = {
        val at = newTermName(name)
        val ts = im.symbol.typeSignature
        val method = (ts member at).asMethod
    
        // either defarg or default val for type of p
        def valueFor(p: Symbol, i: Int): Any = {
          val defarg = ts member newTermName(s"$name$$default$$${i+1}")
          if (defarg != NoSymbol) {
            println(s"default $defarg")
            (im reflectMethod defarg.asMethod)()
          } else {
            println(s"def val for $p")
            p.typeSignature match {
              case t if t =:= typeOf[String] => null
              case t if t =:= typeOf[Int]    => 0
              case x                         => throw new IllegalArgumentException(x.toString)
            }
          }
        }
        val args = (for (ps <- method.paramss; p <- ps) yield p).zipWithIndex map (p => valueFor(p._1,p._2))
        (im reflectMethod method)(args: _*).asInstanceOf[A]
      }
    
      assert(Person(name = null) == newCase[Person]())
    }