diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | CONTRIBUTORS | 2 | ||||
-rwxr-xr-x | benchmarks/2.12/benchmark | 10 | ||||
-rw-r--r-- | benchmarks/2.12/src/adt1.scala | 78 | ||||
-rw-r--r-- | benchmarks/2.12/src/magnolia/show_50.scala | 100 | ||||
-rw-r--r-- | benchmarks/2.12/src/scalaz-deriving/show_50.scala | 61 | ||||
-rwxr-xr-x | benchmarks/fetch.sh | 8 | ||||
-rw-r--r-- | build.sbt | 7 | ||||
-rw-r--r-- | core/shared/src/main/scala/interface.scala | 13 | ||||
-rw-r--r-- | core/shared/src/main/scala/magnolia.scala | 69 | ||||
-rw-r--r-- | examples/shared/src/main/scala/default.scala | 3 | ||||
-rw-r--r-- | examples/shared/src/main/scala/show.scala | 6 | ||||
-rw-r--r-- | tests/src/main/scala/tests.scala | 45 |
13 files changed, 322 insertions, 82 deletions
@@ -2,4 +2,4 @@ target .jvm .js .native -.idea/ +.idea diff --git a/CONTRIBUTORS b/CONTRIBUTORS index f495d8d..e376ca4 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1,3 +1,3 @@ Jon Pretty <jon.pretty@propensive.com> [@propensive](https://twitter.com/propensive/) Loïc Descotte - +Georgi Krastev <jorokr21@gmail.com> [@joro_kr](https://twitter.com/joro_kr/) diff --git a/benchmarks/2.12/benchmark b/benchmarks/2.12/benchmark index 744639d..513e7bd 100755 --- a/benchmarks/2.12/benchmark +++ b/benchmarks/2.12/benchmark @@ -6,17 +6,19 @@ shift benchmark() { DERIVATION="$1" TEST="$2" + ADT="$3" echo "$DERIVATION/$TEST:" - echo "src/adt.scala" > .script + echo "src/$ADT.scala" > .script for I in $(seq 1 $COUNT); do echo "src/$DERIVATION/$TEST.scala" >> .script done - cat .script | scalac-2.12 -Xresident -d bin -cp $(ls lib/* | tr '\n' :)bin | ts -i -s '%M:%.S' > out.log + cat .script | scalac -Xplugin:lib/paradise.jar -Ypartial-unification -Xresident -d bin -cp $(ls lib/* | tr '\n' :)bin | ts -i -s '%M:%.S' > out.log cat out.log | tail -n +2 | paste -d' ' - .script | sed 's/nsc.//g' | head -n -1 echo } for J in "$@"; do - benchmark magnolia "$J" - benchmark kittens "$J" + benchmark magnolia "$J" "adt" + benchmark kittens "$J" "adt" + benchmark scalaz-deriving "$J" "adt1" done diff --git a/benchmarks/2.12/src/adt1.scala b/benchmarks/2.12/src/adt1.scala new file mode 100644 index 0000000..f9ab65f --- /dev/null +++ b/benchmarks/2.12/src/adt1.scala @@ -0,0 +1,78 @@ +package adt1 + +import scalaz._ +import Scalaz._ + +// @deriving(Show, Equal) sealed trait Tree +// @deriving(Show, Equal) case class Leaf(value: String) extends Tree +// @deriving(Show, Equal) case class Branch(left: Tree, right: Tree) extends Tree + +// @deriving(Show, Equal) sealed trait GTree[T] +// @deriving(Show, Equal) case class GLeaf[T](value: String) extends GTree[T] +// @deriving(Show, Equal) case class GBranch[T](left: GTree[T], right: GTree[T]) extends GTree[T] + +// @deriving(Show, Equal) sealed trait Entity + +// @deriving(Show, Equal) case class Company(name: String) extends Entity +// @deriving(Show, Equal) case class Human(name: String, age: Int) extends Entity +// @deriving(Show, Equal) case class Address(line1: String, occupant: Human) + +@deriving(Show, Equal) sealed trait Alphabet + +@deriving(Show, Equal) case class Greek(άλφα: Letter, + βήτα: Letter, + γάμα: Letter, + δέλτα: Letter, + έψιλον: Letter, + ζήτα: Letter, + ήτα: Letter, + θήτα: Letter) + extends Alphabet + +@deriving(Show, Equal) case class Cyrillic(б: Letter, в: Letter, г: Letter, д: Letter, ж: Letter, з: Letter) + extends Alphabet + +@deriving(Show, Equal) case class Latin(a: Letter, + b: Letter, + c: Letter, + d: Letter, + e: Letter, + f: Letter, + g: Letter, + h: Letter, + i: Letter, + j: Letter, + k: Letter, + l: Letter, + m: Letter, + n: Letter, + o: Letter, + p: Letter, + q: Letter, + r: Letter, + s: Letter, + t: Letter, + u: Letter, + v: Letter) + extends Alphabet + +//@deriving(Show, Equal) case class Letter(name: String, phonetic: String) +//@deriving(Show, Equal) case class Country(name: String, language: Language, leader: Person, existence: DateRange) +//@deriving(Show, Equal) case class Language(name: String, code: String, alphabet: Alphabet) +//@deriving(Show, Equal) case class Person(name: String, dateOfBirth: Date) +//@deriving(Show, Equal) case class Date(year: Int, month: Month, day: Int) +//@deriving(Show, Equal) case class DateRange(from: Date, toDate: Date) + +// @deriving(Show, Equal) sealed trait Month +// @deriving(Show, Equal) case object Jan extends Month +// @deriving(Show, Equal) case object Feb extends Month +// @deriving(Show, Equal) case object Mar extends Month +// @deriving(Show, Equal) case object Apr extends Month +// @deriving(Show, Equal) case object May extends Month +// @deriving(Show, Equal) case object Jun extends Month +// @deriving(Show, Equal) case object Jul extends Month +// @deriving(Show, Equal) case object Aug extends Month +// @deriving(Show, Equal) case object Sep extends Month +// @deriving(Show, Equal) case object Oct extends Month +// @deriving(Show, Equal) case object Nov extends Month +// @deriving(Show, Equal) case object Dec extends Month diff --git a/benchmarks/2.12/src/magnolia/show_50.scala b/benchmarks/2.12/src/magnolia/show_50.scala index 27cfbae..df431af 100644 --- a/benchmarks/2.12/src/magnolia/show_50.scala +++ b/benchmarks/2.12/src/magnolia/show_50.scala @@ -1,58 +1,58 @@ import magnolia._, examples._ object Gen { - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] - Show.generic[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] + Show.gen[adt.Alphabet] } diff --git a/benchmarks/2.12/src/scalaz-deriving/show_50.scala b/benchmarks/2.12/src/scalaz-deriving/show_50.scala new file mode 100644 index 0000000..37a00f0 --- /dev/null +++ b/benchmarks/2.12/src/scalaz-deriving/show_50.scala @@ -0,0 +1,61 @@ +import adt1._ + +import scalaz._ +import Scalaz._ + +object Gen { + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] + Show[Alphabet] +} diff --git a/benchmarks/fetch.sh b/benchmarks/fetch.sh index 6644a06..4f7105d 100755 --- a/benchmarks/fetch.sh +++ b/benchmarks/fetch.sh @@ -10,4 +10,12 @@ for V in 2.12 2.11; do wget -O $V/lib/cats-kernel.jar "http://repo1.maven.org/maven2/org/typelevel/cats-kernel_$V/1.0.0-RC1/cats-kernel_$V-1.0.0-RC1.jar" wget -O $V/lib/cats-core.jar "http://repo1.maven.org/maven2/org/typelevel/cats-core_$V/1.0.0-RC1/cats-core_$V-1.0.0-RC1.jar" wget -O $V/lib/kittens.jar "http://repo1.maven.org/maven2/org/typelevel/kittens_$V/1.0.0-RC1/kittens_$V-1.0.0-RC1.jar" + + wget -O $V/lib/iotaz.jar "https://repo1.maven.org/maven2/io/frees/iotaz-core_$V/0.3.2/iotaz-core_$V-0.3.2.jar" + wget -O $V/lib/scalaz.jar "https://repo1.maven.org/maven2/org/scalaz/scalaz-core_$V/7.2.16/scalaz-core_$V-7.2.16.jar" + wget -O $V/lib/deriving-macro.jar "https://repo1.maven.org/maven2/com/fommil/deriving-macro_$V/0.9.0/deriving-macro_$V-0.9.0.jar" + wget -O $V/lib/paradise.jar "https://repo1.maven.org/maven2/org/scalamacros/paradise_$V.4/2.1.1/paradise_$V.4-2.1.1.jar" + wget -O $V/lib/scalaz-deriving.jar "https://repo1.maven.org/maven2/com/fommil/scalaz-deriving_$V/0.9.0/scalaz-deriving_$V-0.9.0.jar" + wget -O $V/lib/scalaz-deriving-core.jar "https://repo1.maven.org/maven2/com/fommil/scalaz-deriving-base_$V/0.9.0/scalaz-deriving-base_$V-0.9.0.jar" + done @@ -25,6 +25,13 @@ lazy val tests = project .settings(buildSettings: _*) .settings(unmanagedSettings) .settings(moduleName := "magnolia-tests") + .settings( + addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full), + libraryDependencies ++= Seq( + "com.fommil" %% "deriving-macro" % "0.9.0", + "com.fommil" %% "scalaz-deriving" % "0.9.0" + ) + ) .dependsOn(examplesJVM) lazy val benchmarks = project diff --git a/core/shared/src/main/scala/interface.scala b/core/shared/src/main/scala/interface.scala index 54f8ce3..193a6f9 100644 --- a/core/shared/src/main/scala/interface.scala +++ b/core/shared/src/main/scala/interface.scala @@ -34,7 +34,7 @@ trait Param[Typeclass[_], Type] { /** the type of the parameter being represented * - * For exmaple, for a case class, + * For example, for a case class, * <pre> * case class Person(name: String, age: Int) * </pre> @@ -46,6 +46,17 @@ trait Param[Typeclass[_], Type] { /** the name of the parameter */ def label: String + /** flag indicating a repeated (aka. vararg) parameter + * + * For example, for a case class, + * <pre> + * case class Account(id: String, emails: String*) + * </pre> + * the [[Param]] instance corresponding to the `emails` parameter would be `repeated` and have a + * [[PType]] equal to the type `Seq[String]`. Note that only the last parameter of a case class + * can be repeated. */ + def repeated: Boolean + /** the typeclass instance associated with this parameter * * This is the instance of the type `Typeclass[PType]` which will have been discovered by diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index 2431e4f..e8350da 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -65,11 +65,13 @@ object Magnolia { * */ def gen[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = { import c.universe._ - import scala.util.{Try, Success, Failure} val magnoliaPkg = q"_root_.magnolia" val scalaPkg = q"_root_.scala" + val repeatedParamClass = definitions.RepeatedParamClass + val scalaSeqType = typeOf[Seq[_]].typeConstructor + val prefixType = c.prefix.tree.tpe def companionRef(tpe: Type): Tree = { @@ -95,7 +97,7 @@ object Magnolia { "magnolia: the derivation object does not define the Typeclass type constructor") } - def checkMethod(termName: String, category: String, expected: String) = { + def checkMethod(termName: String, category: String, expected: String): Unit = { val term = TermName(termName) val combineClass = c.prefix.tree.tpe.baseClasses .find { cls => @@ -204,9 +206,9 @@ object Magnolia { 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 isCaseObject = classType.map(_.isModuleClass).getOrElse(false) - val isSealedTrait = classType.map(_.isSealed).getOrElse(false) + val isCaseClass = classType.exists(_.isCaseClass) + val isCaseObject = classType.exists(_.isModuleClass) + val isSealedTrait = classType.exists(_.isSealed) val primitives = Set(typeOf[Double], typeOf[Float], @@ -215,14 +217,14 @@ object Magnolia { typeOf[Int], typeOf[Long], typeOf[Char], - typeOf[Boolean]) + typeOf[Boolean], + typeOf[Unit]) val isValueClass = genericType <:< typeOf[AnyVal] && !primitives.exists(_ =:= genericType) val resultType = appliedType(typeConstructor, genericType) val result = if (isCaseObject) { - // FIXME: look for an alternative which isn't deprecated on Scala 2.12+ val obj = companionRef(genericType) val className = genericType.typeSymbol.name.decodedName.toString @@ -240,20 +242,29 @@ object Magnolia { val className = genericType.typeSymbol.name.decodedName.toString case class CaseParam(sym: c.universe.MethodSymbol, + repeated: Boolean, typeclass: c.Tree, paramType: c.Type, ref: c.TermName) - val caseParamsReversed: List[CaseParam] = caseClassParameters.foldLeft(List[CaseParam]()) { - case (acc, param) => + val caseParamsReversed = caseClassParameters.foldLeft[List[CaseParam]](Nil) { + (acc, param) => val paramName = param.name.decodedName.toString - val paramType = param.returnType.substituteTypes(genericType.etaExpand.typeParams, - genericType.typeArgs) + val paramTypeSubstituted = param.returnType.substituteTypes( + genericType.etaExpand.typeParams, + genericType.typeArgs) + + val (repeated, paramType) = paramTypeSubstituted match { + case TypeRef(_, `repeatedParamClass`, typeArgs) => + true -> appliedType(scalaSeqType, typeArgs) + case tpe => + false -> tpe + } val predefinedRef = acc.find(_.paramType == paramType) val caseParamOpt = predefinedRef.map { backRef => - CaseParam(param, q"()", paramType, backRef.ref) :: acc + CaseParam(param, repeated, q"()", paramType, backRef.ref) :: acc } caseParamOpt.getOrElse { @@ -266,7 +277,7 @@ object Magnolia { val ref = TermName(c.freshName("paramTypeclass")) val assigned = q"""val $ref = $derivedImplicit""" - CaseParam(param, assigned, paramType, ref) :: acc + CaseParam(param, repeated, assigned, paramType, ref) :: acc } } @@ -278,6 +289,7 @@ object Magnolia { val preAssignments = caseParams.map(_.typeclass) val defaults = if (!isValueClass) { + val constructorParams = genericType.decls.collect { case a: MethodSymbol if a.isConstructor => a }.head.paramLists.head.map(_.asTerm) @@ -287,10 +299,17 @@ object Magnolia { constructorParams.map(_ => q"$scalaPkg.None") } else { val caseClassCompanion = genericType.companion - val constructorMethod = caseClassCompanion.decl(TermName("apply")).asMethod - val indexedConstructorParams = - constructorMethod.paramLists.head.map(_.asTerm).zipWithIndex + // If a companion object is defined with alternative apply methods + // it is needed get all the alternatives + val constructorMethods = + caseClassCompanion.decl(TermName("apply")).alternatives.map(_.asMethod) + + // The last apply method in the alternatives is the one that belongs + // to the case class, not the user defined companion object + val indexedConstructorParams = + constructorMethods.last.paramLists.head.map(_.asTerm).zipWithIndex + indexedConstructorParams.map { case (p, idx) => if (p.isParamWithDefault) { @@ -298,14 +317,15 @@ object Magnolia { q"$scalaPkg.Some(${genericType.typeSymbol.companion.asTerm}.$method)" } else q"$scalaPkg.None" } + } } else List(q"$scalaPkg.None") val assignments = caseParams.zip(defaults).zipWithIndex.map { - case ((CaseParam(param, typeclass, paramType, ref), defaultVal), idx) => + case ((CaseParam(param, repeated, typeclass, paramType, ref), defaultVal), idx) => q"""$paramsVal($idx) = $magnoliaPkg.Magnolia.param[$typeConstructor, $genericType, $paramType]( - ${param.name.decodedName.toString}, $ref, $defaultVal, _.${param.name} + ${param.name.decodedName.toString}, $repeated, $ref, $defaultVal, _.${param.name} )""" } @@ -326,7 +346,8 @@ object Magnolia { ($fnVal: $magnoliaPkg.Param[$typeConstructor, $genericType] => Any) => new $genericType(..${caseParams.zipWithIndex.map { case (typeclass, idx) => - q"$fnVal($paramsVal($idx)).asInstanceOf[${typeclass.paramType}]" + val arg = q"$fnVal($paramsVal($idx)).asInstanceOf[${typeclass.paramType}]" + if (typeclass.repeated) q"$arg: _*" else arg }}) )) }""" @@ -399,9 +420,9 @@ object Magnolia { val genericType: Type = weakTypeOf[T] val currentStack: Stack = - recursionStack.get(c.enclosingPosition).getOrElse(Stack(Map(), List(), List())) + recursionStack.getOrElse(c.enclosingPosition, Stack(Map(), List(), List())) - val directlyReentrant = Some(genericType) == currentStack.frames.headOption.map(_.genericType) + val directlyReentrant = currentStack.frames.headOption.exists(_.genericType == genericType) if (directlyReentrant) throw DirectlyReentrantException() @@ -410,14 +431,14 @@ object Magnolia { emittedErrors += error val trace = error.path.mkString("\n in ", "\n in ", "\n \n") - val msg = s"magnolia: could not derive ${typeConstructor} instance for type " + + val msg = s"magnolia: could not derive $typeConstructor instance for type " + s"${error.genericType}" c.info(c.enclosingPosition, msg + trace, true) } } - val result: Option[Tree] = if (!currentStack.frames.isEmpty) { + val result: Option[Tree] = if (currentStack.frames.nonEmpty) { findType(genericType) match { case None => directInferImplicit(genericType, typeConstructor).map(_.tree) @@ -459,11 +480,13 @@ object Magnolia { * This method is intended to be called only from code generated by the Magnolia macro, and * should not be called directly from users' code. */ def param[Tc[_], T, P](name: String, + isRepeated: Boolean, typeclassParam: Tc[P], defaultVal: => Option[P], deref: T => P) = new Param[Tc, T] { type PType = P def label: String = name + def repeated: Boolean = isRepeated def default: Option[PType] = defaultVal def typeclass: Tc[PType] = typeclassParam def dereference(t: T): PType = deref(t) diff --git a/examples/shared/src/main/scala/default.scala b/examples/shared/src/main/scala/default.scala index 4c1b634..4b781a5 100644 --- a/examples/shared/src/main/scala/default.scala +++ b/examples/shared/src/main/scala/default.scala @@ -30,6 +30,9 @@ object Default { /** default value for ints; 0 */ implicit val int: Default[Int] = new Default[Int] { def default = 0 } + /** default value for sequences; the empty sequence */ + implicit def seq[A]: Default[Seq[A]] = new Typeclass[Seq[A]] { def default = Seq.empty } + /** generates default instances of [[Default]] for case classes and sealed traits */ implicit def gen[T]: Default[T] = macro Magnolia.gen[T] } diff --git a/examples/shared/src/main/scala/show.scala b/examples/shared/src/main/scala/show.scala index 50b34ee..9f634ba 100644 --- a/examples/shared/src/main/scala/show.scala +++ b/examples/shared/src/main/scala/show.scala @@ -60,4 +60,10 @@ object Show extends GenericShow[String] { implicit val int: Show[String, Int] = new Show[String, Int] { def show(s: Int): String = s.toString } + + /** show typeclass for sequences */ + implicit def seq[A](implicit A: Show[String, A]): Show[String, Seq[A]] = + new Show[String, Seq[A]] { + def show(as: Seq[A]): String = as.iterator.map(A.show).mkString("[", ",", "]") + } } diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala index 727fb70..6d883da 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -6,8 +6,6 @@ import contextual.data.scalac._ import contextual.data.fqt._ import contextual.data.txt._ -import scala.util._ - 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] @@ -37,11 +35,30 @@ case object Blue extends Color case class `%%`(`/`: Int, `#`: String) +case class Param(a: String, b: String) +case class Test(param: Param) +object Test { + def apply(): Test = Test(Param("", "")) + + def apply(a: String)(implicit b: Int): Test = Test(Param(a, b.toString)) + + def apply(a: String, b: String): Test = Test(Param(a, b)) +} + +case class Account(id: String, emails: String*) + +case class Portfolio(companies: Company*) + object Tests extends TestApp { def tests() = for (i <- 1 to 1000) { import examples._ + 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)) @@ -161,6 +178,16 @@ object Tests extends TestApp { | 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 + |""")) + test("typenames and labels are not encoded") { implicitly[Show[String, `%%`]].show(`%%`(1, "two")) }.assert(_ == "%%(/=1,#=two)") @@ -177,6 +204,7 @@ object Tests extends TestApp { Show.gen[Length].show(new Length(100)) }.assert(_ == "100") + class ParentClass { case class InnerClass(name: String) @@ -186,5 +214,18 @@ object Tests extends TestApp { } new ParentClass + + test("show an Account") { + Show.gen[Account].show(Account("john_doe", "john.doe@yahoo.com", "john.doe@gmail.com")) + }.assert(_ == "Account(id=john_doe,emails=[john.doe@yahoo.com,john.doe@gmail.com])") + + test("construct a default Account") { + Default.gen[Account].default + }.assert(_ == Account("")) + + test("show a Portfolio of Companies") { + Show.gen[Portfolio].show(Portfolio(Company("Alice Inc"), Company("Bob & Co"))) + }.assert(_ == "Portfolio(companies=[Company(name=Alice Inc),Company(name=Bob & Co)])") + } } |