Scala 2.10.2 calling a "macro method" with a generic type does not work

I define the following macro to convert case fields to display

import scala.language.experimental.macros import scala.reflect.macros.Context def asMap_impl[T: c.WeakTypeTag](c: Context)(t: c.Expr[T]) = { import c.universe._ val mapApply = Select(reify(Map).tree, newTermName("apply")) val pairs = weakTypeOf[T].declarations.collect { case m: MethodSymbol if m.isCaseAccessor => val name = c.literal(m.name.decoded) val value = c.Expr(Select(t.tree, m.name)) reify(name.splice -> value.splice).tree } c.Expr[Map[String, Any]](Apply(mapApply, pairs.toList)) } 

And the implementation of the method

 def asMap[T](t: T) = macro asMap_impl[T] 

Then I define a case class to test it

 case class User(name : String) 

It works fine (with scala repl):

  scala> asMap(User("foo")) res0: scala.collection.immutable.Map[String,String] = Map(name -> foo) 

But when I wrap this method with another universal method

 def printlnMap[T](t: T) = println(asMap(t)) 

This method always prints a blank card:

 scala> printlnMap(User("foo")) Map() 

Type information seems lost, how do I get printlnMap to print all fields?

+6
source share
1 answer

The reason this does not work is because your macro will be called only once - when the printlnMap function is printlnMap . So he will see T as an abstract type. The macro will not be called every time printlnMap called.

One way to quickly fix this is to implement printlnMap just like a macro. Of course, this is not perfect. So here's another approach - materializing typeclass instances:

First, define a class that will allow us to convert instances of the case class to maps:

 trait CaseClassToMap[T] { def asMap(t: T): Map[String,Any] } 

Then we implement a macro that materializes an instance of this type for the case T class. You can put it in a CaseClassToMap companion object CaseClassToMap that it is visible globally.

 object CaseClassToMap { implicit def materializeCaseClassToMap[T]: CaseClassToMap[T] = macro impl[T] def impl[T: c.WeakTypeTag](c: Context): c.Expr[CaseClassToMap[T]] = { import c.universe._ val mapApply = Select(reify(Map).tree, newTermName("apply")) val pairs = weakTypeOf[T].declarations.collect { case m: MethodSymbol if m.isCaseAccessor => val name = c.literal(m.name.decoded) val value = c.Expr(Select(Ident(newTermName("t")), m.name)) reify(name.splice -> value.splice).tree } val mapExpr = c.Expr[Map[String, Any]](Apply(mapApply, pairs.toList)) reify { new CaseClassToMap[T] { def asMap(t: T) = mapExpr.splice } } } } 

Now you can do this:

 def asMap[T: CaseClassToMap](t: T) = implicitly[CaseClassToMap[T]].asMap(t) def printlnMap[T: CaseClassToMap](t: T) = println(asMap(t)) 
+8
source

All Articles