Question

I have the next code, and i would like to extract the default parametr from value.

//
def extractor[T] = macro extractorImpl[T]

def extractorImpl[T: c.WeakTypeTag](c: Context) = {
  //first i got a type contructor
  ???
}

i try with attachments but attachments.all return a Set[Any] with (for example) SymbolSourceAttachment(val name: String = "new name")

SymbolSourceAttachment contain ValDef but i do not know how to extract from SymbolSourceAttachment ValDef.

By the way i should to get a Map[String, String]("name" -> "new name")

Example:

case class Person(name: String = "new name")

object Macro {
  def extractor[T] = macro extractorImpl[T]

  def extractorImpl[T: c.WeakTypeTag](c: Context) = {
    import c.universe._

    c.weakTypeOf[T].declarations.collect {
      case a: MethodSymbol if a.isConstructor =>
        a.paramss.collect {
          case b => b.collect {
            case c =>
              c.attachments.all {
                case d => println(showRaw(d)) // => SymbolSourceAttachment(val name: String = "new name")  
              }
          }
        }
      }
   } 
}

And macro should return Map("name" -> "new name")

Was it helpful?

Solution

Since you're seeing SymbolSourceAttachment, I assume you're using macro paradise (because it's an internal attachment used only in paradise), so I'll feel free to use quasiquotes :)

There's no easy way to get values of default parameters in Scala reflection API. Your best shot would be reverse-engineering the names of methods that are created to calculate default values and then referring to those.

SymbolSourceAttachment would sort of work if your macro is expanding in the same compilation run that compiles the case class, but it would break under separate compilation (attachments aren't saved in class files), and it wouldn't work in vanilla Scala (because this attachment is exclusive to paradise).

=== Macros.scala ===

import scala.reflect.macros.Context
import scala.language.experimental.macros

object Macros {
  def impl[T](c: Context)(T: c.WeakTypeTag[T]): c.Expr[Map[String, Any]] = {
    import c.universe._
    val classSym = T.tpe.typeSymbol
    val moduleSym = classSym.companionSymbol
    val apply = moduleSym.typeSignature.declaration(newTermName("apply")).asMethod
    // can handle only default parameters from the first parameter list
    // because subsequent parameter lists might depend on previous parameters
    val kvps = apply.paramss.head.map(_.asTerm).zipWithIndex.flatMap{ case (p, i) =>
      if (!p.isParamWithDefault) None
      else {
        val getterName = newTermName("apply$default$" + (i + 1))
        Some(q"${p.name.toString} -> $moduleSym.$getterName")
      }
    }
    c.Expr[Map[String, Any]](q"Map[String, Any](..$kvps)")
  }

  def extractor[T]: Map[String, Any] = macro impl[T]
}

=== Test.scala ===

case class C(x: Int = 2, y: String, z: Boolean = true)(t: String = "hello")

object Test extends App {
  println(Macros.extractor[C])
}

17:10 ~/Projects/Paradise2103/sandbox/src/main/scala (2.10.3)$ scalac Macros.scala && scalac Test.scala && scala Test
Map(x -> 2, z -> true)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top