Skip to content

Instantly share code, notes, and snippets.

@DeaconDesperado
Last active June 1, 2019 19:46
Show Gist options
  • Save DeaconDesperado/cbaa1c0e3e5b4f3507b0 to your computer and use it in GitHub Desktop.
Save DeaconDesperado/cbaa1c0e3e5b4f3507b0 to your computer and use it in GitHub Desktop.
import scala.reflect.macros.Context
import scala.language.experimental.macros
trait MapReader[T] {
// should really return Option[T], but let's keep it simple in this example
def read(map: Map[String, Any], key: String): T
}
object MapReader extends MapReaderLowPriorityImplicits {
// convenience method to retrieve implicit instance explicitly
def apply[T: MapReader]: MapReader[T] = implicitly[MapReader[T]]
// these implicits are always implicitly available as long as `MapReader` is in scope
implicit object IntMapReader extends MapReader[Int] {
def read(map: Map[String, Any], key: String): Int =
map(key).asInstanceOf[Int]
}
implicit object StringMapReader extends MapReader[String] {
def read(map: Map[String, Any], key: String): String =
map(key).toString
}
implicit object DoubleMapReader extends MapReader[Double] {
def read(map: Map[String, Any], key: String): Double = {
map(key).asInstanceOf[Double]
}
}
// etc. for all the basic types
implicit def caseClassMapReader[T]: MapReader[T] = macro caseClassMapReaderImpl[T]
}
// these are only attempted if none of the implicits in the companion object work
trait MapReaderLowPriorityImplicits {
def caseClassMapReaderImpl[T: c.WeakTypeTag](c: Context): c.Expr[MapReader[T]] = {
import c.universe._
def recurseParams(tpe:c.universe.Type, keys:List[String] = Nil):List[Tree] = {
val fields = tpe.decls.collectFirst{
case m:MethodSymbol if m.isPrimaryConstructor => m
}.get.paramLists.head
fields.map { field =>
val name = field.name
val decoded = name.decoded
val returnType = tpe.declaration(name).typeSignature
returnType match {
case x if x.baseClasses.contains(typeOf[Product].typeSymbol) =>
q"${x.typeSymbol.companion}(..${recurseParams(x, keys :+ decoded)})"
case x =>
val key2Get = keys.foldLeft[Tree](q"map")((prv, next) => q"$prv(${next}).asInstanceOf[Map[String, Any]]")
q"MapReader[$returnType].read($key2Get, $decoded)"
}
}
}
val tpe = weakTypeOf[T]
val companion = tpe.typeSymbol.companionSymbol
val output = q"""
new MapReader[$tpe] {
def read(map: Map[String, Any], key: String): $tpe =
$companion(..${recurseParams(tpe)})
}
"""
println(output)
c.Expr[MapReader[T]] { output }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment