From a5e3bd7b949ba9b2997b1cbcca5365c1f97e2487 Mon Sep 17 00:00:00 2001 From: Jon Pretty Date: Mon, 29 May 2017 19:05:19 -0600 Subject: Pretty close to having the generic macro working And yet so far. --- core/src/main/scala/generic.scala | 189 ++++++++++++++++++++++++++++++-------- 1 file changed, 150 insertions(+), 39 deletions(-) (limited to 'core/src/main') 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() + ??? } } -- cgit v1.2.3