diff options
-rw-r--r-- | core/src/main/scala/generic.scala | 191 | ||||
-rw-r--r-- | tests/shared/src/main/scala/magnolia/main.scala | 8 |
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"))) - - - } } |