Search code examples
scalareflectionscala-macrosscala-reflect

Get constructor parameter values using scala reflection


Working in a codebase with scala that wants you, for certain classes, to define a sort of "make a new version" - so for instance if you have a class x(a :int, b:String, c:double)... it would have a function like this:

class x( a: Integer, b : String, c : Double) extends CanMakeNew
{
    def newValue() = x( a, b, c)
}

I have no control over that - but would prefer not to implement it every time. Or, well... ever. Is there a way in scala, with reflection - to iterate over the constructor parameter values? I can use reflection to look at the parameter types - but as parameternames has not been turned on for this module, and I can't turn it on - I can't correlate those with the stored values in the class. Fundamentally, I'm looking for anyway of implementing a trait like:

trait CanMakeNewDoneForMe extends CanMakeNew {
    def newValue()  {I need the code that goes here}

So does scala reflection have any way of either inspecting the constructor or inspecting the object and seeing "ahh, this was the third parameter in the constructor"?


Solution

  • If you make X a case class it will have apply, copy... generated by compiler automatically.

    And basically it’s not my codebase, so I can’t really change any of the shape of things...

    When you make a class a case class you don't actually "change shape of things", you just add autogenerated methods.

    Anyway, you can create a macro annotation that generates method newValue.

      import scala.annotation.StaticAnnotation
      import scala.language.experimental.macros
      import scala.reflect.macros.blackbox
    
      class newValue extends StaticAnnotation {
        def macroTransform(annottees: Any*): Any = macro newValueMacro.impl
      }
    
      object newValueMacro {
        def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
          import c.universe._
          annottees match {
            case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: tail =>
              val tparams1 = tparams.map {
                case q"$_ type $name[..$_] >: $_ <: $_" => tq"$name"
              }
              val paramss1 = paramss.map(_.map {
                case q"$_ val $pat: $_ = $_" => pat
              })
              q"""
                  $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self =>
                    def newValue() = new $tpname[..$tparams1](...$paramss1)
                    ..$stats
                  }
    
                  ..$tail
                """
    
            case _ => c.abort(c.enclosingPosition, "not a class")
          }
    
        }
      }
    
      @newValue
      /*case*/ class X(a: Int, b : String, c : Double) {
        override def toString: String = s"X($a, $b, $c)"
      }
    
      val x = new X(1, "a", 2.0) //X(1, a, 2.0)
      //  val x1 = x.copy()
      val x1 = x.newValue() //X(1, a, 2.0)