aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Pretty <jon.pretty@propensive.com>2017-06-06 23:32:34 +0200
committerJon Pretty <jon.pretty@propensive.com>2017-06-06 23:32:34 +0200
commitd72c7223282f3d64fb1df3e647c1c2a75b8c1bb5 (patch)
treefc0bebab2eee223d6a7505e2a4ebe2ccc11ec354
parent0ca7b31dcd258cedda79dee1f42459454b5685b1 (diff)
downloadmagnolia-d72c7223282f3d64fb1df3e647c1c2a75b8c1bb5.tar.gz
magnolia-d72c7223282f3d64fb1df3e647c1c2a75b8c1bb5.tar.bz2
magnolia-d72c7223282f3d64fb1df3e647c1c2a75b8c1bb5.zip
Generic macro appears to work more universally
-rw-r--r--core/src/main/scala/generic.scala191
-rw-r--r--tests/shared/src/main/scala/magnolia/main.scala8
2 files changed, 114 insertions, 85 deletions
diff --git a/core/src/main/scala/generic.scala b/core/src/main/scala/generic.scala
index ffb2c14..f8ecc21 100644
--- a/core/src/main/scala/generic.scala
+++ b/core/src/main/scala/generic.scala
@@ -7,37 +7,54 @@ import language.existentials
import scala.collection.immutable.ListMap
+import GlobalMutableState.log
+
case class Pos(pos: api.Position) {
override def toString = s"${pos.line}:${pos.column}"
}
-case class ReentrantException() extends Exception("attempt to recurse directly")
+case class DirectlyReentrantException() extends Exception("attempt to recurse directly")
object GlobalMutableState {
+
+ def log(c: whitebox.Context)(msg: String) = msg.split("/n").foreach { ln =>
+ println(" "*stackSize(c)+ln)
+ }
+
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, $value)")
+
+
+ private[magnolia] def wrap[T](c: whitebox.Context)(key: c.universe.Type, value: c.universe.TermName)(fn: => T): Option[T] = {
+ push(c)(key, value)
+ try Some(fn) catch {
+ case e: Exception => None
+ } finally {
+ pop(c)
+ }
+ }
+ private def push(c: whitebox.Context)(key: c.universe.Type, value: c.universe.TermName): Unit = {
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})")
+ private def pop(c: whitebox.Context): Unit = {
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] = {
+ private[magnolia] def find(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
+ private[magnolia] def enclosingTypes(c: whitebox.Context): List[c.universe.Type] =
+ state.get(Pos(c.enclosingPosition)).to[List].flatMap(_.keySet.map(_.asInstanceOf[c.universe.Type]).to[List])
+
+ private[magnolia] def stackSize(c: whitebox.Context): Int = state.get(Pos(c.enclosingPosition)).map(_.size).getOrElse(0)
+
+ private[magnolia] var searchType: Option[Universe#Type] = None
}
@bundle
@@ -68,48 +85,46 @@ class Macros(val context: whitebox.Context) extends GenericMacro(context) {
abstract class GenericMacro(whiteboxContext: whitebox.Context) {
val c = whiteboxContext
+
+ import c.universe._
+ val transformer = new Transformer {
+ override def transform(tree: Tree): Tree = tree match {
+ case ta@TypeApply(Select(Literal(Constant(method: String)), TermName("asInstanceOf")), List(tpe)) =>
+ log(c)(s"FOUND TYPEAPPLY: ${ta}")
+ val m = TermName(method)
+ q"$m"
+ case _ => super.transform(tree)
+ }
+ }
+
def getImplicit(genericType: c.universe.Type,
typeConstructor: c.universe.Type,
myName: c.universe.TermName): c.Tree = {
- import c.universe._
- //println(s"getImplicit1($genericType)")
- val x = GlobalMutableState.has(c)(genericType)
- val result = x.map { nm =>
- q"$nm"
+ //log(c)(s"getImplicit1($genericType)")
+ GlobalMutableState.find(c)(genericType).map { nm =>
+ val str = nm.encodedName.toString
+ val searchType = appliedType(typeConstructor, genericType)
+ q"$str.asInstanceOf[${searchType}]"
}.orElse {
val searchType = appliedType(typeConstructor, genericType)
- if(GlobalMutableState.has(c)(genericType).isEmpty) {
- GlobalMutableState.searchType = genericType
+ if(GlobalMutableState.find(c)(genericType).isEmpty) {
+ log(c)(s"could not find type $genericType in current context")
+ GlobalMutableState.searchType = Some(genericType)
val inferredImplicit = try Some({
- //println(s"ONE: $genericType -> $myName")
- //GlobalMutableState.push(c)(genericType, myName)
val imp = c.inferImplicitValue(searchType, false, false)
- //GlobalMutableState.pop(c)
- q"""{
- def $myName = $imp
+ /*q"""{
+ def $myName: $searchType = $imp
$myName
- }"""
+ }"""*/
+ imp
}) 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: ${ta}")
- val m = TermName(method)
- q"$m"
- case _ => super.transform(tree)
- }
- }
-
inferredImplicit.map { imp =>
- val x = c.untypecheck(imp)
- val z = c.untypecheck(transformer.transform(imp))
- println("x: "+x+", z: "+z)
- z
+ imp // c.untypecheck(transformer.transform(imp))
}.orElse {
directInferImplicit(genericType, typeConstructor)
}
@@ -117,18 +132,17 @@ abstract class GenericMacro(whiteboxContext: whitebox.Context) {
directInferImplicit(genericType, typeConstructor)
}
}.getOrElse {
- println("Really failed to find extractor for type "+genericType)
+ log(c)("failed this branch of derivation for type "+genericType)
c.abort(c.enclosingPosition, "Could not find extractor for type "+genericType)
}
-
- result
}
def directInferImplicit(genericType: c.universe.Type,
typeConstructor: c.universe.Type): Option[c.Tree] = {
- import c.universe._
- //println(s"directInferImplicit($genericType)")
+ log(c)(s"directInferImplicit($genericType) given definitions for ${GlobalMutableState.enclosingTypes(c).mkString("{", ", ", "}")}")
+
+ //if(genericType.typeSymbol.isAbstract) log(c)(s"cannot derive typeclass for abstract type $genericType")
val myName: TermName = TermName(c.freshName(genericType.typeSymbol.name.encodedName.toString.toLowerCase+"Extractor"))
val typeSymbol = genericType.typeSymbol
@@ -140,36 +154,51 @@ abstract class GenericMacro(whiteboxContext: whitebox.Context) {
val resultType = appliedType(typeConstructor, genericType)
val construct = if(isCaseClass) {
+ log(c)(s"deriving $genericType as a product type")
val implicits = genericType.decls.collect {
case m: MethodSymbol if m.isCaseAccessor => m.asMethod
}.map { param =>
+ log(c)(s"dealing with parameter '${param.name}'")
val returnType = param.returnType
- println(s"TWO: $genericType -> $myName")
- GlobalMutableState.push(c)(genericType, myName)
- val imp = getImplicit(returnType, typeConstructor, myName)
- GlobalMutableState.pop(c)
+ val imp = GlobalMutableState.wrap(c)(genericType, myName) {
+ getImplicit(returnType, typeConstructor, myName)
+ }.map { imp =>
+ val impString = if(imp.toString.contains("\\n")) "<synthetic>" else imp.toString
+ log(c)(s"found an implicit for type $returnType: $impString")
+ imp
+ }.getOrElse {
+ log(c)(s"failed to get implicit for type $genericType")
+ c.abort(c.enclosingPosition, s"failed to get implicit for type $genericType")
+ }
val dereferenced = dereferenceValue(c)(q"src", param.name.toString)
callDelegateMethod(c)(imp, dereferenced)
}
Some(q"new $genericType(..$implicits)")
} else if(isSealedTrait) {
+ log(c)(s"deriving $genericType as a coproduct type")
val subtypes = classType.get.knownDirectSubclasses.to[List]
- Some(subtypes.map(_.asType.toType).map { searchType =>
- println(s"THREE: $genericType -> $myName")
- GlobalMutableState.push(c)(genericType, myName)
- val res = getImplicit(searchType, typeConstructor, myName)
- GlobalMutableState.pop(c)
- res
- }.reduce(coproductReduction(c))).map { imp =>
+ Some {
+ subtypes.map(_.asType.toType).map { searchType =>
+ log(c)(s"exploring coproduct subtype $searchType")
+ val imp = GlobalMutableState.wrap(c)(genericType, myName) {
+ getImplicit(searchType, typeConstructor, myName)
+ }.getOrElse {
+ log(c)("exploration was unsuccessful")
+ c.abort(c.enclosingPosition, s"failed to get implicit for type $searchType")
+ }
+ log(c)(s"successful exploration: $imp")
+ imp
+ }.reduce(coproductReduction(c))
+ }.map { imp =>
callDelegateMethod(c)(imp, q"src")
}
} else None
- val result = construct.map { const =>
-
+ construct.map { const =>
val methodImplementation = classBody(c)(genericType, const)
+
q"""{
def $myName: $resultType = new $resultType {
$methodImplementation
@@ -177,9 +206,6 @@ abstract class GenericMacro(whiteboxContext: whitebox.Context) {
$myName
}"""
}
-
-
- result
}
protected def classBody(c: whitebox.Context)(genericType: c.Type, implementation: c.Tree): c.Tree
@@ -187,44 +213,49 @@ abstract class GenericMacro(whiteboxContext: whitebox.Context) {
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 {
+ def generic[T: c.WeakTypeTag, Typeclass: 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"previous: ${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 {
+ val directlyReentrant = Some(genericType) == GlobalMutableState.searchType
+ //log(c)(s"previous: ${GlobalMutableState.searchType}; THIS TYPE = $genericType")
+ val result: Option[c.Tree] = if(directlyReentrant) {
+ log(c)(s"detected direct reentry into generic macro for $genericType; aborting")
+ throw DirectlyReentrantException()
+ } else if(GlobalMutableState.searchType != None) {
+ log(c)(s"searching for in-scope implicit for $genericType")
+ GlobalMutableState.find(c)(genericType) match {
case None =>
- val typeConstructor: Type = weakTypeOf[Tc].typeConstructor
+ log(c)(s"could not find suitable implicit, so recursing on $genericType")
+ val typeConstructor: Type = weakTypeOf[Typeclass].typeConstructor
directInferImplicit(genericType, typeConstructor)
- case Some(t) =>
- val str = t.toString
- val typeConstructor: Type = weakTypeOf[Tc].typeConstructor
+ case Some(enclosingRef) =>
+ log(c)(s"found an enclosing implicit for $enclosingRef")
+ val str = enclosingRef.toString
+ val typeConstructor: Type = weakTypeOf[Typeclass].typeConstructor
val searchType = appliedType(typeConstructor, genericType)
- println(s"$str ===>>> ${searchType}")
Some(q"$str.asInstanceOf[${searchType}]")
}
} else {
- val typeConstructor: Type = weakTypeOf[Tc].typeConstructor
+ val typeConstructor: Type = weakTypeOf[Typeclass].typeConstructor
directInferImplicit(genericType, typeConstructor)
}
- println("Final result: "+result)
-
- result.getOrElse {
- println("FAIL.")
+ result.map { tree =>
+ val depth = GlobalMutableState.stackSize(c)
+ println("depth = "+depth)
+ val transformedTree = if(depth == 0) c.untypecheck(transformer.transform(tree)) else tree
+ val treeString = if(tree.toString.contains("\\n")) "<synthetic>" else tree.toString
+ log(c)(s"returning a complete tree, which may or may not typecheck: $treeString")
+ transformedTree
+ }.getOrElse {
+ log(c)("failed to derive a tree")
c.abort(c.enclosingPosition, "Could not infer extractor. Sorry.")
}
} catch {
- case e@ReentrantException() => throw e
+ case e@DirectlyReentrantException() => throw e
case e: Exception =>
- println("Oh no, there was a problem: "+e)
+ log(c)("Oh no, there was a problem: "+e)
e.printStackTrace()
???
}
diff --git a/tests/shared/src/main/scala/magnolia/main.scala b/tests/shared/src/main/scala/magnolia/main.scala
index aaa8ec9..d01d37a 100644
--- a/tests/shared/src/main/scala/magnolia/main.scala
+++ b/tests/shared/src/main/scala/magnolia/main.scala
@@ -3,17 +3,15 @@ package magnolia
sealed trait Bar
case class Foo(one: String) extends Bar
-case class Quux(two: Double, three: List[Bar]) extends Bar
-case class Bippy(four: String)
+case class Quux(two: String, three: Double, four: List[Bar]) extends Bar
+case class Bippy(five: String, six: List[Quux]) extends Bar
class Baz(val x: Bar) extends AnyVal
object Main {
+
def main(args: Array[String]): Unit = {
println(implicitly[Extractor[Bar]].extract(Thing("42")))
-
-
-
}
}