aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Pretty <jon.pretty@propensive.com>2017-11-10 15:23:38 +0000
committerGitHub <noreply@github.com>2017-11-10 15:23:38 +0000
commit9c90d3f393d18ef0f8f27751a8328913dad7ad25 (patch)
tree345f7f8a497f5fc12e2aaad9cf9e8119e0059d62
parent657d2fb2c16b75c790bf3c3a63d1f830a42893db (diff)
parent2ae9a65ed0b773f77b5cd8d8c134a125bbaccf5d (diff)
downloadmagnolia-9c90d3f393d18ef0f8f27751a8328913dad7ad25.tar.gz
magnolia-9c90d3f393d18ef0f8f27751a8328913dad7ad25.tar.bz2
magnolia-9c90d3f393d18ef0f8f27751a8328913dad7ad25.zip
Merge pull request #27 from propensive/allow-type-constructor-in-supertrait
Allow type constructor in supertrait
-rw-r--r--build.sbt5
-rw-r--r--core/src/main/scala/magnolia.scala23
-rw-r--r--examples/src/main/scala/show.scala28
-rw-r--r--tests/src/main/scala/tests.scala4
4 files changed, 43 insertions, 17 deletions
diff --git a/build.sbt b/build.sbt
index 628088f..c5d6d74 100644
--- a/build.sbt
+++ b/build.sbt
@@ -77,7 +77,10 @@ lazy val publishSettings = Seq(
import java.io.File
-lazy val unmanagedSettings = unmanagedBase := (scalaVersion.value.split("\\.").map(_.toInt).to[List] match {
+lazy val unmanagedSettings = unmanagedBase := (scalaVersion.value
+ .split("\\.")
+ .map(_.toInt)
+ .to[List] match {
case List(2, 12, _) => baseDirectory.value / "lib" / "2.12"
case List(2, 11, _) => baseDirectory.value / "lib" / "2.11"
})
diff --git a/core/src/main/scala/magnolia.scala b/core/src/main/scala/magnolia.scala
index 293d0f1..9aa5d5f 100644
--- a/core/src/main/scala/magnolia.scala
+++ b/core/src/main/scala/magnolia.scala
@@ -71,8 +71,21 @@ object Magnolia {
val magnoliaObj = q"$magnoliaPkg.Magnolia"
val arrayCls = tq"_root_.scala.Array"
- val typeConstructor: c.Type =
- c.prefix.tree.tpe.member(TypeName("Typeclass")).asType.toType.typeConstructor
+ val prefixType = c.prefix.tree.tpe
+
+ val typeDefs = prefixType.baseClasses.flatMap { cls =>
+ cls.asType.toType.decls.filter(_.isType).find(_.name.toString == "Typeclass").map { tpe =>
+ tpe.asType.toType.asSeenFrom(prefixType, cls)
+ }
+ }
+
+ val typeConstructorOpt =
+ typeDefs.headOption.map(_.typeConstructor)
+
+ val typeConstructor = typeConstructorOpt.getOrElse {
+ c.abort(c.enclosingPosition,
+ "magnolia: the derivation object does not define the Typeclass type constructor")
+ }
def findType(key: Type): Option[TermName] =
recursionStack(c.enclosingPosition).frames.find(_.genericType == key).map(_.termName(c))
@@ -258,9 +271,9 @@ object Magnolia {
$paramsVal,
($fnVal: Param[$typeConstructor, $genericType] => Any) =>
new $genericType(..${caseParams.zipWithIndex.map {
- case (typeclass, idx) =>
- q"$fnVal($paramsVal($idx)).asInstanceOf[${typeclass.paramType}]"
- } })
+ case (typeclass, idx) =>
+ q"$fnVal($paramsVal($idx)).asInstanceOf[${typeclass.paramType}]"
+ }})
))
}"""
)
diff --git a/examples/src/main/scala/show.scala b/examples/src/main/scala/show.scala
index e9e74b1..e101c11 100644
--- a/examples/src/main/scala/show.scala
+++ b/examples/src/main/scala/show.scala
@@ -9,43 +9,51 @@ import scala.language.experimental.macros
* be something other than a string. */
trait Show[Out, T] { def show(value: T): Out }
-/** companion object to [[Show]] */
-object Show {
+trait GenericShow[Out] {
/** the type constructor for new [[Show]] instances
*
* The first parameter is fixed as `String`, and the second parameter varies generically. */
- type Typeclass[T] = Show[String, T]
+ type Typeclass[T] = Show[Out, T]
+
+ def join(typeName: String, strings: Seq[String]): Out
/** creates a new [[Show]] instance by labelling and joining (with `mkString`) the result of
* showing each parameter, and prefixing it with the class name */
- def combine[T](ctx: CaseClass[Typeclass, T]): Show[String, T] = new Show[String, T] {
+ def combine[T](ctx: CaseClass[Typeclass, T]): Show[Out, T] = new Show[Out, T] {
def show(value: T) = {
val paramStrings = ctx.parameters.map { param =>
s"${param.label}=${param.typeclass.show(param.dereference(value))}"
}
- paramStrings.mkString(s"${ctx.typeName.split("\\.").last}(", ",", ")")
+ join(ctx.typeName.split("\\.").last, paramStrings)
}
}
/** choose which typeclass to use based on the subtype of the sealed trait */
- def dispatch[T](ctx: SealedTrait[Typeclass, T]): Show[String, T] = new Show[String, T] {
- def show(value: T): String = ctx.dispatch(value) { sub =>
+ def dispatch[T](ctx: SealedTrait[Typeclass, T]): Show[Out, T] = new Show[Out, T] {
+ def show(value: T): Out = ctx.dispatch(value) { sub =>
sub.typeclass.show(sub.cast(value))
}
}
+ /** bind the Magnolia macro to this derivation object */
+ implicit def gen[T]: Show[Out, T] = macro Magnolia.gen[T]
+}
+
+/** companion object to [[Show]] */
+object Show extends GenericShow[String] {
+
/** show typeclass for strings */
implicit val string: Show[String, String] = new Show[String, String] {
def show(s: String): String = s
}
+ def join(typeName: String, params: Seq[String]): String =
+ params.mkString(s"$typeName(", ",", ")")
+
/** show typeclass for integers */
implicit val int: Show[String, Int] = new Show[String, Int] {
def show(s: Int): String = s.toString
}
-
- /** bind the Magnolia macro to this derivation object */
- implicit def gen[T]: Show[String, T] = macro Magnolia.gen[T]
}
diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala
index 10eb465..5142861 100644
--- a/tests/src/main/scala/tests.scala
+++ b/tests/src/main/scala/tests.scala
@@ -55,7 +55,9 @@ object Tests extends TestApp {
test("local implicit beats Magnolia") {
import magnolia.examples._
- implicit val showPerson: Show[String, Person] = new Show[String, Person] { def show(p: Person) = "nobody" }
+ implicit val showPerson: Show[String, Person] = new Show[String, Person] {
+ def show(p: Person) = "nobody"
+ }
implicitly[Show[String, Address]].show(Address("Home", Person("John Smith", 44)))
}.assert(_ == "Address(line1=Home,occupant=nobody)")