diff options
author | Georgi Krastev <joro.kr.21@gmail.com> | 2018-01-05 14:48:53 +0100 |
---|---|---|
committer | Georgi Krastev <joro.kr.21@gmail.com> | 2018-01-06 17:46:24 +0100 |
commit | e3fddd33620eb5d124d271b7f27859295ef2d267 (patch) | |
tree | 9dccf32b335f2e7042617ad3b461e71f8759d428 | |
parent | c0a73e04f9d902e1dbb66182485d7da3ee7097b6 (diff) | |
download | magnolia-e3fddd33620eb5d124d271b7f27859295ef2d267.tar.gz magnolia-e3fddd33620eb5d124d271b7f27859295ef2d267.tar.bz2 magnolia-e3fddd33620eb5d124d271b7f27859295ef2d267.zip |
Rework compile time stack
* Use a classic mutable stack (a case class without lenses is cumbersome)
* Add typeclass constructor to stack frames, cache and error messages
* Clean-up usage of `Option`s
-rw-r--r-- | core/shared/src/main/scala/magnolia.scala | 322 | ||||
-rw-r--r-- | tests/src/main/scala/tests.scala | 101 |
2 files changed, 195 insertions, 228 deletions
diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index b5c196a..3135b57 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -1,11 +1,9 @@ package magnolia -import scala.util.control.NonFatal -import scala.reflect._ -import macros._ -import scala.collection.immutable.ListMap -import language.existentials -import language.higherKinds +import scala.collection.mutable +import scala.language.existentials +import scala.language.higherKinds +import scala.reflect.macros._ /** the object which defines the Magnolia macro */ object Magnolia { @@ -65,7 +63,7 @@ object Magnolia { * will suffice, however the qualifications regarding additional type parameters and implicit * parameters apply equally to `dispatch` as to `combine`. * */ - def gen[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = { + def gen[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = Stack.withContext(c) { stack => import c.universe._ import internal._ @@ -73,13 +71,15 @@ object Magnolia { .find(_.tree.tpe <:< typeOf[debug]) .flatMap(_.tree.children.tail.collectFirst { case Literal(Constant(s: String)) => s }) - val magnoliaPkg = q"_root_.magnolia" - val scalaPkg = q"_root_.scala" + val magnoliaPkg = c.mirror.staticPackage("magnolia") + val scalaPkg = c.mirror.staticPackage("scala") val repeatedParamClass = definitions.RepeatedParamClass val scalaSeqType = typeOf[Seq[_]].typeConstructor val prefixType = c.prefix.tree.tpe + val prefixObject = prefixType.typeSymbol + val prefixName = prefixObject.name.decodedName val typeDefs = prefixType.baseClasses.flatMap { cls => cls.asType.toType.decls.filter(_.isType).find(_.name.toString == "Typeclass").map { tpe => @@ -87,13 +87,10 @@ object Magnolia { } } - val typeConstructorOpt = - typeDefs.headOption.map(_.typeConstructor) - - val typeConstructor = typeConstructorOpt.getOrElse { + val typeConstructor = typeDefs.headOption.fold { c.abort(c.enclosingPosition, - "magnolia: the derivation object does not define the Typeclass type constructor") - } + s"magnolia: the derivation $prefixObject does not define the Typeclass type constructor") + } (_.typeConstructor) def checkMethod(termName: String, category: String, expected: String): Unit = { val term = TermName(termName) @@ -104,7 +101,7 @@ object Magnolia { .getOrElse { c.abort( c.enclosingPosition, - s"magnolia: the method `$termName` must be defined on the derivation object to derive typeclasses for $category" + s"magnolia: 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 @@ -117,91 +114,43 @@ object Magnolia { checkMethod("combine", "case classes", "CaseClass[Typeclass, _]") checkMethod("dispatch", "sealed traits", "SealedTrait[Typeclass, _]") - def findType(key: Type): Option[TermName] = - recursionStack(c.enclosingPosition).frames.find(_.genericType == key).map(_.termName(c)) - - final case class Typeclass(typ: c.Type, tree: c.Tree) - - def recurse[A](path: TypePath, key: Type, value: TermName)(fn: => A): Option[A] = { - val oldRecursionStack = recursionStack.get(c.enclosingPosition) - recursionStack = recursionStack.updated( - c.enclosingPosition, - oldRecursionStack.map(_.push(path, key, value)).getOrElse { - Stack(Map(), List(Frame(path, key, value)), Nil) - } - ) - - try Some(fn) - catch { case NonFatal(_) => None } finally { - val currentStack = recursionStack(c.enclosingPosition) - recursionStack = recursionStack.updated(c.enclosingPosition, currentStack.pop()) - } - } - - val removeDeferred: Transformer = new Transformer { - override def transform(tree: Tree): Tree = tree match { - case q"$magnoliaPkg.Deferred.apply[$returnType](${Literal(Constant(method: String))})" => - q"${TermName(method)}" + val removeDeferred = new Transformer { + override def transform(tree: Tree) = tree match { + case q"$magnolia.Deferred.apply[$_](${Literal(Constant(method: String))})" + if magnolia.symbol == magnoliaPkg => q"${TermName(method)}" case _ => super.transform(tree) } } - def typeclassTree(paramName: Option[String], - genericType: Type, - typeConstructor: Type, - assignedName: TermName): Tree = { - + def typeclassTree(genericType: Type, typeConstructor: Type): Tree = { val searchType = appliedType(typeConstructor, genericType) - - val deferredRef = findType(genericType).map { methodName => + val deferredRef = for (methodName <- stack find searchType) yield { val methodAsString = methodName.decodedName.toString q"$magnoliaPkg.Deferred.apply[$searchType]($methodAsString)" } - val foundImplicit = deferredRef.orElse { - val (inferredImplicit, newStack) = - recursionStack(c.enclosingPosition).lookup(c)(searchType) { - val implicitSearchTry = scala.util.Try { - val genericTypeName: String = - genericType.typeSymbol.name.decodedName.toString.toLowerCase - - val assignedName: TermName = TermName(c.freshName(s"${genericTypeName}Typeclass")) - - recurse(ChainedImplicit(genericType.toString), genericType, assignedName) { - c.inferImplicitValue(searchType, false, false) - }.get + deferredRef.getOrElse { + val path = ChainedImplicit(s"$prefixName.Typeclass", genericType.toString) + val frame = stack.Frame(path, searchType, termNames.EMPTY) + stack.recurse(frame, searchType) { + Option(c.inferImplicitValue(searchType)).filterNot(_.isEmpty) + .orElse(directInferImplicit(genericType, typeConstructor)) + .getOrElse { + val missingType = stack.top.fold(searchType)(_.searchType.asInstanceOf[Type]) + val typeClassName = s"${missingType.typeSymbol.name.decodedName}.Typeclass" + val genericType = missingType.typeArgs.head + val trace = stack.trace.mkString(" in ", "\n in ", "\n") + c.abort(c.enclosingPosition, + s"magnolia: could not find $typeClassName for type $genericType\n$trace") } - - implicitSearchTry.toOption.orElse( - directInferImplicit(genericType, typeConstructor).map(_.tree) - ) - } - recursionStack = recursionStack.updated(c.enclosingPosition, newStack) - inferredImplicit - } - - foundImplicit.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 stackPaths = recursionStack(c.enclosingPosition).frames.map(_.path) - val stack = stackPaths.mkString(" in ", "\n in ", "\n") - - c.abort(c.enclosingPosition, - s"magnolia: could not find typeclass for type $genericType\n$stack") + } } } - def directInferImplicit(genericType: c.Type, typeConstructor: Type): Option[Typeclass] = { - - val genericTypeName: String = genericType.typeSymbol.name.decodedName.toString.toLowerCase - val assignedName: TermName = TermName(c.freshName(s"${genericTypeName}Typeclass")) + def directInferImplicit(genericType: Type, typeConstructor: Type): Option[Tree] = { + val genericTypeName = genericType.typeSymbol.name.decodedName.toString.toLowerCase + val assignedName = TermName(c.freshName(s"${genericTypeName}Typeclass")) val typeSymbol = genericType.typeSymbol val classType = if (typeSymbol.isClass) Some(typeSymbol.asClass) else None val isCaseClass = classType.exists(_.isCaseClass) @@ -235,18 +184,18 @@ object Magnolia { $typeName, true, false, new $scalaPkg.Array(0), _ => ${genericType.typeSymbol.asClass.module}) ) """ - Some(Typeclass(genericType, impl)) + Some(impl) } else if (isCaseClass || isValueClass) { val caseClassParameters = genericType.decls.collect { case m: MethodSymbol if m.isCaseAccessor || (isValueClass && m.isParamAccessor) => m.asMethod } - final case class CaseParam(sym: c.universe.MethodSymbol, + case class CaseParam(sym: MethodSymbol, repeated: Boolean, - typeclass: c.Tree, - paramType: c.Type, - ref: c.TermName) + typeclass: Tree, + paramType: Type, + ref: TermName) val caseParamsReversed = caseClassParameters.foldLeft[List[CaseParam]](Nil) { (acc, param) => @@ -260,23 +209,18 @@ object Magnolia { false -> tpe } - val predefinedRef = acc.find(_.paramType == paramType) - - val caseParamOpt = predefinedRef.map { backRef => - CaseParam(param, repeated, q"()", paramType, backRef.ref) :: acc - } - - caseParamOpt.getOrElse { - val derivedImplicit = - recurse(ProductType(paramName, genericType.toString), genericType, assignedName) { - typeclassTree(Some(paramName), paramType, typeConstructor, assignedName) - }.getOrElse( - c.abort(c.enclosingPosition, s"failed to get implicit for type $genericType") - ) + acc.find(_.paramType =:= paramType).fold { + val path = ProductType(paramName, genericType.toString) + val frame = stack.Frame(path, resultType, assignedName) + val derivedImplicit = stack.recurse(frame, appliedType(typeConstructor, paramType)) { + typeclassTree(paramType, typeConstructor) + } val ref = TermName(c.freshName("paramTypeclass")) - val assigned = q"""val $ref = $derivedImplicit""" + val assigned = q"""lazy val $ref = $derivedImplicit""" CaseParam(param, repeated, assigned, paramType, ref) :: acc + } { backRef => + CaseParam(param, repeated, q"()", paramType, backRef.ref) :: acc } } @@ -288,7 +232,7 @@ object Magnolia { val preAssignments = caseParams.map(_.typeclass) val defaults = if (!isValueClass) { - val companionRef = GlobalUtil.patchedCompanionRef(c)(genericType) + val companionRef = GlobalUtil.patchedCompanionRef(c)(genericType.dealias) val companionSym = companionRef.symbol.asModule.info // If a companion object is defined with alternative apply methods @@ -318,10 +262,7 @@ object Magnolia { )""" } - Some( - Typeclass( - genericType, - q"""{ + Some(q"""{ ..$preAssignments val $paramsVal: $scalaPkg.Array[$magnoliaPkg.Param[$typeConstructor, $genericType]] = new $scalaPkg.Array(${assignments.length}) @@ -346,7 +287,6 @@ object Magnolia { } })})) }""") - ) } else if (isSealedTrait) { val genericSubtypes = classType.get.knownDirectSubclasses.to[List] val subtypes = genericSubtypes.map { sub => @@ -362,18 +302,18 @@ object Magnolia { if (subtypes.isEmpty) { c.info(c.enclosingPosition, s"magnolia: could not find any direct subtypes of $typeSymbol", - true) + force = true) c.abort(c.enclosingPosition, "") } val subtypesVal: TermName = TermName(c.freshName("subtypes")) - val typeclasses = subtypes.map { searchType => - recurse(CoproductType(genericType.toString), genericType, assignedName) { - (searchType, typeclassTree(None, searchType, typeConstructor, assignedName)) - }.getOrElse { - c.abort(c.enclosingPosition, s"failed to get implicit for type $searchType") + val typeclasses = for (subType <- subtypes) yield { + val path = CoproductType(genericType.toString) + val frame = stack.Frame(path, resultType, assignedName) + subType -> stack.recurse(frame, appliedType(typeConstructor, subType)) { + typeclassTree(subType, typeConstructor) } } @@ -386,11 +326,8 @@ object Magnolia { (t: $genericType) => t.asInstanceOf[$typ] )""" } - - Some { - Typeclass( - genericType, - q"""{ + + Some(q"""{ val $subtypesVal: $scalaPkg.Array[$magnoliaPkg.Subtype[$typeConstructor, $genericType]] = new $scalaPkg.Array(${assignments.size}) @@ -402,64 +339,37 @@ object Magnolia { $typeName, $subtypesVal: $scalaPkg.Array[$magnoliaPkg.Subtype[$typeConstructor, $genericType]]) ): $resultType - }""" - ) - } + }""") } else None - result.map { - case Typeclass(t, r) => - Typeclass(t, q"""{ - def $assignedName: $resultType = $r - $assignedName - }""") - } + for (term <- result) yield q"""{ + lazy val $assignedName: $resultType = $term + $assignedName + }""" } val genericType: Type = weakTypeOf[T] - - val currentStack: Stack = - recursionStack.getOrElse(c.enclosingPosition, Stack(Map(), List(), List())) - - val directlyReentrant = currentStack.frames.headOption.exists(_.genericType == genericType) - + val searchType = appliedType(typeConstructor, genericType) + val directlyReentrant = stack.top.exists(_.searchType =:= searchType) 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 " + - s"${error.genericType}" - - c.info(c.enclosingPosition, msg + trace, true) - } + val result = stack.find(searchType).map { enclosingRef => + q"$magnoliaPkg.Deferred[$searchType](${enclosingRef.toString})" + }.orElse { + directInferImplicit(genericType, typeConstructor) } - val result: Option[Tree] = if (currentStack.frames.nonEmpty) { - findType(genericType) match { - case None => - directInferImplicit(genericType, typeConstructor).map(_.tree) - case Some(enclosingRef) => - val methodAsString = enclosingRef.toString - val searchType = appliedType(typeConstructor, genericType) - Some(q"$magnoliaPkg.Deferred[$searchType]($methodAsString)") - } - } else directInferImplicit(genericType, typeConstructor).map(_.tree) - - if (currentStack.frames.isEmpty) recursionStack = ListMap() - - val dereferencedResult = result.map { tree => - if (debug.isDefined && genericType.toString.contains(debug.get)) { - c.echo(c.enclosingPosition, s"Magnolia macro expansion for $genericType") - c.echo(NoPosition, s"... = ${showCode(tree)}\n\n") - } - if (currentStack.frames.isEmpty) c.untypecheck(removeDeferred.transform(tree)) else tree + for (tree <- result) if (debug.isDefined && genericType.toString.contains(debug.get)) { + c.echo(c.enclosingPosition, s"Magnolia macro expansion for $genericType") + c.echo(NoPosition, s"... = ${showCode(tree)}\n\n") } + val dereferencedResult = if (stack.nonEmpty) result + else for (tree <- result) yield c.untypecheck(removeDeferred.transform(tree)) + dereferencedResult.getOrElse { - c.abort(c.enclosingPosition, s"magnolia: could not infer typeclass for type $genericType") + c.abort(c.enclosingPosition, + s"magnolia: could not infer $prefixName.Typeclass for type $genericType") } } @@ -483,7 +393,7 @@ object Magnolia { * should not be called directly from users' code. */ def param[Tc[_], T, P](name: String, isRepeated: Boolean, - typeclassParam: Tc[P], + typeclassParam: => Tc[P], defaultVal: => Option[P], deref: T => P): Param[Tc, T] = new Param[Tc, T] { type PType = P @@ -521,35 +431,61 @@ private[magnolia] object CompileTimeState { final case class ProductType(paramName: String, typeName: String) extends TypePath(s"parameter '$paramName' of product type $typeName") - final case class ChainedImplicit(typeName: String) - extends TypePath(s"chained implicit of type $typeName") + final case class ChainedImplicit(typeClassName: String, typeName: String) + extends TypePath(s"chained implicit $typeClassName for type $typeName") - final case class ImplicitNotFound(genericType: String, path: List[TypePath]) + final class Stack[C <: whitebox.Context] { + private var frames = List.empty[Frame] + private val cache = mutable.Map.empty[C#Type, C#Tree] - final case class Stack(cache: Map[whitebox.Context#Type, Option[whitebox.Context#Tree]], - frames: List[Frame], - errors: List[ImplicitNotFound]) { + def isEmpty: Boolean = frames.isEmpty + def nonEmpty: Boolean = frames.nonEmpty + def top: Option[Frame] = frames.headOption + def pop(): Unit = frames = frames drop 1 + def push(frame: Frame): Unit = frames ::= frame - def lookup(c: whitebox.Context)(t: c.Type)(orElse: => Option[c.Tree]): (Option[c.Tree], Stack) = - if (cache.contains(t)) { - (cache(t).asInstanceOf[Option[c.Tree]], this) - } else { - val value = orElse - (value, copy(cache.updated(t, value))) - } + def clear(): Unit = { + frames = Nil + cache.clear() + } + + def find(searchType: C#Type): Option[C#TermName] = frames.collectFirst { + case Frame(_, tpe, term) if tpe =:= searchType => term + } + + def recurse[T <: C#Tree](frame: Frame, searchType: C#Type)(fn: => T): T = { + push(frame) + val result = cache.getOrElseUpdate(searchType, fn) + pop() + result.asInstanceOf[T] + } - def push(path: TypePath, key: whitebox.Context#Type, value: whitebox.Context#TermName): Stack = - Stack(cache, Frame(path, key, value) :: frames, errors) + def trace: List[TypePath] = frames.drop(1).foldLeft[(C#Type, List[TypePath])]((null, Nil)) { + case ((_, Nil), frame) => + (frame.searchType, frame.path :: Nil) + case (continue @ (tpe, acc), frame) => + if (tpe =:= frame.searchType) continue + else (frame.searchType, frame.path :: acc) + }._2.reverse - def pop(): Stack = Stack(cache, frames.tail, errors) - } + override def toString: String = + frames.mkString("magnolia stack:\n", "\n", "\n") - final case class Frame(path: TypePath, - genericType: whitebox.Context#Type, - term: whitebox.Context#TermName) { - def termName(c: whitebox.Context): c.TermName = term.asInstanceOf[c.TermName] + final case class Frame(path: TypePath, searchType: C#Type, term: C#TermName) } - var recursionStack: ListMap[api.Position, Stack] = ListMap() - var emittedErrors: Set[ImplicitNotFound] = Set() + object Stack { + private val global = new Stack[whitebox.Context] + private val workSet = mutable.Set.empty[whitebox.Context#Symbol] + + def withContext(c: whitebox.Context)(fn: Stack[c.type] => c.Tree): c.Tree = { + workSet += c.macroApplication.symbol + val depth = c.enclosingMacros.count(m => workSet(m.macroApplication.symbol)) + try fn(global.asInstanceOf[Stack[c.type]]) + finally if (depth <= 1) { + global.clear() + workSet.clear() + } + } + } } diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala index 6c2ab9a..7840133 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -1,11 +1,11 @@ package magnolia.tests import language.experimental.macros -import magnolia._ -import estrapade._ +import estrapade.{test, TestApp} import contextual.data.scalac._ import contextual.data.fqt._ import contextual.data.txt._ +import magnolia.examples._ import scala.util.control.NonFatal @@ -13,6 +13,11 @@ sealed trait Tree[+T] case class Leaf[+L](value: L) extends Tree[L] case class Branch[+B](left: Tree[B], right: Tree[B]) extends Tree[B] +sealed trait Path[+A] +case class Destination[+A](value: A) extends Path[A] +case class Crossroad[+A](left: Path[A], right: Path[A]) extends Path[A] +case class OffRoad[+A](path: Option[Path[A]]) extends Path[A] + sealed trait Entity case class Company(name: String) extends Entity @@ -24,7 +29,6 @@ class Length(val value: Int) extends AnyVal case class FruitBasket(fruits: Fruit*) case class Lunchbox(fruit: Fruit, drink: String) object Fruit { - import examples._ implicit val showFruit: Show[String, Fruit] = new Show[String, Fruit] { def show(f: Fruit): String = f.name } } @@ -61,33 +65,29 @@ case class Account(id: String, emails: String*) case class Portfolio(companies: Company*) -object Tests extends TestApp { +case class Recursive(children: Seq[Recursive]) - def tests(): Unit = for (i <- 1 to 1) { - import examples._ +object Tests extends TestApp { + + def tests(): Unit = for (_ <- 1 to 1) { test("construct a Show product instance with alternative apply functions") { - import examples._ Show.gen[Test].show(Test("a", "b")) }.assert(_ == """Test(param=Param(a=a,b=b))""") test("construct a Show product instance") { - import examples._ Show.gen[Person].show(Person("John Smith", 34)) }.assert(_ == """Person(name=John Smith,age=34)""") test("construct a Show coproduct instance") { - import examples._ Show.gen[Person].show(Person("John Smith", 34)) }.assert(_ == "Person(name=John Smith,age=34)") test("serialize a Branch") { - import magnolia.examples._ implicitly[Show[String, Branch[String]]].show(Branch(Leaf("LHS"), Leaf("RHS"))) }.assert(_ == "Branch(left=Leaf(value=LHS),right=Leaf(value=RHS))") test("local implicit beats Magnolia") { - import magnolia.examples._ implicit val showPerson: Show[String, Person] = new Show[String, Person] { def show(p: Person) = "nobody" } @@ -95,40 +95,33 @@ object Tests extends TestApp { }.assert(_ == "Address(line1=Home,occupant=nobody)") test("even low-priority implicit beats Magnolia for nested case") { - import magnolia.examples._ import Show.gen implicitly[Show[String, Lunchbox]].show(Lunchbox(Fruit("apple"), "lemonade")) }.assert(_ == "Lunchbox(fruit=apple,drink=lemonade)") test("low-priority implicit does not beat Magnolia when not nested") { - import magnolia.examples._ import Show.gen implicitly[Show[String, Fruit]].show(Fruit("apple")) }.assert(_ == "Fruit(name=apple)") test("low-priority implicit does not beat Magnolia when chained") { - import magnolia.examples._ import Show.gen implicitly[Show[String, FruitBasket]].show(FruitBasket(Fruit("apple"), Fruit("banana"))) }.assert(_ == "FruitBasket(fruits=[Fruit(name=apple),Fruit(name=banana)])") test("typeclass implicit scope has lower priority than ADT implicit scope") { - import magnolia.examples._ implicitly[Show[String, Fruit]].show(Fruit("apple")) }.assert(_ == "apple") test("test equality false") { - import examples._ Eq.gen[Entity].equal(Person("John Smith", 34), Person("", 0)) }.assert(_ == false) test("test equality true") { - import examples._ Eq.gen[Entity].equal(Person("John Smith", 34), Person("John Smith", 34)) }.assert(_ == true) test("test branch equality true") { - import examples._ Eq.gen[Tree[String]].equal(Branch(Leaf("one"), Leaf("two")), Branch(Leaf("one"), Leaf("two"))) }.assert(_ == true) @@ -138,14 +131,12 @@ object Tests extends TestApp { test("construction of Show instance for Leaf") { scalac""" - import magnolia.examples._ implicitly[Show[String, Leaf[java.lang.String]]] """ }.assert(_ == Returns(fqt"magnolia.examples.Show[String,magnolia.tests.Leaf[String]]")) test("construction of Show instance for Tree") { scalac""" - import magnolia.examples._ implicitly[Show[String, Tree[String]]] """ }.assert(_ == Returns(fqt"magnolia.examples.Show[String,magnolia.tests.Tree[String]]")) @@ -186,25 +177,25 @@ object Tests extends TestApp { test("show error stack") { scalac""" - import magnolia.examples._ case class Alpha(integer: Double) case class Beta(alpha: Alpha) Show.gen[Beta] """ - }.assert(_ == TypecheckError(txt"""magnolia: could not find typeclass for type Double - | in parameter 'integer' of product type Alpha - | in parameter 'alpha' of product type Beta - |""")) + }.assert(_ == TypecheckError( + txt"""magnolia: could not find Show.Typeclass for type Double + | in parameter 'integer' of product type Alpha + | in parameter 'alpha' of product type Beta + |""")) test("not attempt to instantiate Unit when producing error stack") { scalac""" - import magnolia.examples._ case class Gamma(unit: Unit) Show.gen[Gamma] """ - }.assert(_ == TypecheckError(txt"""magnolia: could not find typeclass for type Unit - | in parameter 'unit' of product type Gamma - |""")) + }.assert(_ == TypecheckError( + txt"""magnolia: could not find Show.Typeclass for type Unit + | in parameter 'unit' of product type Gamma + |""")) test("typenames and labels are not encoded") { implicitly[Show[String, `%%`]].show(`%%`(1, "two")) @@ -231,7 +222,7 @@ object Tests extends TestApp { test("can't show a Box with invariant label") { scalac"Show.gen[Box[Int]]" }.assert { _ == TypecheckError( - txt"""magnolia: could not find typeclass for type L + txt"""magnolia: could not find Show.Typeclass for type L | in parameter 'label' of product type magnolia.tests.LabelledBox[Int, _ <: String] | in coproduct type magnolia.tests.Box[Int] |""") @@ -241,7 +232,7 @@ object Tests extends TestApp { // these two implicits can be removed once https://github.com/propensive/magnolia/issues/58 is closed implicit val stringPatcher = Patcher.forSingleValue[String] implicit val intPatcher = Patcher.forSingleValue[Int] - + val person = Person("Bob", 42) implicitly[Patcher[Entity]] .patch(person, Seq(null, 21)) @@ -286,7 +277,9 @@ object Tests extends TestApp { test("construct a default case class inside another class") { Default.gen[InnerClassWithDefault].default - }.assert(_ == InnerClassWithDefault("foo")) + }.assert(_ == InnerClassWithDefault()) + + () } def testLocal(): Unit = { @@ -299,10 +292,12 @@ object Tests extends TestApp { test("construct a default case class inside a method") { Default.gen[LocalClassWithDefault].default - }.assert(_ == LocalClassWithDefault("foo")) + }.assert(_ == LocalClassWithDefault()) + + () } } - + val parent = new ParentClass() parent.testInner() parent.testLocal() @@ -322,7 +317,7 @@ object Tests extends TestApp { test("show a List[Int]") { Show.gen[List[Int]].show(List(1, 2, 3)) }.assert(_ == "::(head=1,tl$access$1=::(head=2,tl$access$1=::(head=3,tl$access$1=Nil())))") - + test("sealed trait typeName should be complete and unchanged") { TypeNameInfo.gen[Color].name }.assert(_.full == "magnolia.tests.Color") @@ -331,5 +326,41 @@ object Tests extends TestApp { implicit val stringTypeName: TypeNameInfo[String] = new TypeNameInfo[String] { def name = ??? } TypeNameInfo.gen[Fruit].name }.assert(_.full == "magnolia.tests.Fruit") + + test("show chained error stack") { + scalac""" + Show.gen[(Int, Seq[(Long, String)])] + """ + } assert { _ == TypecheckError( + txt"""magnolia: could not find Show.Typeclass for type Long + | in parameter '_1' of product type (Long, String) + | in chained implicit Show.Typeclass for type Seq[(Long, String)] + | in parameter '_2' of product type (Int, Seq[(Long, String)]) + |""") + } + + test("show a recursive case class") { + Show.gen[Recursive].show(Recursive(Seq(Recursive(Nil)))) + }.assert(_ == "Recursive(children=[Recursive(children=[])])") + + test("manually derive a recursive case class instance") { + implicit lazy val showRecursive: Show[String, Recursive] = Show.gen[Recursive] + showRecursive.show(Recursive(Seq(Recursive(Nil)))) + }.assert(_ == "Recursive(children=[Recursive(children=[])])") + + test("show a type aliased case class") { + type T = Person + Show.gen[T].show(Person("Donald Duck", 313)) + }.assert(_ == "Person(name=Donald Duck,age=313)") + + test("dependencies between derived type classes") { + implicit def showDefaultOption[A]( + implicit showA: Show[String, A], defaultA: Default[A] + ): Show[String, Option[A]] = new Show[String, Option[A]] { + def show(optA: Option[A]) = showA.show(optA.getOrElse(defaultA.default)) + } + + Show.gen[Path[String]].show(OffRoad(Some(Crossroad(Destination("A"), Destination("B"))))) + }.assert(_ == "OffRoad(path=Crossroad(left=Destination(value=A),right=Destination(value=B)))") } } |