I think that your main confusion is that you don't seem to entirely get how macros are processed.
It is entirely compile time. When your macro runs, it has access to information about the compiled program, including Symbol
s.
The macro then generates some code that will become part of your program, but that generated code itself has not annymore access to the
Symbol
s or anything else that the macro had access to.
So MsonType.apply
in your example is not going to be of any use, because the input is a Symbol
which is available in the macro only (at compile-time),
and the output is a MsonSchema
, which you need at run-time. What would make sense is to change the return type from MsonType
to c.Expr[MsonType]
(or simply c.universe.Tree
),
or in other words MsonType.apply
would now take a symbol and return a tree representing the MsonType
instance
(as opposed to returning an MsonType
instance), and your macro could then call that and include it in the final tree returned to the compiler
(which the compiler will then include in your program at the call site).
In this particular case, I think it's better to just remove MsonType.apply
altogether, and implement the conversion from Symbol
to c.universe.Tree
right in the writerImpl
macro.
This conversion is actually very easy. All you need to construct MsonType
is the field name, and a Class
instance, so there you go:
q"""org.bibble.MsonType(${f.name.decoded}, classOf[${f.typeSignature}])"""
For a field foo
of type Bar
this will generate a tree representing the following expression:
org.bibble.MsonType("foo", classOf[Bar])
So that's it, we're good to go.
You'll find a full implementation below. Note that I've taken the liberty to change the definition of your schema types in a way that is far more logical and versatile to me.
And in particular, each field now has its associated schema (given your previous question, this is clearly what you wanted in the first place).
scala> :paste
// Entering paste mode (ctrl-D to finish)
import scala.language.experimental.macros
import scala.reflect.macros._
case class MsonSchema(`type`: Class[_], fields: Seq[MsonField])
case class MsonField(name: String, schema: MsonSchema)
trait SchemaWriter[T] {
def schema: MsonSchema
}
object SchemaWriter {
def apply[T:SchemaWriter]: SchemaWriter[T] = implicitly
implicit def defaultWriter[T] = macro Macros.writerImpl[T]
}
object Macros {
def writerImpl[T: c.WeakTypeTag](c: Context): c.Expr[SchemaWriter[T]] = {
import c.universe._
c.Expr[SchemaWriter[T]](generateSchemaWriterTree(c)(weakTypeOf[T]))
}
private def generateSchemaWriterTree(c: Context)(T: c.universe.Type): c.universe.Tree = {
import c.universe._
val fields = T.declarations.collectFirst {
case m: MethodSymbol if m.isPrimaryConstructor => m
}.get.paramss.head
val MsonFieldSym = typeOf[MsonField].typeSymbol
val SchemaWriterSym = typeOf[SchemaWriter[_]].typeSymbol
val fieldTrees: Seq[Tree] = fields.map { f =>
q"""new $MsonFieldSym(
${f.name.decoded},
_root_.scala.Predef.implicitly[$SchemaWriterSym[${f.typeSignature}]].schema
)"""
}
c.resetLocalAttrs(q"""
new $SchemaWriterSym[$T] {
val schema = MsonSchema(classOf[$T], Seq(..$fieldTrees))
}
""")
}
}
// Exiting paste mode, now interpreting.
warning: there were 7 deprecation warnings; re-run with -deprecation for details
warning: there was one feature warning; re-run with -feature for details
import scala.language.experimental.macros
import scala.reflect.macros._
defined class MsonSchema
defined class MsonField
defined trait SchemaWriter
defined object SchemaWriter
defined object Macros
scala> case class Foo(ab: String, cd: Int)
defined class Foo
scala> SchemaWriter[Foo].schema
res0: MsonSchema = MsonSchema(class Foo,List(MsonField(ab,MsonSchema(class java.lang.String,List())), MsonField(cd,MsonSchema(int,List()))))