diff options
author | Jon Pretty <jon.pretty@propensive.com> | 2017-06-10 18:25:51 +0200 |
---|---|---|
committer | Jon Pretty <jon.pretty@propensive.com> | 2017-06-10 18:25:51 +0200 |
commit | 57159b663961a5931de5a0a058c7ade39c0c2a80 (patch) | |
tree | 40a7dfbff8b634f25e081494d581c1f2cbd8dc05 /core/src/main | |
parent | b56855910df8f8a2bf4f00d3de0bbd8b0afc14ac (diff) | |
download | magnolia-57159b663961a5931de5a0a058c7ade39c0c2a80.tar.gz magnolia-57159b663961a5931de5a0a058c7ade39c0c2a80.tar.bz2 magnolia-57159b663961a5931de5a0a058c7ade39c0c2a80.zip |
Further cleanup; no long lines
Diffstat (limited to 'core/src/main')
-rw-r--r-- | core/src/main/scala/generic.scala | 245 |
1 files changed, 96 insertions, 149 deletions
diff --git a/core/src/main/scala/generic.scala b/core/src/main/scala/generic.scala index 8ba4fd4..67cc93e 100644 --- a/core/src/main/scala/generic.scala +++ b/core/src/main/scala/generic.scala @@ -7,90 +7,37 @@ 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 DirectlyReentrantException() extends Exception("attempt to recurse directly") - -object Lazy { - def apply[T](method: String): T = ??? +abstract class Transformation[C <: whitebox.Context](val c: C) { + def typeclassBody(genericType: c.Type, implementation: c.Tree): c.Tree + def coproductReduction(left: c.Tree, right: c.Tree): c.Tree + def dereferenceValue(value: c.Tree, elem: String): c.Tree + def callDelegateMethod(value: c.Tree, argument: c.Tree): c.Tree } -object GlobalMutableState { +abstract class MagnoliaMacro(val c: whitebox.Context) { + import c.universe._ + import CompileTimeState._ - def log(c: whitebox.Context)(msg: String) = msg.split("/n").foreach { ln => - println(" "*stackSize(c)+ln) - } + protected def transformation(c: whitebox.Context): Transformation[c.type] - 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 def findType(key: c.universe.Type): Option[c.universe.TermName] = + recursionStack(c.enclosingPosition).get(key).map(_.asInstanceOf[c.universe.TermName]) - 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))) - } - - private def pop(c: whitebox.Context): Unit = { - state = state.updated(Pos(c.enclosingPosition), state(Pos(c.enclosingPosition)).init) - } - - 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 def recurse[T](key: c.universe.Type, value: c.universe.TermName)(fn: => T): Option[T] = { + recursionStack = recursionStack.updated( + c.enclosingPosition, + recursionStack.get(c.enclosingPosition).map { m => + m.updated(key, value) + }.getOrElse(ListMap(key -> value)) + ) + + try Some(fn) catch { case e: Exception => None } finally { + recursionStack = recursionStack.updated(c.enclosingPosition, + recursionStack(c.enclosingPosition).init) } } - - 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 -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 - - import c.universe._ - val transformer = new Transformer { + private val transformer = new Transformer { override def transform(tree: Tree): Tree = tree match { case q"_root_.magnolia.Lazy[$returnType](${Literal(Constant(method: String))})" => q"${TermName(method)}" @@ -99,26 +46,26 @@ abstract class GenericMacro(whiteboxContext: whitebox.Context) { } - def getImplicit(genericType: c.universe.Type, + private def getImplicit(genericType: c.universe.Type, typeConstructor: c.universe.Type, - myName: c.universe.TermName): c.Tree = { + assignedName: c.universe.TermName): c.Tree = { - GlobalMutableState.find(c)(genericType).map { methodName => + findType(genericType).map { methodName => val methodAsString = methodName.encodedName.toString val searchType = appliedType(typeConstructor, genericType) q"_root_.magnolia.Lazy[$searchType]($methodAsString)" }.orElse { val searchType = appliedType(typeConstructor, genericType) - if(GlobalMutableState.find(c)(genericType).isEmpty) { - log(c)(s"could not find type $genericType in current context") - GlobalMutableState.searchType = Some(genericType) + if(findType(genericType).isEmpty) { + lastSearchType = Some(genericType) val inferredImplicit = try Some({ - val myName: TermName = TermName(c.freshName(genericType.typeSymbol.name.encodedName.toString.toLowerCase+"Extractor")) - GlobalMutableState.wrap(c)(genericType, myName) { + val genericTypeName: String = genericType.typeSymbol.name.encodedName.toString.toLowerCase + val assignedName: TermName = TermName(c.freshName(s"${genericTypeName}Typeclass")) + recurse(genericType, assignedName) { val imp = c.inferImplicitValue(searchType, false, false) q"""{ - def $myName: $searchType = $imp - $myName + def $assignedName: $searchType = $imp + $assignedName }""" }.get }) catch { @@ -132,107 +79,84 @@ abstract class GenericMacro(whiteboxContext: whitebox.Context) { directInferImplicit(genericType, typeConstructor) } }.getOrElse { - log(c)("failed this branch of derivation for type "+genericType) c.abort(c.enclosingPosition, "Could not find extractor for type "+genericType) } } - def directInferImplicit(genericType: c.universe.Type, + private def directInferImplicit(genericType: c.universe.Type, typeConstructor: c.universe.Type): Option[c.Tree] = { - - log(c)(s"directInferImplicit($genericType) given definitions for ${GlobalMutableState.enclosingTypes(c).mkString("{", ", ", "}")}") - val myName: TermName = TermName(c.freshName(genericType.typeSymbol.name.encodedName.toString.toLowerCase+"Extractor")) + val genericTypeName: String = genericType.typeSymbol.name.encodedName.toString.toLowerCase + val assignedName: TermName = TermName(c.freshName(s"${genericTypeName}Typeclass")) 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 isValueClass = genericType <:< typeOf[AnyVal] 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 - 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 + + val derivedImplicit = recurse(genericType, assignedName) { + getImplicit(param.returnType, typeConstructor, assignedName) }.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) + + val dereferencedValue = transformation(c).dereferenceValue(q"src", param.name.toString) + + transformation(c).callDelegateMethod( + derivedImplicit, + dereferencedValue + ) } 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 => - log(c)(s"exploring coproduct subtype $searchType") - val imp = GlobalMutableState.wrap(c)(genericType, myName) { - getImplicit(searchType, typeConstructor, myName) + + Some( + transformation(c)callDelegateMethod(subtypes.map(_.asType.toType).map { searchType => + recurse(genericType, assignedName) { + getImplicit(searchType, typeConstructor, assignedName) }.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") - } - + }.reduce(transformation(c).coproductReduction), q"src") + ) } else None construct.map { const => - val methodImplementation = classBody(c)(genericType, const) + val bodyImplementation = transformation(c).typeclassBody(genericType, const) q"""{ - def $myName: $resultType = new $resultType { - $methodImplementation - } - $myName + def $assignedName: $resultType = new $resultType { $bodyImplementation } + $assignedName }""" } } - 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, Typeclass: c.WeakTypeTag]: c.Tree = try { + def magnolia[T: c.WeakTypeTag, Typeclass: c.WeakTypeTag]: c.Tree = try { import c.universe._ val genericType: Type = weakTypeOf[T] - 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 { + val directlyReentrant = Some(genericType) == lastSearchType + if(directlyReentrant) throw DirectlyReentrantException() + val result: Option[c.Tree] = if(lastSearchType != None) { + findType(genericType) match { case None => - log(c)(s"could not find suitable implicit, so recursing on $genericType") val typeConstructor: Type = weakTypeOf[Typeclass].typeConstructor directInferImplicit(genericType, typeConstructor) case Some(enclosingRef) => - log(c)(s"found an enclosing implicit for $enclosingRef") - val str = enclosingRef.toString + val methodAsString = enclosingRef.toString val typeConstructor: Type = weakTypeOf[Typeclass].typeConstructor val searchType = appliedType(typeConstructor, genericType) - Some(q"$str.asInstanceOf[${searchType}]") + Some(q"_root_.magnolia.Lazy[$searchType]($methodAsString)") } } else { val typeConstructor: Type = weakTypeOf[Typeclass].typeConstructor @@ -240,23 +164,46 @@ abstract class GenericMacro(whiteboxContext: whitebox.Context) { } 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 + val recursionDepth = recursionStack.get(c.enclosingPosition).map(_.size).getOrElse(0) + if(recursionDepth == 0) c.untypecheck(transformer.transform(tree)) else tree }.getOrElse { - log(c)("failed to derive a tree") c.abort(c.enclosingPosition, "Could not infer typeclass. Sorry.") } } catch { case e@DirectlyReentrantException() => throw e case e: Exception => - log(c)("Oh no, there was a problem: "+e) e.printStackTrace() ??? } +} + +private[magnolia] case class DirectlyReentrantException() extends + Exception("attempt to recurse directly") +private[magnolia] object Lazy { def apply[T](method: String): T = ??? } + +private[magnolia] object CompileTimeState { + + private[magnolia] var recursionStack: Map[api.Position, ListMap[ + c.universe.Type forSome { val c: whitebox.Context }, + c.universe.TermName forSome { val c: whitebox.Context } + ]] = Map() + + private[magnolia] var lastSearchType: Option[Universe#Type] = None } + +@bundle +class Macros(val context: whitebox.Context) extends MagnoliaMacro(context) { + protected def transformation(c: whitebox.Context): Transformation[c.type] = + new Transformation[c.type](c) { + import c.universe._ + + def typeclassBody(genericType: c.Type, implementation: c.Tree): c.Tree = + q"""def extract(src: _root_.magnolia.Thing): $genericType = $implementation""" + + def dereferenceValue(value: c.Tree, elem: String): c.Tree = q"$value.access($elem)" + def callDelegateMethod(value: c.Tree, argument: c.Tree): c.Tree = q"$value.extract($argument)" + def coproductReduction(left: c.Tree, right: c.Tree): c.Tree = q"$left.orElse($right)" + } +} |