3

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")

2
  • I'm not sure I understand what the macro is supposed to achieve. Could you elaborate, e.g. by providing example input and output? Commented Feb 22, 2014 at 22:41
  • @EugeneBurmako update with example Commented Feb 23, 2014 at 6:49

1 Answer 1

9

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)
Sign up to request clarification or add additional context in comments.

2 Comments

Note that q"foo -> bar" is not hygienic unlike q"(foo, bar)" at the moment.
Doesn't work if you explicitly define the companion object, and try to access apply$default$i from inside it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.