How do I access default parameter values via Scala reflection?
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]())
}
You can do this without making assumptions about the generated names—by casting to the internal API:
scala> :power
** Power User mode enabled - BEEP WHIR GYVE **
** :phase has been set to 'typer'. **
** scala.tools.nsc._ has been imported **
** global._, definitions._ also imported **
** Try :help, :vals, power.<tab> **
scala> case class Foo(id: Int, name: String, note: Option[String] = None)
defined class Foo
scala> val t = typeOf[Foo.type]
t: $r.intp.global.Type = Foo.type
scala> t.declaration(nme.defaultGetterName(nme.CONSTRUCTOR, 3))
res0: $r.intp.global.Symbol = method <init>$default$3
scala> t.declaration(nme.defaultGetterName(newTermName("apply"), 3))
res1: $r.intp.global.Symbol = method apply$default$3
Of course in a way this isn't any nicer, since the name mangling is specified and the internal API isn't, but it may be more convenient.
For case when you only have Type
of Foo
:
% scala
Welcome to Scala 2.13.1 (OpenJDK 64-Bit Server VM, Java 15-loom).
scala> :paste
import scala.reflect.runtime.currentMirror
import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe.definitions._
def defaults(tpe: Type): Seq[(Int, Any)] = {
tpe.typeSymbol.asClass.primaryConstructor.asMethod.paramLists.flatten.
zipWithIndex.flatMap{ case (x, i) =>
if (x.asTerm.isParamWithDefault) {
val m = currentMirror
val im = m.reflect(
m.reflectModule(tpe.typeSymbol.asClass.companion.asModule).instance
)
val method = tpe.companion.decl(
TermName("apply$default$"+(i+1).toString)
).asMethod
val v = im.reflectMethod(method)()
Some(i -> v)
} else None
}
}
scala> case class Foo(id: Int, name: String, note: Option[String] = None)
scala> defaults(typeOf[Foo])
res0: Seq[(Int, Any)] = List((2,None))
The same way you can call any method on companion object.
Documentation about Mirrors contains example how to invoke method on class.