How do I access default parameter values via Scala reflection?

前端 未结 3 984
不思量自难忘°
不思量自难忘° 2020-12-03 01:40

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 i

相关标签:
3条回答
  • 2020-12-03 02:10

    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]())
    }
    
    0 讨论(0)
  • 2020-12-03 02:14

    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.

    0 讨论(0)
  • 2020-12-03 02:15

    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.

    0 讨论(0)
提交回复
热议问题