Last active
June 1, 2019 19:46
-
-
Save DeaconDesperado/cbaa1c0e3e5b4f3507b0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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