aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Pretty <jon.pretty@propensive.com>2018-01-09 10:02:39 +0000
committerJon Pretty <jon.pretty@propensive.com>2018-01-09 10:02:39 +0000
commit6431fba02b1663701564adacef07a292ff7c32d7 (patch)
treef4a56d098df94013bf8c8ea171c8dc7afc33825f
parent2353f9f1d3961a29a1d7b6173f5fd22d7acf0f47 (diff)
downloadmagnolia-virtual-params-3.tar.gz
magnolia-virtual-params-3.tar.bz2
magnolia-virtual-params-3.zip
Make access of combine and dispatch lazyvirtual-params-3
-rw-r--r--core/shared/src/main/scala/magnolia.scala38
-rw-r--r--examples/shared/src/main/scala/default.scala16
-rw-r--r--tests/src/main/scala/tests.scala10
3 files changed, 43 insertions, 21 deletions
diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala
index 759deb6..c0fc10d 100644
--- a/core/shared/src/main/scala/magnolia.scala
+++ b/core/shared/src/main/scala/magnolia.scala
@@ -96,30 +96,26 @@ object Magnolia {
}
}
- val typeclassConstructor = getTypeMember("Typeclass")
-
- def checkMethod(termName: String, category: String, expected: String): Unit = {
+ def getMethod(termName: String)(fn: MethodSymbol => Tree): Option[Tree] = {
val term = TermName(termName)
- val combineClass = prefixType.baseClasses
- .find { cls =>
- cls.asType.toType.decl(term) != NoSymbol
- }
- .getOrElse {
- fail(s"the method `$termName` must be defined on the derivation $prefixObject to derive typeclasses for $category")
- }
- val firstParamBlock = combineClass.asType.toType.decl(term).asTerm.asMethod.paramLists.head
- if (firstParamBlock.lengthCompare(1) != 0)
- fail(s"the method `combine` should take a single parameter of type $expected")
+ val cls = prefixType.baseClasses.find(_.asType.toType.decl(term) != NoSymbol)
+ cls.map(_.asType.toType.decl(term).asTerm.asMethod).map(fn)
}
- // FIXME: Only run these methods if they're used, particularly `dispatch`
- checkMethod("combine", "case classes", "CaseClass[Typeclass, _]")
- checkMethod("dispatch", "sealed traits", "SealedTrait[Typeclass, _]")
+ val typeclassConstructor = getTypeMember("Typeclass")
+
+ lazy val combineMethod = getMethod("combine") { m => q"${c.prefix}.combine" }.getOrElse {
+ fail("the method `combine` should be defined on the derivation object")
+ }
+
+ lazy val dispatchMethod = getMethod("dispatch") { m => q"${c.prefix}.dispatch" }.getOrElse {
+ fail("the method `dispatch` should be defined on the derivation object")
+ }
val removeDeferred = new Transformer {
override def transform(tree: Tree) = tree match {
- case q"$magnolia.Deferred.apply[$_](${Literal(Constant(method: String))})"
- if magnolia.symbol == magnoliaPkg =>
+ case q"$pkg.Deferred.apply[$_](${Literal(Constant(method: String))})"
+ if pkg.symbol == magnoliaPkg =>
q"${TermName(method)}"
case _ =>
super.transform(tree)
@@ -183,7 +179,7 @@ object Magnolia {
val result = if (isCaseObject) {
val impl = q"""
$typeNameDef
- ${c.prefix}.combine($magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType](
+ $combineMethod($magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType](
$typeName, true, false, new $scalaPkg.Array(0), _ => ${genericType.typeSymbol.asClass.module})
)
"""
@@ -277,7 +273,7 @@ object Magnolia {
$typeNameDef
- ${c.prefix}.combine($magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType](
+ $combineMethod($magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType](
$typeName,
false,
$isValueClass,
@@ -342,7 +338,7 @@ object Magnolia {
$typeNameDef
- ${c.prefix}.dispatch(new $magnoliaPkg.SealedTrait(
+ $dispatchMethod(new $magnoliaPkg.SealedTrait(
$typeName,
$subtypesVal: $scalaPkg.Array[$magnoliaPkg.Subtype[$typeConstructor, $genericType]])
): $resultType
diff --git a/examples/shared/src/main/scala/default.scala b/examples/shared/src/main/scala/default.scala
index 4b781a5..fb5966f 100644
--- a/examples/shared/src/main/scala/default.scala
+++ b/examples/shared/src/main/scala/default.scala
@@ -36,3 +36,19 @@ object Default {
/** generates default instances of [[Default]] for case classes and sealed traits */
implicit def gen[T]: Default[T] = macro Magnolia.gen[T]
}
+
+object DefaultNoCoproduct {
+
+ type Typeclass[T] = Default[T]
+
+ /** constructs a default for each parameter, using the constructor default (if provided),
+ * otherwise using a typeclass-provided default */
+ def combine[T](ctx: CaseClass[Default, T]): Default[T] = new Default[T] {
+ def default = ctx.construct { param =>
+ param.default.getOrElse(param.typeclass.default)
+ }
+ }
+
+ /** generates default instances of [[Default]] for case classes and sealed traits */
+ implicit def gen[T]: Default[T] = macro Magnolia.gen[T]
+}
diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala
index f56afcb..909ee82 100644
--- a/tests/src/main/scala/tests.scala
+++ b/tests/src/main/scala/tests.scala
@@ -28,6 +28,7 @@ class Length(val value: Int) extends AnyVal
case class FruitBasket(fruits: Fruit*)
case class Lunchbox(fruit: Fruit, drink: String)
+
object Fruit {
implicit val showFruit: Show[String, Fruit] =
new Show[String, Fruit] { def show(f: Fruit): String = f.name }
@@ -45,6 +46,7 @@ case class `%%`(`/`: Int, `#`: String)
case class Param(a: String, b: String)
case class Test(param: Param)
+
object Test {
def apply(): Test = Test(Param("", ""))
@@ -355,5 +357,13 @@ object Tests extends TestApp {
Show.gen[Path[String]].show(OffRoad(Some(Crossroad(Destination("A"), Destination("B")))))
}.assert(_ == "OffRoad(path=Crossroad(left=Destination(value=A),right=Destination(value=B)))")
+
+ test("does not derive for coproduct without dispatch defined") {
+ scalac"DefaultNoCoproduct.gen[Entity]"
+ }.assert(_ == TypecheckError("magnolia: the method `dispatch` should be defined on the derivation object"))
+
+ test("still derives for case class without dispatch defined") {
+ DefaultNoCoproduct.gen[Company].default
+ }.assert(_ == Company(""))
}
}