From 08f529d1de02c42cc60680e160b12544e4edc644 Mon Sep 17 00:00:00 2001 From: Sam Halliday Date: Wed, 22 Nov 2017 16:25:11 +0000 Subject: add the alpha release --- build.sbt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build.sbt b/build.sbt index f127515..178ac83 100644 --- a/build.sbt +++ b/build.sbt @@ -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 -- cgit v1.2.3 From f94efcc6283317095979d48359bc030c94230306 Mon Sep 17 00:00:00 2001 From: Sam Halliday Date: Wed, 22 Nov 2017 21:57:58 +0000 Subject: benchmarks for scalaz-deriving on Alphabet --- benchmarks/2.12/benchmark | 10 ++- benchmarks/2.12/src/adt1.scala | 78 +++++++++++++++++ benchmarks/2.12/src/magnolia/show_50.scala | 100 +++++++++++----------- benchmarks/2.12/src/scalaz-deriving/show_50.scala | 61 +++++++++++++ benchmarks/fetch.sh | 8 ++ 5 files changed, 203 insertions(+), 54 deletions(-) create mode 100644 benchmarks/2.12/src/adt1.scala create mode 100644 benchmarks/2.12/src/scalaz-deriving/show_50.scala 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 -- cgit v1.2.3 From 2de22e7d27025f839ef70916d3bfb99e69973521 Mon Sep 17 00:00:00 2001 From: Leandro Bolivar Date: Sat, 25 Nov 2017 11:29:20 -0500 Subject: Fixed error that ocurred when the companion object of a case class has alternative apply methods --- .gitignore | 1 + core/shared/src/main/scala/magnolia.scala | 10 ++++++++-- tests/src/main/scala/tests.scala | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e7f1455..63d54ec 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ target .jvm .js .native +.idea diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index 6400002..337edc7 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -279,9 +279,15 @@ object Magnolia { val defaults = if (!isValueClass) { val caseClassCompanion = genericType.companion - val constructorMethod = caseClassCompanion.decl(TermName("apply")).asMethod + // If a companion object is defined with alternative apply methods + // it is needed get all the alternatives + val constructorMethod = + 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 = - constructorMethod.paramLists.head.map(_.asTerm).zipWithIndex + constructorMethod.last.paramLists.head.map(_.asTerm).zipWithIndex indexedConstructorParams.map { case (p, idx) => diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala index e453502..52dc1d0 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -37,11 +37,26 @@ 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)) +} + 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)) -- cgit v1.2.3 From 3882602c0f233a6849c4ea5b179fe3c7ef7aadf4 Mon Sep 17 00:00:00 2001 From: Leandro Bolivar Date: Sat, 25 Nov 2017 11:32:13 -0500 Subject: Minor format fix --- core/shared/src/main/scala/magnolia.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index 337edc7..4f1c665 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -279,15 +279,16 @@ object Magnolia { val defaults = if (!isValueClass) { val caseClassCompanion = genericType.companion + // If a companion object is defined with alternative apply methods // it is needed get all the alternatives val constructorMethod = - caseClassCompanion.decl(TermName("apply")).alternatives.map(_.asMethod) + 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 = - constructorMethod.last.paramLists.head.map(_.asTerm).zipWithIndex + constructorMethod.last.paramLists.head.map(_.asTerm).zipWithIndex indexedConstructorParams.map { case (p, idx) => -- cgit v1.2.3 From 615af3df03f9079691f6aec5992671ae2c08dac9 Mon Sep 17 00:00:00 2001 From: Leandro Bolivar Date: Sat, 25 Nov 2017 11:44:01 -0500 Subject: Minor semantic error --- core/shared/src/main/scala/magnolia.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index 4f1c665..375ae6e 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -282,13 +282,13 @@ object Magnolia { // If a companion object is defined with alternative apply methods // it is needed get all the alternatives - val constructorMethod = + 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 = - constructorMethod.last.paramLists.head.map(_.asTerm).zipWithIndex + constructorMethods.last.paramLists.head.map(_.asTerm).zipWithIndex indexedConstructorParams.map { case (p, idx) => -- cgit v1.2.3 From c560260df88d597fb8858603deb233c8212163b9 Mon Sep 17 00:00:00 2001 From: Rahul Goma Phulore Date: Sun, 26 Nov 2017 21:02:58 +0000 Subject: Avoid instantiating Unit when producing error stack Fixes https://github.com/propensive/magnolia/issues/41. --- core/shared/src/main/scala/magnolia.scala | 3 ++- tests/src/main/scala/tests.scala | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index 6400002..d400bc3 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -215,7 +215,8 @@ object Magnolia { typeOf[Int], typeOf[Long], typeOf[Char], - typeOf[Boolean]) + typeOf[Boolean], + typeOf[Unit]) val isValueClass = genericType <:< typeOf[AnyVal] && !primitives.exists(_ =:= genericType) diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala index e453502..cf31836 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -161,6 +161,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)") -- cgit v1.2.3 From 26c04da4361d46b21f65e292af6af8ff4bb9700c Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Wed, 29 Nov 2017 12:30:38 +0100 Subject: Fix a typo in interface Also, add `.idea` to `.gitignore`. --- core/shared/src/main/scala/interface.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/shared/src/main/scala/interface.scala b/core/shared/src/main/scala/interface.scala index 54f8ce3..02c1cc5 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, *
     *  case class Person(name: String, age: Int)
     *  
-- cgit v1.2.3 From 93ff9742cf8cd9f2c60986ff4f7af8d24e268b1f Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Wed, 29 Nov 2017 12:33:02 +0100 Subject: Minor code simplifications --- core/shared/src/main/scala/magnolia.scala | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index 9af9b09..2113dff 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -65,7 +65,6 @@ 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" @@ -95,7 +94,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 +203,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], @@ -223,7 +222,6 @@ object Magnolia { 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 @@ -245,8 +243,8 @@ object Magnolia { 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) @@ -398,9 +396,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() @@ -409,14 +407,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) -- cgit v1.2.3 From e9b52248855f9252520a232b28a841ea382922a1 Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Wed, 29 Nov 2017 12:36:04 +0100 Subject: Add support for repeated (vararg) parameters Added a `Param.repeated` flag to `interface`. --- CONTRIBUTORS | 2 +- core/shared/src/main/scala/interface.scala | 11 +++++++++++ core/shared/src/main/scala/magnolia.scala | 29 +++++++++++++++++++++------- examples/shared/src/main/scala/default.scala | 3 +++ examples/shared/src/main/scala/show.scala | 6 ++++++ tests/src/main/scala/tests.scala | 18 +++++++++++++++-- 6 files changed, 59 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index f495d8d..e376ca4 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1,3 +1,3 @@ Jon Pretty [@propensive](https://twitter.com/propensive/) Loïc Descotte - +Georgi Krastev [@joro_kr](https://twitter.com/joro_kr/) diff --git a/core/shared/src/main/scala/interface.scala b/core/shared/src/main/scala/interface.scala index 02c1cc5..193a6f9 100644 --- a/core/shared/src/main/scala/interface.scala +++ b/core/shared/src/main/scala/interface.scala @@ -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, + *
+    * case class Account(id: String, emails: String*)
+    * 
+ * 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 2113dff..c4c13e6 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -69,6 +69,9 @@ object Magnolia { 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 = { @@ -239,6 +242,7 @@ 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) @@ -246,13 +250,21 @@ object Magnolia { 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 { @@ -265,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 } } @@ -299,10 +311,10 @@ object Magnolia { } 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} )""" } @@ -323,7 +335,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 }}) )) }""" @@ -456,11 +469,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 4b0e5c1..4c2a48e 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] @@ -47,6 +45,10 @@ object Test { 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) { @@ -201,5 +203,17 @@ object Tests extends TestApp { test("serialize a value class") { Show.gen[Length].show(new Length(100)) }.assert(_ == "100") + + 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)])") } } -- cgit v1.2.3