aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Pretty <jon.pretty@propensive.com>2017-10-30 12:55:57 +0100
committerJon Pretty <jon.pretty@propensive.com>2017-10-30 12:55:57 +0100
commit8edaa6e2adbb4aa724a7829be5003fdabb01628a (patch)
tree4138ee6e39625d8b65001a678e2cf37b6173b383
parent8cd72499b92d745ce3e3f2621a03ab61ce78ef85 (diff)
downloadmagnolia-8edaa6e2adbb4aa724a7829be5003fdabb01628a.tar.gz
magnolia-8edaa6e2adbb4aa724a7829be5003fdabb01628a.tar.bz2
magnolia-8edaa6e2adbb4aa724a7829be5003fdabb01628a.zip
Derivation working for Show and Eq
-rw-r--r--build.sbt7
-rwxr-xr-xbuild.sh11
-rw-r--r--core/src/main/scala/magnolia.scala456
-rw-r--r--examples/src/main/scala/cats.scala19
-rw-r--r--examples/src/main/scala/eq.scala50
-rw-r--r--examples/src/main/scala/typeclasses.scala93
-rw-r--r--tests/shared/src/main/scala/magnolia/main.scala24
-rw-r--r--tests/src/main/scala/main.scala81
8 files changed, 389 insertions, 352 deletions
diff --git a/build.sbt b/build.sbt
index 33026f3..9fc7552 100644
--- a/build.sbt
+++ b/build.sbt
@@ -32,7 +32,7 @@ lazy val examplesJS = examples.js
lazy val examplesNative = examples.native
lazy val tests = crossProject(JSPlatform, JVMPlatform, NativePlatform)
- .crossType(CrossType.Full)
+ .crossType(CrossType.Pure)
.in(file("tests"))
.settings(buildSettings: _*)
.settings(noPublishSettings: _*)
@@ -134,7 +134,10 @@ lazy val scalaMacroDependencies: Seq[Setting[_]] = Seq(
)
lazy val examplesDependencies: Seq[Setting[_]] = Seq(
- libraryDependencies += "org.typelevel" %% "cats-core" % "0.9.0")
+ libraryDependencies += "org.typelevel" %% "cats-core" % "0.9.0",
+ libraryDependencies += "com.propensive" %% "estrapade" % "1.0.0",
+ libraryDependencies += "com.propensive" %% "contextual-data" % "1.0.3"
+)
credentials ++= (for {
username <- Option(System.getenv().get("SONATYPE_USERNAME"))
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..b556f81
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,11 @@
+#!/bin/zsh
+
+source ../build.zsh
+
+PROJECT=magnolia
+
+TEST_DEPENDENCIES=$PROJECT-core.jar:$PROJECT-examples.jar:../estrapade/estrapade-macros.jar:../estrapade/estrapade-core.jar:../contextual/contextual-core.jar:../contextual/contextual-data.jar
+build core && \
+build examples -cp $PROJECT-core.jar && \
+build tests -cp $TEST_DEPENDENCIES && \
+runtests tests -cp $TEST_DEPENDENCIES:$PROJECT-tests.jar
diff --git a/core/src/main/scala/magnolia.scala b/core/src/main/scala/magnolia.scala
index 4e239c6..9da9f1e 100644
--- a/core/src/main/scala/magnolia.scala
+++ b/core/src/main/scala/magnolia.scala
@@ -1,223 +1,217 @@
package magnolia
import scala.reflect._, macros._
-import macrocompat.bundle
import scala.collection.immutable.ListMap
import language.existentials
import language.higherKinds
import language.experimental.macros
-@bundle
-class Macros(val c: whitebox.Context) {
- import c.universe._
+trait Subclass[Tc[_], T] {
+ type S <: T
+ def typeclass: Tc[S]
+ def label: String
+ def cast: PartialFunction[T, S]
+}
+
+trait Param[Tc[_], T] {
+ type S
+ def typeclass: Tc[S]
+ def label: String
+ def dereference(param: T): S
+}
+
+object Magnolia {
import CompileTimeState._
- sealed trait GeneralDerivationImplicit { def tree: Tree }
- case class DerivationImplicit(tree: Tree) extends GeneralDerivationImplicit
- sealed trait CoderivationImplicit extends GeneralDerivationImplicit
- case class Coderivation1Implicit(tree: Tree) extends CoderivationImplicit
- case class Coderivation2Implicit(tree: Tree) extends CoderivationImplicit
-
- private def findType(key: Type): Option[TermName] =
- recursionStack(c.enclosingPosition).frames.find(_.genericType == key).map(_.termName(c))
-
- private def recurse[T](path: TypePath, key: Type, value: TermName)(fn: => T):
- Option[T] = {
- recursionStack = recursionStack.updated(
- c.enclosingPosition,
- 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())
- }
- }
+ type Construct[Call[_], T] = ((Call[R] => R) forSome { type R }) => T
- 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))})" =>
- q"${TermName(method)}"
- case _ =>
- super.transform(tree)
+ def generic[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
+ import c.universe._
+ import scala.util.{Try, Success, Failure}
+
+ def javaClassName(sym: Symbol): String =
+ if(sym.owner.isPackage) sym.fullName
+ else if(sym.owner.isModuleClass) s"${javaClassName(sym.owner)}$$${sym.name}"
+ else s"${javaClassName(sym.owner)}.${sym.name}"
+
+ def getModule[M](tpe: Type): M = {
+ val typeName = javaClassName(tpe.typeSymbol)
+
+ try {
+ val cls = Class.forName(s"$typeName$$")
+ cls.getField("MODULE$").get(cls).asInstanceOf[M]
+ } catch {
+ case e: ClassNotFoundException =>
+ c.abort(c.enclosingPosition, s"""Class "${typeName}" could not be found. This usually means you are trying to use an interpolator in the same compilation unit as the one in which you defined it. Please try compiling interpolators first, separately from the code using them.""")
+ }
+ }
+
+ val typeConstructor: c.Type = c.prefix.tree.tpe.companion.typeConstructor
+
+ def findType(key: Type): Option[TermName] =
+ recursionStack(c.enclosingPosition).frames.find(_.genericType == key).map(_.termName(c))
+
+ def recurse[T](path: TypePath, key: Type, value: TermName)(fn: => T):
+ Option[T] = {
+ recursionStack = recursionStack.updated(
+ c.enclosingPosition,
+ 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 def getImplicit(paramName: Option[String],
- genericType: Type,
- typeConstructor: Type,
- assignedName: TermName,
- derivationImplicit: GeneralDerivationImplicit): Tree = {
-
- val searchType = appliedType(typeConstructor, genericType)
- findType(genericType).map { methodName =>
- val methodAsString = methodName.encodedName.toString
- q"_root_.magnolia.Lazy.apply[$searchType]($methodAsString)"
- }.orElse {
- 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 removeDeferred: Transformer = new Transformer {
+ override def transform(tree: Tree): Tree = tree match {
+ case q"_root_.magnolia.Deferred.apply[$returnType](${Literal(Constant(method: String))})" =>
+ q"${TermName(method)}"
+ case _ =>
+ super.transform(tree)
+ }
+ }
- val updatedStack = currentStack.copy(errors = error :: currentStack.errors)
- recursionStack = recursionStack.updated(c.enclosingPosition, updatedStack)
- c.abort(c.enclosingPosition, s"magnolia: could not find typeclass for type $genericType")
+ def implicitTree(paramName: Option[String],
+ genericType: Type,
+ typeConstructor: Type,
+ assignedName: TermName): Tree = {
+
+ val searchType = appliedType(typeConstructor, genericType)
+ findType(genericType).map { methodName =>
+ val methodAsString = methodName.encodedName.toString
+ q"_root_.magnolia.Deferred.apply[$searchType]($methodAsString)"
+ }.orElse {
+ 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))
+ }.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)
+ recursionStack = recursionStack.updated(c.enclosingPosition, updatedStack)
+ val stack = recursionStack(c.enclosingPosition).frames.map(_.path).mkString(" in ", "\n in ", "\n")
+ c.abort(c.enclosingPosition, s"magnolia: could not find typeclass for type $genericType\n$stack")
+ }
}
- }
- private def directInferImplicit(genericType: Type,
- typeConstructor: Type,
- derivationImplicit: GeneralDerivationImplicit): Option[Tree] = {
+ def directInferImplicit(genericType: c.Type,
+ typeConstructor: Type): Option[c.Tree] = {
- 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 isValueClass = genericType <:< typeOf[AnyVal]
+ 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 isValueClass = genericType <:< typeOf[AnyVal]
- val resultType = appliedType(typeConstructor, genericType)
+ val resultType = appliedType(typeConstructor, genericType)
- val construct = if(isCaseClass) {
- val caseClassParameters = genericType.decls.collect {
- case m: MethodSymbol if m.isCaseAccessor => m.asMethod
- }
- val className = genericType.toString
- val implicits = caseClassParameters.map { param =>
- val paramName = param.name.encodedName.toString
+ // FIXME: Handle AnyVals
+ if(isCaseClass) {
+ val caseClassParameters = genericType.decls.collect {
+ case m: MethodSymbol if m.isCaseAccessor => m.asMethod
+ }
+ val className = genericType.toString
- val derivedImplicit = recurse(ProductType(paramName, genericType.toString), genericType,
- assignedName) {
+ val implicits: List[(c.universe.MethodSymbol, c.Tree)] = caseClassParameters.map { param =>
+ val paramName = param.name.encodedName.toString
- getImplicit(Some(paramName), param.returnType, typeConstructor, assignedName,
- derivationImplicit)
+ val derivedImplicit = recurse(ProductType(paramName, genericType.toString), genericType,
+ assignedName) {
- }.getOrElse {
- c.abort(c.enclosingPosition, s"failed to get implicit for type $genericType")
- }
+ implicitTree(Some(paramName), param.returnType, typeConstructor, assignedName)
- derivationImplicit match {
- case DerivationImplicit(impl) =>
- val dereferencedValue = q"$impl.dereference(sourceParameter, ${param.name.toString})"
- q"$impl.call($derivedImplicit, $dereferencedValue)"
- case Coderivation1Implicit(impl) =>
- val paramName = TermName(param.name.toString)
- val dereferencedValue = q"sourceParameter.$paramName"
- q"$impl.call($derivedImplicit, $dereferencedValue)"
- case Coderivation2Implicit(impl) =>
- val paramName = TermName(param.name.toString)
- val dereferencedValue1 = q"sourceParameter1.$paramName"
- val dereferencedValue2 = q"sourceParameter2.$paramName"
- q"$impl.call($derivedImplicit, $dereferencedValue1, $dereferencedValue2)"
- }
- }
+ }.getOrElse {
+ c.abort(c.enclosingPosition, s"failed to get implicit for type $genericType")
+ }
- derivationImplicit match {
- case DerivationImplicit(_) =>
- Some(q"new $genericType(..$implicits)")
- case coderivation: CoderivationImplicit =>
- val namedImplicits = caseClassParameters.zip(implicits).map { case (param, tree) =>
- q"(${param.name.encodedName.toString}, $tree)"
+ (param, derivedImplicit)
+ }.to[List]
+
+ Some {
+ val callables = implicits.map { case (param, imp) =>
+ val label = param.name.toString
+ q"""new _root_.magnolia.Param[$typeConstructor, ${genericType}] {
+ type S = ${param.returnType}
+ def typeclass: ${appliedType(typeConstructor, param.returnType)} = $imp
+ def label: _root_.java.lang.String = $label
+ def dereference(param: ${genericType}): ${param.returnType} = param.${TermName(label)}
+ }"""
}
+
+ val constructor = q"null" //q"""{ => new ${}(..$params) }"""
+
+ val impl = q"""
+ ${c.prefix}.join(${constructor}, $className, _root_.scala.List(..$callables))
+ """
- Some(q"""${coderivation.tree}.join(
- $className,
- _root_.scala.collection.immutable.ListMap(..$namedImplicits)
- )""")
- }
- } else if(isSealedTrait) {
+ q"""
+ def $assignedName: $resultType = $impl
+ $assignedName
+ """
+ }
+ } else if(isSealedTrait) {
- val subtypes = classType.get.knownDirectSubclasses.to[List]
+ val subtypes = classType.get.knownDirectSubclasses.to[List]
- if(subtypes.isEmpty) {
- c.info(c.enclosingPosition, s"magnolia: could not find any direct subtypes of $typeSymbol", true)
- c.abort(c.enclosingPosition, "")
- }
-
- Some {
- val components = subtypes.map(_.asType.toType).map { searchType =>
- recurse(CoproductType(genericType.toString), genericType, assignedName) {
- getImplicit(None, searchType, typeConstructor, assignedName, derivationImplicit)
- }.getOrElse {
- c.abort(c.enclosingPosition, s"failed to get implicit for type $searchType")
- }
+ if(subtypes.isEmpty) {
+ c.info(c.enclosingPosition,
+ s"magnolia: could not find any direct subtypes of $typeSymbol", true)
+
+ c.abort(c.enclosingPosition, "")
}
- derivationImplicit match {
- case DerivationImplicit(impl) =>
- val reduction = components.reduce { (left, right) => q"$impl.combine($left, $right)" }
- q"$impl.call($reduction, sourceParameter)"
-
- case Coderivation2Implicit(impl) =>
- val parts = subtypes.tail.zip(components.tail)
- val base = q"""
- $impl.call(
- ${components.head},
- sourceParameter1.asInstanceOf[${subtypes.head}],
- sourceParameter2.asInstanceOf[${subtypes.head}]
- )
- """
- parts.foldLeft(base) { case (aggregated, (componentType, derivedImplicit)) =>
- q"""
- if(sourceParameter1.isInstanceOf[$componentType] &&
- sourceParameter2.isInstanceOf[$componentType])
- $impl.call($derivedImplicit, sourceParameter1.asInstanceOf[$componentType],
- sourceParameter2.asInstanceOf[$componentType])
- else $aggregated"""
- }
- case Coderivation1Implicit(impl) =>
- val parts = subtypes.zip(components)
-
- val caseClauses = parts.map { case (subtype, component) =>
- cq"(value: $subtype) => $impl.call($component, value)"
- }
+ Some {
- q"""(sourceParameter: @_root_.scala.annotation.switch) match {
- case ..$caseClauses
+ val subclasses = subtypes.map(_.asType.toType).map { searchType =>
+ recurse(CoproductType(genericType.toString), genericType, assignedName) {
+ (searchType, implicitTree(None, searchType, typeConstructor, assignedName))
+ }.getOrElse {
+ c.abort(c.enclosingPosition, s"failed to get implicit for type $searchType")
+ }
+ }.map { case (typ, typeclass) =>
+ val caseClause = cq"(t: $typ) => t"
+ val pf = q"""new _root_.scala.PartialFunction[$genericType, $typ] {
+ def isDefinedAt(t: $genericType): Boolean = t.isInstanceOf[$typ]
+ def apply(t: $genericType): $typ = t.asInstanceOf[$typ]
}"""
- }
- }
- } else None
-
- construct.map { const =>
- derivationImplicit match {
- case DerivationImplicit(_) =>
- ???
- case Coderivation1Implicit(impl) =>
- q"""{
- def $assignedName: $resultType = $impl.construct { sourceParameter => $const }
- $assignedName
+ q"""new _root_.magnolia.Subclass[$typeConstructor, $genericType] {
+ type S = $typ
+ def label: _root_.java.lang.String = ${typ.typeSymbol.name.toString}
+ def typeclass: ${appliedType(typeConstructor, typ)} = $typeclass
+ def cast: _root_.scala.PartialFunction[$genericType, $typ] = $pf
+ }"""
+ }
+
+ val impl = q"""{
+ ${c.prefix}.split(_root_.scala.collection.immutable.List[_root_.magnolia.Subclass[$typeConstructor, $genericType]](..$subclasses))
}"""
- case Coderivation2Implicit(impl) =>
- q"""{
- def $assignedName: $resultType = $impl.construct {
- case (sourceParameter1, sourceParameter2) => $const
- }
+
+ q"""
+ def $assignedName: $resultType = $impl
$assignedName
- }"""
- }
+ """
+ }
+ } else None
}
- }
-
- def magnolia[T: WeakTypeTag, Typeclass: WeakTypeTag, DerivationType: WeakTypeTag]: Tree = {
- import scala.util.{Try, Success, Failure}
val genericType: Type = weakTypeOf[T]
@@ -225,32 +219,17 @@ class Macros(val c: whitebox.Context) {
recursionStack.get(c.enclosingPosition).getOrElse(Stack(List(), List()))
val directlyReentrant = Some(genericType) == currentStack.frames.headOption.map(_.genericType)
- val typeConstructor: Type = weakTypeOf[Typeclass].typeConstructor
- val DerivationTypeclass = weakTypeOf[Derivation[_]].typeConstructor
- val CoderivationTypeclass = weakTypeOf[Coderivation[_]].typeConstructor
- val Coderivation2Typeclass = weakTypeOf[Coderivation2[_]].typeConstructor
-
- val derivationType = appliedType(DerivationTypeclass, List(typeConstructor))
- val coderivationType = appliedType(CoderivationTypeclass, List(typeConstructor))
- val coderivation2Type = appliedType(Coderivation2Typeclass, List(typeConstructor))
-
- val derivationImplicit = weakTypeOf[DerivationType].typeConstructor match {
- case DerivationTypeclass =>
- DerivationImplicit(c.prefix.tree)
- case CoderivationTypeclass =>
- Coderivation1Implicit(c.prefix.tree)
- case Coderivation2Typeclass =>
- Coderivation2Implicit(c.prefix.tree)
- }
-
if(directlyReentrant) throw DirectlyReentrantException()
currentStack.errors.foreach { error =>
if(!emittedErrors.contains(error)) {
emittedErrors += error
val trace = error.path.mkString("\n in ", "\n in ", "\n \n")
- val msg = s"magnolia: could not derive ${typeConstructor} instance for type ${error.genericType}"
+
+ val msg = s"magnolia: could not derive ${typeConstructor} instance for type "+
+ s"${error.genericType}"
+
c.info(c.enclosingPosition, msg+trace, true)
}
}
@@ -258,18 +237,20 @@ class Macros(val c: whitebox.Context) {
val result: Option[Tree] = if(!currentStack.frames.isEmpty) {
findType(genericType) match {
case None =>
- directInferImplicit(genericType, typeConstructor, derivationImplicit)
+ directInferImplicit(genericType, typeConstructor)
case Some(enclosingRef) =>
val methodAsString = enclosingRef.toString
val searchType = appliedType(typeConstructor, genericType)
- Some(q"_root_.magnolia.Lazy[$searchType]($methodAsString)")
+ Some(q"_root_.magnolia.Deferred[$searchType]($methodAsString)")
}
- } else directInferImplicit(genericType, typeConstructor, derivationImplicit)
+ } else directInferImplicit(genericType, typeConstructor)
if(currentStack.frames.isEmpty) recursionStack = ListMap()
result.map { tree =>
- if(currentStack.frames.isEmpty) c.untypecheck(removeLazy.transform(tree)) else tree
+ val out = if(currentStack.frames.isEmpty) c.untypecheck(removeDeferred.transform(tree))
+ else tree
+ out
}.getOrElse {
c.abort(c.enclosingPosition, s"magnolia: could not infer typeclass for type $genericType")
}
@@ -279,22 +260,19 @@ class Macros(val c: whitebox.Context) {
private[magnolia] case class DirectlyReentrantException() extends
Exception("attempt to recurse directly")
-private[magnolia] object Lazy { def apply[T](method: String): T = ??? }
+private[magnolia] object Deferred { def apply[T](method: String): T = ??? }
private[magnolia] object CompileTimeState {
- sealed trait TypePath
- case class CoproductType(typeName: String) extends TypePath {
- override def toString = s"coproduct type $typeName"
- }
-
- case class ProductType(paramName: String, typeName: String) extends TypePath {
- override def toString = s"parameter '$paramName' of product type $typeName"
- }
-
- case class ChainedImplicit(typeName: String) extends TypePath {
- override def toString = s"chained implicit of type $typeName"
- }
+ sealed class TypePath(path: String) { override def toString = path }
+ case class CoproductType(typeName: String) extends
+ TypePath(s"coproduct type $typeName")
+
+ case class ProductType(paramName: String, typeName: String) extends
+ TypePath(s"parameter '$paramName' of product type $typeName")
+
+ case class ChainedImplicit(typeName: String) extends
+ TypePath(s"chained implicit of type $typeName")
case class ImplicitNotFound(genericType: String, path: List[TypePath])
@@ -312,42 +290,6 @@ private[magnolia] object CompileTimeState {
def termName(c: whitebox.Context): c.TermName = term.asInstanceOf[c.TermName]
}
- private[magnolia] var recursionStack: ListMap[api.Position, Stack] =
- ListMap()
-
- private[magnolia] var emittedErrors: Set[ImplicitNotFound] = Set()
-}
-
-abstract class Derivation[Typeclass[_]] {
- type Value
- def dereference(value: Value, param: String): Value
- def call[T](typeclass: Typeclass[T], value: Value): T
- def construct[T](body: Value => T): Typeclass[T]
-
- def combine[Supertype, Right <: Supertype](left: Typeclass[_ <: Supertype],
- right: Typeclass[Right]): Typeclass[Supertype]
-
- implicit def generic[T]: Typeclass[T] = macro Macros.magnolia[T, Typeclass[_],
- Derivation[Tc] forSome { type Tc[_] }]
-}
-
-abstract class Coderivation[Typeclass[_]] {
- type Return
- def call[T](typeclass: Typeclass[T], value: T): Return
- def construct[T](body: T => Return): Typeclass[T]
- def join(name: String, elements: ListMap[String, Return]): Return
-
- implicit def generic[T]: Typeclass[T] = macro Macros.magnolia[T, Typeclass[_],
- Coderivation[Tc] forSome { type Tc[_] }]
+ var recursionStack: ListMap[api.Position, Stack] = ListMap()
+ var emittedErrors: Set[ImplicitNotFound] = Set()
}
-
-abstract class Coderivation2[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(name: String, elements: ListMap[String, Return]): Return
-
- implicit def generic[T]: Typeclass[T] = macro Macros.magnolia[T, Typeclass[_],
- Coderivation2[Tc] forSome { type Tc[_] }]
-}
-
diff --git a/examples/src/main/scala/cats.scala b/examples/src/main/scala/cats.scala
deleted file mode 100644
index 2283ce0..0000000
--- a/examples/src/main/scala/cats.scala
+++ /dev/null
@@ -1,19 +0,0 @@
-package magnolia.examples
-
-import scala.collection.immutable.ListMap
-import scala.language.experimental.macros
-
-import cats.Show
-import magnolia.{Coderivation, Macros}
-
-object catsShowDerivation {
-
- val ShowDerivation = new Coderivation[Show] {
- type Return = String
- def call[T](show: Show[T], value: T): String = show.show(value)
- def construct[T](body: T => String): Show[T] = body(_)
- def join(name: String, xs: ListMap[String, String]): String =
- xs.map { case (k, v) => s"$k=$v" }.mkString(s"$name(", ", ", ")")
- }
-
-}
diff --git a/examples/src/main/scala/eq.scala b/examples/src/main/scala/eq.scala
deleted file mode 100644
index 5a4c9fa..0000000
--- a/examples/src/main/scala/eq.scala
+++ /dev/null
@@ -1,50 +0,0 @@
-package magnolia.examples
-
-import magnolia._
-
-import language.experimental.macros
-import collection.immutable.ListMap
-
-object `package` {
- 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) extends Tree
-
-sealed trait Entity
-case class Person(name: String, address: Address) extends Entity
-case class Organization(name: String, contacts: Set[Person]) extends Entity
-case class Address(lines: List[String], country: Country)
-case class Country(name: String, code: String, salesTax: Boolean)
-
-trait Eq[T] { def isEqual(a: T, b: T): Boolean }
-
-object Eq {
-
- implicit val eqInt: Eq[Int] = _ == _
-
- val derivation: Coderivation2[Eq] = new Coderivation2[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(className: String, elements: ListMap[String, Boolean]): Boolean =
- elements.forall(_._2)
- }
-
-}
diff --git a/examples/src/main/scala/typeclasses.scala b/examples/src/main/scala/typeclasses.scala
new file mode 100644
index 0000000..bc5df94
--- /dev/null
+++ b/examples/src/main/scala/typeclasses.scala
@@ -0,0 +1,93 @@
+package magnolia.examples
+
+import scala.collection.immutable.ListMap
+import scala.language.existentials
+import scala.language.higherKinds
+
+import magnolia._
+import scala.reflect._
+import scala.reflect.macros._
+import scala.language.experimental.macros
+import scala.annotation.unchecked.uncheckedVariance
+
+
+object Show {
+ def join[T](construct: Any, className: String, elems: List[Param[Show, T]])(value: T): String =
+ elems.map { call => s"${call.label}=${call.typeclass.show(call.dereference(value))}" }.mkString(s"{", ",", "}")
+
+ def split[T](subclasses: List[Magnolia.Subclass[Show, T]])(value: T): String =
+ subclasses.map { sub => sub.cast.andThen { value =>
+ sub.typeclass.show(sub.cast(value))
+ } }.reduce(_ orElse _)(value)
+
+ implicit val string: Show[String] = identity
+ implicit val int: Show[Int] = new Show[Int] { def show(s: Int): String = s.toString }
+ implicit def generic[T]: Show[T] = macro Magnolia.generic[T]
+}
+
+trait Show[T] { def show(value: T): String }
+
+object Eq {
+ def join[T](construct: Any, className: String, elems: List[Param[Eq, T]])(param1: T, param2: T): Boolean =
+ elems.forall { case call => call.typeclass.equal(call.dereference(param1), call.dereference(param2)) }
+
+ def split[T](subclasses: List[Magnolia.Subclass[Eq, T]])(param1: T, param2: T): Boolean =
+ subclasses.map { case subclass =>
+ subclass.cast.andThen { value => subclass.typeclass.equal(subclass.cast(param1), subclass.cast(param2)) }
+ }.reduce(_ orElse _)(param1)
+
+ implicit val string: Eq[String] = _ == _
+ implicit val int: Eq[Int] = _ == _
+ implicit def generic[T]: Eq[T] = macro Magnolia.generic[T]
+}
+
+trait Eq[T] { def equal(value: T, value2: T): Boolean }
+
+object Default {
+ case class Call[T](label: String, typeclass: Default[T])
+ case class Subclass[T](label: String, typeclass: Default[T], cast: PartialFunction[_ >: T, T])
+
+ def join[T](construct: ((Call[R] => R) forSome { type R }) => T, className: String, elems: List[Call[_]]): T =
+ construct { call: Call[_] => call.typeclass.default }
+
+ def split[T](subclasses: List[Subclass[T]])(param: T): T = subclasses.head.typeclass.default
+
+
+ implicit val string: Default[String] = new Default[String] { def default: String = "" }
+ implicit val int: Default[Int] = new Default[Int] { def default: Int = 0 }
+ implicit def generic[T]: Default[T] = macro Magnolia.generic[T]
+}
+
+trait Default[T] { def default: T }
+
+object Decoder {
+ case class Call[T](label: String, typeclass: Decoder[T], value: String)
+
+ case class Subclass[T](label: String, typeclass: Decoder[T], cast: PartialFunction[_ >: T, T])
+
+ def join[T](construct: ((Call[R] => R) forSome { type R }) => T, className: String, elems: List[Call[_]]): T =
+ construct { call: Call[_] => call.typeclass.decode(call.value) }
+
+ def split[T](subclasses: List[Subclass[T]])(param: String): T =
+ subclasses.map { case Subclass(name, typeclass, cast) =>
+ PartialFunction[String, T] { case _ if decodes(typeclass, param) => typeclass.decode(param) }
+ }.reduce(_ orElse _)(param)
+
+ def decodes[T](tc: Decoder[T], s: String): Boolean = try { decodes(tc, s); true } catch { case e: Exception => false }
+
+ implicit val string: Decoder[String] = new Decoder[String] { def decode(str: String): String = str }
+ implicit val int: Decoder[Int] = new Decoder[Int] { def decode(str: String): Int = str.toInt }
+ implicit def generic[T]: Decoder[T] = macro Magnolia.generic[T]
+}
+
+trait Decoder[T] { def decode(str: String): T }
+
+sealed trait Tree
+case class Leaf(value: String) extends Tree
+case class Branch(left: Tree, right: Tree) extends Tree
+
+sealed trait Entity
+
+case class Company(name: String) extends Entity
+case class Person(name: String, age: Int) extends Entity
+case class Address(line1: String, occupant: Person)
diff --git a/tests/shared/src/main/scala/magnolia/main.scala b/tests/shared/src/main/scala/magnolia/main.scala
deleted file mode 100644
index 9d7accb..0000000
--- a/tests/shared/src/main/scala/magnolia/main.scala
+++ /dev/null
@@ -1,24 +0,0 @@
-package magnolia
-
-import examples.{Address, Branch, Country, Entity, Leaf, Person, Eq}
-import cats.instances.all._
-import cats.syntax.all._
-import examples.catsShowDerivation.ShowDerivation.generic
-import examples.catsShowDerivation.ShowDerivation
-import language.experimental.macros
-
-object Main {
-
- def main(args: Array[String]): Unit = {
- val tree1 = Branch(Branch(Leaf(1), Leaf(2)), Leaf(3))
-
- println(tree1.show)
-
- println(List[Entity](Person("John Smith",
- Address(List("1 High Street", "London", "SW1A 1AA"),
- Country("UK", "GBR", false)))).show)
-
- import Eq.derivation.generic
- println(tree1 isEqualTo tree1)
- }
-}
diff --git a/tests/src/main/scala/main.scala b/tests/src/main/scala/main.scala
new file mode 100644
index 0000000..7dee065
--- /dev/null
+++ b/tests/src/main/scala/main.scala
@@ -0,0 +1,81 @@
+package magnolia.tests
+
+import magnolia._
+import estrapade._
+import contextual.data.scalac._
+import contextual.data.fqt._
+import contextual.data.txt._
+
+import scala.util._
+
+object Tests extends TestApp {
+
+ def tests() = {
+
+ test("construct a Show product instance") {
+ import examples._
+ Show.generic[Person].show(Person("John Smith", 34))
+ }.assert(_ == """{name=John Smith,age=34}""")
+
+ test("construct a Show coproduct instance") {
+ import examples._
+ Show.generic[Person].show(Person("John Smith", 34))
+ }.assert(_ == "{name=John Smith,age=34}")
+
+ test("serialize a Branch") {
+ import magnolia.examples._
+ implicitly[Show[Branch]].show(Branch(Leaf("LHS"), Leaf("RHS")))
+ }.assert(_ == "{left={value=LHS},right={value=RHS}}")
+
+ test("test equality false") {
+ import examples._
+ Eq.generic[Entity].equal(Person("John Smith", 34), Person("", 0))
+ }.assert(_ == false)
+
+ test("test equality true") {
+ import examples._
+ Eq.generic[Entity].equal(Person("John Smith", 34), Person("John Smith", 34))
+ }.assert(_ == true)
+
+ test("test branch equality true") {
+ import examples._
+ Eq.generic[Tree].equal(Branch(Leaf("one"), Leaf("two")), Branch(Leaf("one"), Leaf("two")))
+ }.assert(_ == true)
+
+ /*test("construction of Show instance for Leaf") {
+ scalac"""
+ import magnolia.examples._
+ implicitly[Show[Leaf]]
+ """
+ }.assert(_ == Returns(fqt"magnolia.examples.Show[magnolia.examples.Leaf]"))
+
+ test("construction of Show instance for Tree") {
+ scalac"""
+ import magnolia.examples._
+ implicitly[Show[Tree]]
+ """
+ }.assert(_ == Returns(fqt"magnolia.examples.Show[magnolia.examples.Tree]"))
+
+ test("serialize a Leaf") {
+ import magnolia.examples._
+ implicitly[Show[Leaf]].show(Leaf("testing"))
+ }.assert(_ == "{value=testing}")
+
+ test("serialize a Branch as a Tree") {
+ import magnolia.examples._
+ implicitly[Show[Tree]].show(Branch(Leaf("LHS"), Leaf("RHS")))
+ }.assert(_ == "{left={value=LHS},right={value=RHS}}")
+
+ test("show error stack") {
+ scalac"""
+ import magnolia.examples._
+ case class Alpha(integer: Int)
+ case class Beta(alpha: Alpha)
+ Show.generic[Beta]
+ """
+ }.assert(_ == TypecheckError(txt"""magnolia: could not find typeclass for type Int
+ | in parameter 'integer' of product type Alpha
+ | in parameter 'alpha' of product type Beta
+ |"""))*/
+ }
+}