vendredi 25 mars 2016

scala macro reify local variable

I'm trying to write a simple macro to pull public fields from a class, along with their 'schema' (for Spark SQL). Now, a schema is simply a string name, and a spark sql DataType (StringType, BooleanType for example). I'm pattern matching to convert from a Scala type to a spark sql type. BUT, I can't seem to get the local variable (dt, a spark sql DataType) into my tree.

Weird thing is, if I replace dt with the literal StringType, it works perfectly. If it can do that, I'm not sure why it can't take it from a variable. Even doing val dt = StringType doesn't work.

I feel like there is some way of doing something like c.Expr[DataType](Select(<local variable>).splice() but I can't figure it out.

import scala.language.experimental.macros
import scala.reflect.macros.whitebox
import org.apache.spark.sql.types.{DataType, StringType, BooleanType}

object DataExtractor {
  case class SomeField(name: StructField, value: Any)
  type SomeData = Seq[SomeField]

  implicit class DataExtraction[M <: DataExtractor](val model: M) extends AnyVal {
    def allData: SomeData = macro Macros.impl[M]
  }

  private object Macros {
    def impl[T: c.WeakTypeTag](c: whitebox.Context): c.Expr[SomeData] = {
      import c.universe._

      val applier = Select(reify(Seq).tree, TermName("apply"))
      val model = Select(c.prefix.tree, TermName("model"))

      val terms = weakTypeOf[T].members.collect {
        case m: MethodSymbol if m.isGetter && m.isPublic => m
      }.map( t => {
        val name = t.name.decodedName.toString
        val value = c.Expr(Select(model, t.name))
        val sig: Type = t.typeSignature.resultType

        val dt: DataType = sig match {
          case a if sig =:= typeOf[String] => StringType
          case e if sig =:= typeOf[Boolean] => BooleanType
          case _ => throw new Exception(s"Failed to match a ${sig.toString}")
        }

        reify(ClaimField(
          StructField(c.Expr[String](Literal(Constant(name))).splice, dt),
          value.splice
        )).tree
      })
      c.Expr[SomeData](Apply(applier, terms.toList))
    }
  }
}





Aucun commentaire:

Enregistrer un commentaire