0

I'm trying to use the fields of a type in a macro, and pass the symbol or typesigs for the fields to a method so I can do some operations that require concrete information about the type.

I have code like this (played about with variations):

object Macros {

  import scala.reflect.runtime.universe._

  def foo(t: Symbol) : String = t.name.decoded

  def materializeWriterImpl[T: c.WeakTypeTag](c: Context): c.Expr[List[String]] = {
    import c.universe._

    val tpe = weakTypeOf[T]

    val fields = tpe.declarations.collectFirst {
      case m: MethodSymbol if m.isPrimaryConstructor => m
    }.get.paramss.head

    c.Expr[List[String]] { q"""
      val q = $fields
      val names = q.map(Macros.foo)
      List(names)
    """
    }
  }
}

The error I get is

Error:(53, 24) Can't unquote List[c.universe.Symbol], consider using .. or providing an implicit instance of Liftable[List[c.universe.Symbol]] val names = foo($fields) ^

So, perhaps its not possible to lift symbol / type using qquotes. But I can see methods to do this in StandardLiftableApi:

implicit def liftScalaSymbol : U#Liftable[scala.Symbol]
implicit def liftType[T <: U#Type] : U#Liftable[T]

I can get this working if I pass strings, just as a test, but I really need something more substantial passed out.

sksamuel
  • 16,154
  • 8
  • 60
  • 108
  • At first blush I'd say all you want is q"List(..${fields.map(Macros.foo)})". Your main conceptual mistake here is that you are trying to generate code that maps over the symbols, as opposed to having your generating code map over the symbols and then lift the result. As a side note, `liftScalaSymbol` is for lifting a `scala.Symbol`, not a `Universe.Symbol`. – Régis Jean-Gilles Sep 17 '15 at 07:08
  • Also, as the compiler is hinting in the error message, you need to prepend `$` with two dots (`..`) when you want to interpolate a list. Refer to the quasiquotes documentation. – Régis Jean-Gilles Sep 17 '15 at 07:14
  • Yeah I'm (at a basic level) understand ".." already. Your first comment is really interesting, I tried to generate my output as quasiquotes and then include the List of qqs but that didn't work either (same kind of Liftable error). Is it usual to write your won implementation of Liftable? – sksamuel Sep 17 '15 at 09:50
  • But have you tried just returning `c.Expr[List[String]](q"List(..${fields.map(Macros.foo)})")`? Does it work? If not what's the precise error? Or if you need to do more than just get the field names, what is it exactly? – Régis Jean-Gilles Sep 17 '15 at 10:18
  • I want to recursively generate a schema, so for that I need to get the field name, and some type information. So ideally I want to pass about a symbol, or a name + typesig. – sksamuel Sep 17 '15 at 10:25
  • But can't you generate (recursively) the schema entirely in your macro? Why do you need to pass type information at runtime? For what it's worth, here's what I would do: define a type class to generate a schema for a given type, and have your macro generate a schema for a given type by recursively infering the correct type class instance for each field. The only place where you need the type then is when infering the right type class instance, and this is done by normal implicit lookup so you don't need to pass any type information at runtime. – Régis Jean-Gilles Sep 17 '15 at 10:31
  • I don't want to pass type information at runtime, and I'm trying to do what you are suggesting. I'm just not explaining myself very well :) – sksamuel Sep 17 '15 at 10:36
  • I need the macro generate a typeclass instance, so lets say my type class is Writer[T] then I need the macro to generate this, for T, by taking the fields of T and writing out (lets say its like json schema for ease, "field : type" or something like that), but if type is a class itself I need to recursive, as the this "json schema" will need to be generated for the nested object and so on. I hope that makes more sense. – sksamuel Sep 17 '15 at 10:38
  • Yes it totally makes sense, I actually wrote such code already. You should add that `Writer` class to your example code to reflect what you're actually trying to achieve. – Régis Jean-Gilles Sep 17 '15 at 10:43
  • Ok I will, I'll try an updated implementation tonight, but it'll have pseudo code element as I have no idea how to get the symbol passed into my qq generating functions (hence the question :)) – sksamuel Sep 17 '15 at 10:50

1 Answers1

1

Following your comments about what you're actually trying to achieve, here's a rather comprehensive example that shows how to generate schemas (for a totally made up schema description language).

import scala.language.experimental.macros
import scala.reflect.macros._

implicit class Schema[T](val value: String) extends AnyVal {
  override def toString = value
}; object Schema {
  implicit val intSchema: Schema[Int] = "int"
  implicit val floatSchema: Schema[Float] = "float"
  implicit val stringSchema: Schema[String] = "string"
  // ... and so on for all the base types you need to support

  implicit def optionSchema[T:Schema]: Schema[Option[T]] = "optional[" + implicitly[Schema[T]] + "]"
  implicit def listSchema[T:Schema]: Schema[List[T]] = "list_of[" + implicitly[Schema[T]] + "]"
  implicit def classSchema[T]: Schema[T] = macro classSchema_impl[T]

  def classSchema_impl[T:c.WeakTypeTag](c: Context): c.Expr[Schema[T]] = {
    import c.universe._
    val T = weakTypeOf[T]
    val fields = T.declarations.collectFirst {
      case m: MethodSymbol if m.isPrimaryConstructor => m
    }.get.paramss.head
    val fieldSchemaPartTrees: Seq[Tree] = fields.map{ f =>
      q"""${f.name.decoded} + ": " + implicitly[Schema[${f.typeSignature}]]"""
    }
    c.Expr[Schema[T]](q"""
      new Schema[$T](
        "{" +
        Seq(..$fieldSchemaPartTrees).mkString(", ") +
        "}"
      )
    """)
  }
}

And some REPL test:

scala> case class Foo(ab: Option[String], cd: Int)
defined class Foo

scala> case class Bar(ef: List[Foo], gh: Float)
defined class Bar

scala> implicitly[Schema[Bar]]
res7: Schema[Bar] = {ef: list_of[{ab: optional[string], cd: int}], gh: float}
Régis Jean-Gilles
  • 32,541
  • 5
  • 83
  • 97