6
votes

I'm attempting to generate a companion object from a case class using Macros but I'm having a very hard time finding any examples of how to accomplish this.

For example:

case class Person(name: String, age: Int, id: Option[Int] = None)

If I do:

object PersonTable extends TypedTable[Person]

I want it to generate:

object PersonTable extends Table("PERSON") {
  val name = column[String]("NAME")
  val age = column[Int]("AGE")
  val id = column[Option[Int]]("ID")
}

Further, I want to be able to extend it and add additional fields as well:

object PersonTable extends TypedTable[Person] {
  val created = column[Timestamp]("TIMESTAMP")
}

And it would generate:

object PersonTable extends Table("PERSON") {
  val name = column[String]("NAME")
  val age = column[Int]("AGE")
  val id = column[Option[Int]]("ID")
  val created = column[Timestamp]("TIMESTAMP")
}

Edit

After reading about macro annotations (thanks Eugene and Mario) I created the following code:

class table[T] extends StaticAnnotation {
  def macroTransform(annottees: Any*) = macro TableGenerator.impl
}

object TableGenerator {
  def impl(c: whitebox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._

    def modifiedObject(objectDef: ModuleDef): c.Expr[Any] = {
      val ModuleDef(_, objectName, template) = objectDef
      val ret = q"""
object $objectName {
  def test() = println("Wahoo!")
}
      """
      c.Expr[Any](ret)
    }

    annottees.map(_.tree) match {
      case (objectDecl: ModuleDef) :: _ => modifiedObject(objectDecl)
      case x => c.abort(c.enclosingPosition, s"@table can only be applied to an object, not to $x")
    }
  }
}

And then attempted to use it like this:

@table[String] object MyCoolObject
MyCoolObject.test()

The first line works fine but the second line says it can't find the test method. How do I make it so that the test method is visible?

1
It's possible to do this via macro annotations: if you annotate the class, the macro will receive ASTs of both the class and the object in its arguments. In the macro, you'll be able to adjust and emit both.Eugene Burmako
As @EugeneBurmako said, Macro Annotations look to be the way to go. You might want to have a look at github.com/yetu/scala-beanutils for some inspirationMario Camou
Thanks @EugeneBurmako and Mario. Please see my edit based on your suggestion.darkfrog
I'm still hoping for a response here. I'm stuck with loss of type information on the created object.darkfrog
Can you post your entire project on github. Looks like this should be working in principle.Eugene Burmako

1 Answers

8
votes

It's unfortunately very difficult to find good examples of Macros online - especially for 2.11. I was finally able to get everything working so I wanted to provide a link to the code for anyone struggling with the same problems later.

https://github.com/outr/scalarelational/blob/master/mapper/src/main/scala/org/scalarelational/mapper/typedTable.scala

Thanks again Eugene and Mario for your great answers that led me to finding my answer.