1

I am trying to create a UI to test a scala interface. To do so, I am extracting all the declared methods from the interface using java reflection. I am able to get the method names via

Class[MyClass].getDeclaredMethods.map(_.getName)

I am also able to extract all the parameter names and types via:

val paramNames = method.getParameters.map(_.getName)
val paramType = param.getParameterizedType.getTypeName

Now I am also trying to extract any default value of a parameter if there are any. However, I couldn't find a way to do so yet.

edit: To add more context. I am trying to extract the default parameter value from a class method:

class X {
  def getSum(x: Int, y: Int = 10): Int = x + y
}

I want to identify that the method getSum has a parameter y of type Int that has a default value of 10. I have been able to extract the values getSum, y, and Int. But so far I haven't found a way to extract the default value of 10.

Kyuubi
  • 1,228
  • 3
  • 18
  • 32
  • https://stackoverflow.com/questions/14034142/how-do-i-access-default-parameter-values-via-scala-reflection – Dmytro Mitin Sep 24 '21 at 18:27
  • that post talks about extracting default params from a case class. I am not sure if the same can be applied to extract the default params from a method. – Kyuubi Sep 24 '21 at 19:28
  • 2
    For the constructor of a (case) class (``) you have to deal with methods `$default$N`, for a method `m` you have to deal with methods `m$default$N`. – Dmytro Mitin Sep 24 '21 at 19:33
  • Also you can use `shapeless.Default` https://github.com/milessabin/shapeless/blob/main/core/src/main/scala/shapeless/default.scala#L8-L34 https://github.com/milessabin/shapeless/blob/main/core/src/test/scala/shapeless/default.scala – Dmytro Mitin Sep 24 '21 at 19:37
  • But `shapeless.Default` works for **case** classes (and case-class-like types). – Dmytro Mitin Sep 24 '21 at 19:40
  • https://stackoverflow.com/questions/68421043/type-class-derivation-accessing-default-values (Scala 3, class param) – Dmytro Mitin Sep 28 '21 at 16:49

1 Answers1

1

Using Java reflection

There is no notion of default value for method parameters in Java byte code.

So how does the Scala compiler represent default values at runtime? For each parameter with a default value the compiler adds a new method into the same class. The name of that synthesized method follows the pattern: <methodName>$default$<parameterNumber>.

In your case, the name of the method that returns the default value of y is getSum$default$2. It takes no argument and it returns 10.

In order to get the value you need to call that method on an instance of class X. If you do not have an instance of X then you cannot get the value at runtime.

That is because, in the general case, a default value can depend on some previous parameters or even on some fields of the class.

class X(default: Int) {
  def getSum(x: Int, y: Int = default): Int = x + y
}

class Y {
  def getSum(x: Int)(y: Int = x): Int = x + y
}

In the first case the body of getSum$default$2 is this.default. In the second case the getSum$default$2 method takes x as parameter and returns its value.

Using Scala Compile-time Reflection (Scala 2)

Possibly you could use scala-reflect to find the DefDef tree of the getSum$default$2 method. Then you may be able to get the body of the method.

See Scala 2 reflection documentation here.

Using Macros (Scala 3)

You can create a macro method that returns the body of the getter method of some default value.

Here I made some experiment:

package macros

import quoted._

object Check:
  inline def defaultValue[T]: Any = ${ impl[T] }

  private def impl[T](using Quotes, Type[T]): Expr[Any] =
    import quotes.reflect.*

    val tpe = TypeRepr.of[T]
    val symbol = tpe.classSymbol
      .getOrElse(report.throwError("It is not a class"))
    
    val classTree = symbol.tree.asInstanceOf[ClassDef]
    val method = classTree.body
      .collect { case m: DefDef => m }
      .find(m => m.name == "getSum$default$2")
      .getOrElse(report.throwError("Getter method not found"))
    method.rhs.get.asExprOf[Any]
@main def run(): Unit =
  println(macros.Check.defaultValue[X])

We need to use the -Yretain-trees compiler option to compile that code. It should print 10.

Adrien Piquerez
  • 1,009
  • 10
  • 11