aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/scala/generic.scala
diff options
context:
space:
mode:
authorJon Pretty <jon.pretty@propensive.com>2017-05-29 19:05:19 -0600
committerJon Pretty <jon.pretty@propensive.com>2017-05-29 19:05:19 -0600
commita5e3bd7b949ba9b2997b1cbcca5365c1f97e2487 (patch)
treeeaf85686122cbd6d5f32eea47c5adf382e00469b /core/src/main/scala/generic.scala
parent17cfdb350fa38454a76ed5370ac6f36c3a532d3e (diff)
downloadmagnolia-a5e3bd7b949ba9b2997b1cbcca5365c1f97e2487.tar.gz
magnolia-a5e3bd7b949ba9b2997b1cbcca5365c1f97e2487.tar.bz2
magnolia-a5e3bd7b949ba9b2997b1cbcca5365c1f97e2487.zip
Pretty close to having the generic macro working
And yet so far.
Diffstat (limited to 'core/src/main/scala/generic.scala')
-rw-r--r--core/src/main/scala/generic.scala189
1 files changed, 150 insertions, 39 deletions
diff --git a/core/src/main/scala/generic.scala b/core/src/main/scala/generic.scala
index c045cbc..4519290 100644
--- a/core/src/main/scala/generic.scala
+++ b/core/src/main/scala/generic.scala
@@ -2,59 +2,126 @@ 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: ListMap[AnyRef, AnyRef] = ListMap()
- private[magnolia] def push(key: AnyRef, value: AnyRef): Unit = state += ((key, value))
- private[magnolia] def pop(): Unit = state = state.init
+ 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 has(c: whitebox.Context)(key: AnyRef): Option[c.universe.TermName] =
- state.get(key).asInstanceOf[Option[c.universe.TermName]]
+ 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 c: whitebox.Context) {
+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) {
- def dereference(path: c.Tree, elem: String): c.Tree = path
+ val c = whiteboxContext
def getImplicit(genericType: c.universe.Type,
typeConstructor: c.universe.Type,
- myName: c.universe.TermName): c.Tree = {
+ myName: c.universe.TermName,
+ count: Int): c.Tree = {
import c.universe._
-
- GlobalMutableState.push(genericType, myName)
-
- val result = GlobalMutableState.has(c)(genericType).map { nm => q"$nm" }.orElse {
+ 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) {
- val inferredImplicit =
- try Some(c.inferImplicitValue(searchType, false, false)) catch {
- case e: Exception => None
+ 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.orElse {
- directInferImplicit(genericType, typeConstructor)
+ }
+
+ inferredImplicit.map { imp =>
+ transformer.transform(imp)
+ }.orElse {
+ directInferImplicit(genericType, typeConstructor, count + 1)
}
} else {
- directInferImplicit(genericType, typeConstructor)
+ 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)
}
- GlobalMutableState.pop()
+ //println(" = "+result)
result
}
def directInferImplicit(genericType: c.universe.Type,
- typeConstructor: c.universe.Type): Option[c.Tree] = {
+ typeConstructor: c.universe.Type,
+ count: Int): Option[c.Tree] = {
import c.universe._
- val myName = TermName(c.freshName("extractor$"))
+ 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)
@@ -66,49 +133,93 @@ class Macros(val c: whitebox.Context) {
val construct = if(isCaseClass) {
val implicits = genericType.decls.collect {
case m: MethodSymbol if m.isCaseAccessor => m.asMethod
- }.map { p =>
- val ret = p.returnType
- val imp = getImplicit(ret, typeConstructor, myName)
- q"$imp.extract(src)"
+ }.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(t => getImplicit(t, typeConstructor, myName)).foldLeft(q"null": c.Tree) { (a, b) =>
- q"(try { $b.extract(src) } catch { case e: _root_.java.lang.Exception => $a })"
- })
+ 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 { c =>
+ val result = construct.map { const =>
+
+ val methodImplementation = classBody(c)(genericType, const)
q"""{
def $myName: $resultType = new $resultType {
- def extract(src: _root_.java.lang.String): $genericType = $c
+ $methodImplementation
}
$myName
}"""
}
- //GlobalMutableState.pop()
-
- println(result)
result
}
- def generic[T: c.WeakTypeTag, Tc: c.WeakTypeTag]: c.Tree = {
+ 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._
- val genericType: Type = weakTypeOf[T]
- val typeConstructor: Type = weakTypeOf[Tc].typeConstructor
+ println("Entering generic for type "+weakTypeOf[T])
- val result = directInferImplicit(genericType, typeConstructor)
+ 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()
+ ???
}
}