I'm a newcomer to scala-macros. I'm writing an automated JSON writer/reader for the InfluxDB client.
The reader looks like:
trait InfluxReader[T] {
def read(js: JsArray): T
}
InfluxFormatter:
object InfluxFormatter {
/**
* Generate InfluxReader for type ${A}
*/
def reader[A]: InfluxReader[A] = macro InfluxFormatterImpl.reader_impl[A]
}
InfluxFormatterImpl:
private[macros] object InfluxFormatterImpl {
/***
* Generate AST for current type at compile time.
* @tparam T - Type parameter for whom will be generated AST
*/
def reader_impl[T: c.WeakTypeTag](c: blackbox.Context): c.universe.Tree = {
import c.universe._
val tpe = c.weakTypeOf[T]
val methods = tpe.decls.toList collect {
case m: MethodSymbol if m.isCaseAccessor =>
m.name.decodedName.toString -> m.returnType.dealias
}
if (methods.lengthCompare(1) < 0) {
c.abort(c.enclosingPosition, "Type parameter must be a case class with more then 1 fields")
}
val bool = typeOf[Boolean].dealias
val int = typeOf[Int].dealias
val long = typeOf[Long].dealias
val double = typeOf[Double].dealias
val string = typeOf[String].dealias
val params = methods
.map(_._1)
.sorted
.map(v => TermName(v))
.map(v => q"$v = $v")
val jsParams = methods
.sortBy(_._1) // influx return results in alphabetical order
.map { case (k, v) => TermName(k) -> v }
.map {
case (name, `bool`) => q"JsBoolean($name)"
case (name, `string`) => q"JsString($name)"
case (name, `int`) => q"JsNumber($name)"
case (name, `long`) => q"JsNumber($name)"
case (name, `double`) => q"JsNumber($name)"
case (_, other) => c.abort(c.enclosingPosition, s"Unknown type $other")
}
val failureMsg = s"Can't deserialize $tpe object"
val result = q"""
new InfluxReader[$tpe] {
def read(js: JsArray): $tpe = js.elements.tail match {
case Vector(..$jsParams) => new $tpe(..$params)
case _ => throw DeserializationException($failureMsg)
}
}"""
result
}
}
Test Spec:
import com.github.fsanaulla.core.model._
import com.github.fsanaulla.core.utils._
import com.github.fsanaulla.macros.InfluxFormatter
import org.scalatest.{FlatSpec, Matchers}
import spray.json._
class MacroReaderSpec extends FlatSpec with Matchers {
"Macros" should "generate reader" in {
case class Test(name: String, age: Int)
val rd: InfluxReader[Test] = InfluxFormatter.reader[Test]
rd.read(JsArray(JsNumber(234324), JsNumber(4), JsString("Fz"))) shouldEqual Test("Fz", 4)
}
}
Compilation of MacroReaderSpec
fails with a compilation error:
Error:(14, 36) not found: value age
val rd = InfluxFormatter.reader[Test]
Error:(14, 36) not found: value name
val rd = InfluxFormatter.reader[Test]
With compiler options "-Ymacro-debug-lite"
, it looks like:
Warning:scalac: {
final class $anon extends InfluxReader[Test] {
def <init>() = {
super.<init>();
()
};
def read(js: JsArray): Test = js.elements.tail match {
case Vector(JsNumber(age), JsString(name)) => new Test(age = age, name = name)
case _ => throw DeserializationException("Can\'t deserialize Test object")
}
};
new $anon()
}
Error appears in this line of code at pattern part:
case Vector(JsNumber(age), JsString(name)) => new Test(age = age, name = name)
Why it can't find it? I think it can't recognize it as an expression for pattern matching and can't call extractor. If yes, how to change it? Maybe it somehow calls it under the score? I'm using scala 2.12.4. Sbt 1.1.0. Source code can be found here
Thanks, everyone.