package magnolia
import scala.reflect._, macros._
import macrocompat.bundle
import scala.util.Try
import language.existentials
import scala.collection.immutable.ListMap
case class Pos(pos: api.Position) {
override def toString = s"${pos.line}:${pos.column}"
}
case class ReentrantException() extends Exception("attempt to recurse directly")
object GlobalMutableState {
private[magnolia] var state: Map[Pos, ListMap[c.universe.Type forSome { val c: whitebox.Context }, c.universe.TermName forSome { val c: whitebox.Context }]] = Map()
private[magnolia] def push(c: whitebox.Context)(key: c.universe.Type, value: c.universe.TermName): Unit = {
println(s"push($key)")
state = state.updated(Pos(c.enclosingPosition), state.get(Pos(c.enclosingPosition)).map { m =>
m.updated(key, value)
}.getOrElse(ListMap(key -> value)))
println("state = "+state)
}
private[magnolia] def pop(c: whitebox.Context): Unit = {
println(s"pop(${state(Pos(c.enclosingPosition)).last})")
state = state.updated(Pos(c.enclosingPosition), state(Pos(c.enclosingPosition)).init)
println("state = "+state)
}
private[magnolia] def has(c: whitebox.Context)(key: c.universe.Type): Option[c.universe.TermName] =
try state(Pos(c.enclosingPosition)).get(key).asInstanceOf[Option[c.universe.TermName]] catch {
case e: Exception =>
???
}
private[magnolia] var searchType: AnyRef = null
}
@bundle
class Macros(val context: whitebox.Context) extends GenericMacro(context) {
protected def classBody(context: whitebox.Context)(genericType: context.Type, implementation: context.Tree): context.Tree = {
import context.universe._
q"""def extract(src: _root_.magnolia.Thing): $genericType = $implementation"""
}
protected def dereferenceValue(context: whitebox.Context)(value: context.Tree, elem: String): context.Tree = {
import context.universe._
q"$value.access($elem)"
}
protected def callDelegateMethod(context: whitebox.Context)(value: context.Tree, argument: context.Tree): context.Tree = {
import context.universe._
q"$value.extract($argument)"
}
protected def coproductReduction(context: whitebox.Context)(left: context.Tree, right: context.Tree): context.Tree = {
import context.universe._
q"$left.orElse($right)"
}
}
abstract class GenericMacro(whiteboxContext: whitebox.Context) {
val c = whiteboxContext
def getImplicit(genericType: c.universe.Type,
typeConstructor: c.universe.Type,
myName: c.universe.TermName,
count: Int): c.Tree = {
import c.universe._
println(s"getImplicit1($genericType, $count)")
val x = GlobalMutableState.has(c)(genericType)
x.foreach { y => println("y = "+y) }
val result = x.map { nm =>
q"$nm"
}.orElse {
val searchType = appliedType(typeConstructor, genericType)
if(GlobalMutableState.has(c)(genericType).isEmpty) {
GlobalMutableState.searchType = genericType
val inferredImplicit = try Some(c.inferImplicitValue(searchType, false, false)) catch {
case e: Exception => None
}
object transformer extends Transformer {
override def transform(tree: Tree): Tree = tree match {
case ta@TypeApply(Select(Literal(Constant(method: String)), TermName("asInstanceOf")), List(tpe)) =>
println(s"Found typeapply: ${tpe}")
val m = TermName(method)
q"$m"
case _ => super.transform(tree)
}
}
inferredImplicit.map { imp =>
c.resetLocalAttrs(imp)
transformer.transform(imp)
c.typecheck(imp)
}.orElse {
directInferImplicit(genericType, typeConstructor, count + 1)
}
} else {
directInferImplicit(genericType, typeConstructor, count + 1)
}
}.getOrElse {
println("Really failed to find extractor for type "+genericType)
c.abort(c.enclosingPosition, "Could not find extractor for type "+genericType)
}
//println(" = "+result)
result
}
def directInferImplicit(genericType: c.universe.Type,
typeConstructor: c.universe.Type,
count: Int): Option[c.Tree] = {
import c.universe._
println(s"directInferImplicit($genericType, $count)")
val myName: TermName = TermName(c.freshName(genericType.typeSymbol.name.encodedName.toString.toLowerCase+"Extractor"))
val typeSymbol = genericType.typeSymbol
val classType = if(typeSymbol.isClass) Some(typeSymbol.asClass) else None
val isCaseClass = classType.map(_.isCaseClass).getOrElse(false)
val isSealedTrait = classType.map(_.isSealed).getOrElse(false)
val isAnyVal = genericType <:< typeOf[AnyVal]
val resultType = appliedType(typeConstructor, genericType)
val construct = if(isCaseClass) {
val implicits = genericType.decls.collect {
case m: MethodSymbol if m.isCaseAccessor => m.asMethod
}.map { param =>
val returnType = param.returnType
GlobalMutableState.push(c)(genericType, myName)
val imp = getImplicit(returnType, typeConstructor, myName, count)
GlobalMutableState.pop(c)
val dereferenced = dereferenceValue(c)(q"src", param.name.toString)
callDelegateMethod(c)(imp, dereferenced)
}
Some(q"new $genericType(..$implicits)")
} else if(isSealedTrait) {
val subtypes = classType.get.knownDirectSubclasses.to[List]
Some(subtypes.map(_.asType.toType).map { searchType =>
GlobalMutableState.push(c)(genericType, myName)
val res = getImplicit(searchType, typeConstructor, myName, count)
GlobalMutableState.pop(c)
res
}.reduce(coproductReduction(c))).map { imp =>
callDelegateMethod(c)(imp, q"src")
}
} else None
val result = construct.map { const =>
val methodImplementation = classBody(c)(genericType, const)
q"""{
def $myName: $resultType = new $resultType {
$methodImplementation
}
$myName
}"""
}
result
}
protected def classBody(c: whitebox.Context)(genericType: c.Type, implementation: c.Tree): c.Tree
protected def coproductReduction(c: whitebox.Context)(left: c.Tree, right: c.Tree): c.Tree
protected def dereferenceValue(c: whitebox.Context)(value: c.Tree, elem: String): c.Tree
protected def callDelegateMethod(c: whitebox.Context)(value: c.Tree, argument: c.Tree): c.Tree
def generic[T: c.WeakTypeTag, Tc: c.WeakTypeTag]: c.Tree = try {
import c.universe._
println("Entering generic for type "+weakTypeOf[T])
val genericType: Type = weakTypeOf[T]
val reentrant = genericType == GlobalMutableState.searchType
println(s"LAST TYPE = ${GlobalMutableState.searchType}; THIS TYPE = $genericType")
val result: Option[c.Tree] = if(reentrant) {
println("Reentrant.")
throw ReentrantException()
} else if(GlobalMutableState.searchType != null) {
GlobalMutableState.has(c)(genericType) match {
case None =>
val typeConstructor: Type = weakTypeOf[Tc].typeConstructor
directInferImplicit(genericType, typeConstructor, 0)
case Some(t) =>
val str = t.toString
val typeConstructor: Type = weakTypeOf[Tc].typeConstructor
val searchType = appliedType(typeConstructor, genericType)
Some(q"$str.asInstanceOf[${searchType}]")
}
} else {
val typeConstructor: Type = weakTypeOf[Tc].typeConstructor
directInferImplicit(genericType, typeConstructor, 0)
}
println(result)
try result.map { tree => c.typecheck(tree) } catch {
case e: Exception =>
println(result)
println("Failed to typecheck because: "+e)
}
result.getOrElse {
println("FAIL.")
c.abort(c.enclosingPosition, "Could not infer extractor. Sorry.")
}
} catch {
case e@ReentrantException() => throw e
case e: Exception =>
println("Oh no, there was a problem: "+e)
e.printStackTrace()
???
}
}