aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/src/main/scala/generic.scala245
-rw-r--r--examples/src/main/scala/example.scala2
2 files changed, 97 insertions, 150 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)"
+ }
+}
diff --git a/examples/src/main/scala/example.scala b/examples/src/main/scala/example.scala
index 1e2f104..23c0e86 100644
--- a/examples/src/main/scala/example.scala
+++ b/examples/src/main/scala/example.scala
@@ -35,5 +35,5 @@ trait Extractor_1 extends Extractor_2 {
}
}
trait Extractor_2 {
- implicit def generic[T]: Extractor[T] = macro Macros.generic[T, Extractor[_]]
+ implicit def generic[T]: Extractor[T] = macro Macros.magnolia[T, Extractor[_]]
}