aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--core/src/main/scala/magnolia.scala171
-rw-r--r--examples/src/main/scala/example.scala43
-rw-r--r--tests/shared/src/main/scala/magnolia/main.scala14
4 files changed, 160 insertions, 70 deletions
diff --git a/README.md b/README.md
index ebc850e..b4f1876 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,8 @@
# Magnolia
+[![Join the chat at https://gitter.im/propensive/magnolia](https://badges.gitter.im/propensive/magnolia.svg)](https://gitter.im/propensive/magnolia?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
Magnolia is a generic macro for automatic derivation of typeclasses for
datatypes composed from case classes and sealed traits. It supports
recursively-defined datatypes out-of-the-box, and incurs no significant
diff --git a/core/src/main/scala/magnolia.scala b/core/src/main/scala/magnolia.scala
index d2978b5..66d59d0 100644
--- a/core/src/main/scala/magnolia.scala
+++ b/core/src/main/scala/magnolia.scala
@@ -2,7 +2,6 @@ package magnolia
import scala.reflect._, macros._
import macrocompat.bundle
-import scala.util.Try
import scala.collection.immutable.ListMap
import language.existentials
import language.higherKinds
@@ -12,6 +11,14 @@ class Macros(val c: whitebox.Context) {
import c.universe._
import CompileTimeState._
+
+ sealed trait DerivationImplicit { def tree: Tree }
+ case class CovariantDerivationImplicit(tree: Tree) extends DerivationImplicit
+ sealed trait ContravariantDerivationImplicit extends DerivationImplicit
+ case class ContravariantDerivation1Implicit(tree: Tree) extends ContravariantDerivationImplicit
+ case class ContravariantDerivation2Implicit(tree: Tree) extends ContravariantDerivationImplicit
+
+
private def findType(key: Type): Option[TermName] =
recursionStack(c.enclosingPosition).frames.find(_.genericType == key).map(_.termName(c))
@@ -22,14 +29,14 @@ class Macros(val c: whitebox.Context) {
recursionStack.get(c.enclosingPosition).map(_.push(path, key, value)).getOrElse(
Stack(List(Frame(path, key, value)), Nil))
)
-
+
try Some(fn) catch { case e: Exception => None } finally {
val currentStack = recursionStack(c.enclosingPosition)
recursionStack = recursionStack.updated(c.enclosingPosition,
currentStack.pop())
}
}
-
+
private val removeLazy: Transformer = new Transformer {
override def transform(tree: Tree): Tree = tree match {
case q"_root_.magnolia.Lazy.apply[$returnType](${Literal(Constant(method: String))})" =>
@@ -38,49 +45,44 @@ class Macros(val c: whitebox.Context) {
super.transform(tree)
}
}
-
+
private def getImplicit(paramName: Option[String],
genericType: Type,
typeConstructor: Type,
assignedName: TermName,
- derivationImplicit: Either[Tree, Tree]): Tree = {
-
+ derivationImplicit: DerivationImplicit): Tree = {
+
+ val searchType = appliedType(typeConstructor, genericType)
findType(genericType).map { methodName =>
val methodAsString = methodName.encodedName.toString
- val searchType = appliedType(typeConstructor, genericType)
q"_root_.magnolia.Lazy.apply[$searchType]($methodAsString)"
}.orElse {
- val searchType = appliedType(typeConstructor, genericType)
- findType(genericType).map { _ =>
- directInferImplicit(genericType, typeConstructor, derivationImplicit)
- }.getOrElse {
- scala.util.Try {
- val genericTypeName: String = genericType.typeSymbol.name.encodedName.toString.toLowerCase
- val assignedName: TermName = TermName(c.freshName(s"${genericTypeName}Typeclass"))
- recurse(ChainedImplicit(genericType.toString), genericType, assignedName) {
- val inferredImplicit = c.inferImplicitValue(searchType, false, false)
- q"""{
- def $assignedName: $searchType = $inferredImplicit
- $assignedName
- }"""
- }.get
- }.toOption.orElse(directInferImplicit(genericType, typeConstructor, derivationImplicit))
- }
+ scala.util.Try {
+ val genericTypeName: String = genericType.typeSymbol.name.encodedName.toString.toLowerCase
+ val assignedName: TermName = TermName(c.freshName(s"${genericTypeName}Typeclass"))
+ recurse(ChainedImplicit(genericType.toString), genericType, assignedName) {
+ val inferredImplicit = c.inferImplicitValue(searchType, false, false)
+ q"""{
+ def $assignedName: $searchType = $inferredImplicit
+ $assignedName
+ }"""
+ }.get
+ }.toOption.orElse(directInferImplicit(genericType, typeConstructor, derivationImplicit))
}.getOrElse {
val currentStack: Stack = recursionStack(c.enclosingPosition)
-
+
val error = ImplicitNotFound(genericType.toString,
recursionStack(c.enclosingPosition).frames.map(_.path))
-
- val updatedStack = currentStack.copy(errors = error :: currentStack.errors)
+
+ val updatedStack = currentStack.copy(errors = error :: currentStack.errors)
recursionStack = recursionStack.updated(c.enclosingPosition, updatedStack)
c.abort(c.enclosingPosition, s"Could not find type class for type $genericType")
}
}
-
+
private def directInferImplicit(genericType: Type,
typeConstructor: Type,
- derivationImplicit: Either[Tree, Tree]): Option[Tree] = {
+ derivationImplicit: DerivationImplicit): Option[Tree] = {
val genericTypeName: String = genericType.typeSymbol.name.encodedName.toString.toLowerCase
val assignedName: TermName = TermName(c.freshName(s"${genericTypeName}Typeclass"))
@@ -89,7 +91,7 @@ class Macros(val c: whitebox.Context) {
val isCaseClass = classType.map(_.isCaseClass).getOrElse(false)
val isSealedTrait = classType.map(_.isSealed).getOrElse(false)
val isValueClass = genericType <:< typeOf[AnyVal]
-
+
val resultType = appliedType(typeConstructor, genericType)
val construct = if(isCaseClass) {
@@ -99,37 +101,42 @@ class Macros(val c: whitebox.Context) {
val implicits = caseClassParameters.map { param =>
val paramName = param.name.encodedName.toString
-
+
val derivedImplicit = recurse(ProductType(paramName, genericType.toString), genericType,
assignedName) {
-
+
getImplicit(Some(paramName), param.returnType, typeConstructor, assignedName,
derivationImplicit)
-
+
}.getOrElse {
c.abort(c.enclosingPosition, s"failed to get implicit for type $genericType")
}
-
+
derivationImplicit match {
- case Left(impl) =>
+ case CovariantDerivationImplicit(impl) =>
val dereferencedValue = q"$impl.dereference(sourceParameter, ${param.name.toString})"
q"$impl.call($derivedImplicit, $dereferencedValue)"
- case Right(impl) =>
+ case ContravariantDerivation1Implicit(impl) =>
val paramName = TermName(param.name.toString)
val dereferencedValue = q"sourceParameter.$paramName"
q"$impl.call($derivedImplicit, $dereferencedValue)"
+ case ContravariantDerivation2Implicit(impl) =>
+ val paramName = TermName(param.name.toString)
+ val dereferencedValue1 = q"sourceParameter1.$paramName"
+ val dereferencedValue2 = q"sourceParameter2.$paramName"
+ q"$impl.call($derivedImplicit, $dereferencedValue1, $dereferencedValue2)"
}
}
derivationImplicit match {
- case Left(_) =>
+ case CovariantDerivationImplicit(_) =>
Some(q"new $genericType(..$implicits)")
- case Right(impl) =>
+ case contra: ContravariantDerivationImplicit =>
val namedImplicits = caseClassParameters.zip(implicits).map { case (param, tree) =>
q"(${param.name.encodedName.toString}, $tree)"
}
- Some(q"$impl.join(_root_.scala.collection.immutable.ListMap(..$namedImplicits))")
+ Some(q"${contra.tree}.join(_root_.scala.collection.immutable.ListMap(..$namedImplicits))")
}
} else if(isSealedTrait) {
@@ -150,36 +157,56 @@ class Macros(val c: whitebox.Context) {
}
derivationImplicit match {
- case Left(impl) =>
+ case CovariantDerivationImplicit(impl) =>
val reduction = components.reduce { (left, right) => q"$impl.combine($left, $right)" }
q"$impl.call($reduction, sourceParameter)"
- case Right(impl) =>
+
+ case ContravariantDerivation2Implicit(impl) =>
val parts = subtypes.tail.zip(components.tail)
-
val base = q"""
- $impl.call(${components.head}, sourceParameter.asInstanceOf[${subtypes.head}])
+ $impl.call(${components.head}, sourceParameter1.asInstanceOf[${subtypes.head}], sourceParameter2.asInstanceOf[${subtypes.head}])
"""
-
parts.foldLeft(base) { case (aggregated, (componentType, derivedImplicit)) =>
q"""
- if(sourceParameter.isInstanceOf[$componentType])
- $impl.call($derivedImplicit, sourceParameter.asInstanceOf[$componentType])
+ if(sourceParameter1.isInstanceOf[$componentType] && sourceParameter2.isInstanceOf[$componentType])
+ $impl.call($derivedImplicit, sourceParameter1.asInstanceOf[$componentType], sourceParameter2.asInstanceOf[$componentType])
else $aggregated"""
}
+ case ContravariantDerivation1Implicit(impl) =>
+ val parts = subtypes.zip(components)
+
+ val caseClauses = parts.map { case (subtype, component) =>
+ cq"(value: $subtype) => $impl.call($component, value)"
+ }
+
+ q"""(sourceParameter: @_root_.scala.annotation.switch) match {
+ case ..$caseClauses
+ }"""
}
}
} else None
construct.map { const =>
- val impl = derivationImplicit.merge
- q"""{
- def $assignedName: $resultType = $impl.construct { sourceParameter => $const }
- $assignedName
- }"""
+
+ derivationImplicit match {
+ case CovariantDerivationImplicit(_) =>
+ ???
+ case ContravariantDerivation1Implicit(impl) =>
+ q"""{
+ def $assignedName: $resultType = $impl.construct { sourceParameter => $const }
+ $assignedName
+ }"""
+ case ContravariantDerivation2Implicit(impl) =>
+ q"""{
+ def $assignedName: $resultType = $impl.construct { case (sourceParameter1, sourceParameter2) => $const }
+ $assignedName
+ }"""
+ }
}
}
def magnolia[T: WeakTypeTag, Typeclass: WeakTypeTag]: Tree = {
+ import scala.util.{Try, Success, Failure}
val genericType: Type = weakTypeOf[T]
val currentStack: Stack = recursionStack.get(c.enclosingPosition).getOrElse(Stack(List(), List()))
@@ -188,23 +215,29 @@ class Macros(val c: whitebox.Context) {
val coDerivationTypeclass = weakTypeOf[CovariantDerivation[_]].typeConstructor
val contraDerivationTypeclass = weakTypeOf[ContravariantDerivation[_]].typeConstructor
-
+ val contraDerivation2Typeclass = weakTypeOf[ContravariantDerivation2[_]].typeConstructor
+
val coDerivationType = appliedType(coDerivationTypeclass, List(typeConstructor))
val contraDerivationType = appliedType(contraDerivationTypeclass, List(typeConstructor))
- val derivationImplicit = try {
- Left(c.untypecheck(c.inferImplicitValue(coDerivationType, false, false)))
- } catch {
- case e: Exception =>
- try Right(c.untypecheck(c.inferImplicitValue(contraDerivationType, false, false))) catch {
- case e: Exception =>
- c.info(c.enclosingPosition, s"could not find an implicit instance of "+
- s"CovariantDerivation[$typeConstructor] or "+
- s"ContravariantDerivation[$typeConstructor]", true)
-
- throw e
- }
- }
-
+ val contraDerivation2Type = appliedType(contraDerivation2Typeclass, List(typeConstructor))
+
+ def findDerivationImplicit[T <: DerivationImplicit](tpe: c.Type, cons: Tree => T): Try[DerivationImplicit] =
+ Try(cons(c.untypecheck(c.inferImplicitValue(tpe, false, false))))
+
+ val derivationImplicit =
+ findDerivationImplicit(coDerivationType, CovariantDerivationImplicit)
+ .orElse(findDerivationImplicit(contraDerivationType, ContravariantDerivation1Implicit))
+ .orElse(findDerivationImplicit(contraDerivation2Type, ContravariantDerivation2Implicit)) match {
+ case Failure(e) =>
+ c.info(c.enclosingPosition, s"could not find an implicit instance of "+
+ s"CovariantDerivation[$typeConstructor] or "+
+ s"ContravariantDerivation[$typeConstructor] or "+
+ s"ContravariantDerivation2[$typeConstructor]", true)
+ throw e
+ case Success(di) =>
+ di
+ }
+
if(directlyReentrant) throw DirectlyReentrantException()
currentStack.errors.foreach { error =>
@@ -226,7 +259,6 @@ class Macros(val c: whitebox.Context) {
Some(q"_root_.magnolia.Lazy[$searchType]($methodAsString)")
}
} else {
- val typeConstructor: Type = weakTypeOf[Typeclass].typeConstructor
directInferImplicit(genericType, typeConstructor, derivationImplicit)
}
@@ -301,5 +333,12 @@ trait ContravariantDerivation[Typeclass[_]] {
def call[T](typeclass: Typeclass[T], value: T): Return
def construct[T](body: T => Return): Typeclass[T]
def join(elements: ListMap[String, Return]): Return
+}
+trait ContravariantDerivation2[Typeclass[_]] {
+ type Return
+ def call[T](typeclass: Typeclass[T], value1: T, value2: T): Return
+ def construct[T](body: (T, T) => Return): Typeclass[T]
+ def join(elements: ListMap[String, Return]): Return
}
+
diff --git a/examples/src/main/scala/example.scala b/examples/src/main/scala/example.scala
index c3d4c2f..6c289cb 100644
--- a/examples/src/main/scala/example.scala
+++ b/examples/src/main/scala/example.scala
@@ -6,11 +6,30 @@ import language.experimental.macros
import language.higherKinds
import collection.immutable.ListMap
-sealed trait EmptyType
+
+object `package` {
+ implicit class Showable[T: Show](t: T) {
+ def show: String = implicitly[Show[T]].show(t)
+ }
+ implicit val showString: Show[String] = identity
+ implicit val showBool: Show[Boolean] = _.toString
+ implicit def showList[T: Show]: Show[List[T]] = xs => xs.map { x => s"list:${implicitly[Show[T]].show(x)}" }.mkString(";")
+ implicit def showSet[T: Show]: Show[Set[T]] = s => "set"
+
+ implicit class Equable[T: Eq](t: T) {
+ def isEqualTo(other: T): Boolean = implicitly[Eq[T]].isEqual(t, other)
+ }
+ implicit val eqString: Eq[String] = _ == _
+ implicit val eqBool: Eq[Boolean] = _ == _
+ implicit def eqList[T: Eq]: Eq[List[T]] =
+ (l1, l2) => l1.size == l2.size && (l1 zip l2).forall { case (e1, e2) => e1 isEqualTo e2 }
+ implicit def eqSet[T: Eq]: Eq[Set[T]] =
+ (s1, s2) => s1.size == s2.size && (s1 zip s2).forall { case (e1, e2) => e1 isEqualTo e2 }
+}
sealed trait Tree
case class Branch(left: Tree, right: Tree) extends Tree
-case class Leaf(value: Int, no: String) extends Tree
+case class Leaf(value: Int) extends Tree
sealed trait Entity
case class Person(name: String, address: Address) extends Entity
@@ -41,3 +60,23 @@ object Show extends Show_1 {
trait Show_1 {
implicit def generic[T]: Show[T] = macro Macros.magnolia[T, Show[_]]
}
+
+trait Eq[T] { def isEqual(a: T, b: T): Boolean }
+
+object Eq extends Eq_1 {
+
+ implicit val eqInt: Eq[Int] = _ == _
+
+ implicit val derivation = new ContravariantDerivation2[Eq] {
+ type Return = Boolean
+ def call[T](eq: Eq[T], value1: T, value2: T): Boolean =
+ if(value1.getClass == value2.getClass) eq.isEqual(value1, value2) else false
+ def construct[T](body: (T, T) => Boolean): Eq[T] = body(_, _)
+ def join(elements: ListMap[String, Boolean]): Boolean = elements.forall(_._2)
+ }
+}
+
+trait Eq_1 {
+ implicit def generic[T]: Eq[T] = macro Macros.magnolia[T, Eq[_]]
+}
+
diff --git a/tests/shared/src/main/scala/magnolia/main.scala b/tests/shared/src/main/scala/magnolia/main.scala
index 88b401e..4a9347a 100644
--- a/tests/shared/src/main/scala/magnolia/main.scala
+++ b/tests/shared/src/main/scala/magnolia/main.scala
@@ -5,10 +5,20 @@ import examples.Show._
object Main {
def main(args: Array[String]): Unit = {
- println(Branch(Branch(Leaf(1, "a"), Leaf(2, "b")), Leaf(3, "c")).show)
+
+ val tree1: Tree = Branch(Branch(Leaf(1), Leaf(2)), Leaf(3))
+ val tree2: Tree = Branch(Leaf(1), Leaf(2))
+
+ println(tree1.show)
+ println(tree1 isEqualTo tree1)
+ println(tree1 isEqualTo tree2)
+
+ println(Branch(Branch(Leaf(1), Leaf(2)), Leaf(3)).show)
+
println(List[Entity](Person("John Smith",
- Address(List("1 High Street", "London", "SW1A 1AA"),
+ Address(List("1 High Street", "London", "SW1A 1AA"),
Country("UK", "GBR", false)))).show)
+
}
}