From ebeca9258d5e629327abbcf67e0066a4cdeac173 Mon Sep 17 00:00:00 2001 From: Shadaj Laddad Date: Tue, 21 Nov 2017 10:23:09 -0800 Subject: Add unit tests and fix behavior with default parameters --- tests/src/main/scala/tests.scala | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'tests/src/main/scala') diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala index e453502..6810fc9 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -176,5 +176,13 @@ object Tests extends TestApp { test("serialize a value class") { Show.gen[Length].show(new Length(100)) }.assert(_ == "100") + + class ParentClass { + case class InnerClass(name: String) + + test("serialize a case class inside another class") { + implicitly[Show[String, InnerClass]].show(InnerClass("foo")) + }.assert(_ == "InnerClass(name=foo)") + } } } -- 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(-) (limited to 'tests/src/main/scala') 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 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(-) (limited to 'tests/src/main/scala') 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 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(-) (limited to 'tests/src/main/scala') 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 From d8fb2f5ca2edc1a34ed7831f06d5eca08ba4989c Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Wed, 29 Nov 2017 19:53:01 +0100 Subject: Existentially abstract unbound subtype parameters That happens when the subtype of a sealed trait has more type parameters than its parent. When those extra type parameters are covariant they are replaced by their upper bounds, otherwise they are existentially quantified. --- CONTRIBUTORS | 2 +- core/shared/src/main/scala/magnolia.scala | 16 +++++++++------- examples/shared/src/main/scala/show.scala | 6 ++++++ tests/src/main/scala/tests.scala | 23 +++++++++++++++++++++++ 4 files changed, 39 insertions(+), 8 deletions(-) (limited to 'tests/src/main/scala') 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/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index 2113dff..62a36a5 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -65,6 +65,7 @@ object Magnolia { * */ def gen[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = { import c.universe._ + import internal._ val magnoliaPkg = q"_root_.magnolia" val scalaPkg = q"_root_.scala" @@ -246,9 +247,7 @@ 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 paramType = param.typeSignatureIn(genericType).resultType val predefinedRef = acc.find(_.paramType == paramType) val caseParamOpt = predefinedRef.map { backRef => @@ -332,10 +331,13 @@ object Magnolia { } else if (isSealedTrait) { val genericSubtypes = classType.get.knownDirectSubclasses.to[List] val subtypes = genericSubtypes.map { sub => - val typeArgs = sub.asType.typeSignature.baseType(genericType.typeSymbol).typeArgs - val mapping = typeArgs.zip(genericType.typeArgs).toMap - val newTypeParams = sub.asType.toType.typeArgs.map(mapping(_)) - appliedType(sub.asType.toType.typeConstructor, newTypeParams) + val subType = sub.asType.toType // FIXME: Broken for path dependent types + val typeParams = sub.asType.typeParams + val typeArgs = thisType(sub).baseType(genericType.typeSymbol).typeArgs + val mapping = (typeArgs.map(_.typeSymbol), genericType.typeArgs).zipped.toMap + val newTypeArgs = typeParams.map(mapping.withDefault(_.asType.toType)) + val applied = appliedType(subType.typeConstructor, newTypeArgs) + existentialAbstraction(typeParams, applied) } if (subtypes.isEmpty) { 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..bc33969 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -47,6 +47,14 @@ object Test { def apply(a: String, b: String): Test = Test(Param(a, b)) } +sealed trait Politician[+S] +case class Accountable[+S](slogan: S) extends Politician[S] +case class Corrupt[+S, +L <: Seq[Company]](slogan: S, lobby: L) extends Politician[S] + +sealed trait Box[+A] +case class SimpleBox[+A](value: A) extends Box[A] +case class LabelledBox[+A, L <: String](value: A, var label: L) extends Box[A] + object Tests extends TestApp { def tests() = for (i <- 1 to 1000) { @@ -201,5 +209,20 @@ object Tests extends TestApp { test("serialize a value class") { Show.gen[Length].show(new Length(100)) }.assert(_ == "100") + + // Corrupt being covariant in L <: Seq[Company] enables the derivation for Corrupt[String, _] + test("show a Politician with covariant lobby") { + Show.gen[Politician[String]].show(Corrupt("wall", Seq(Company("Alice Inc")))) + }.assert(_ == "Corrupt(slogan=wall,lobby=[Company(name=Alice Inc)])") + + // LabelledBox being invariant in L <: String prohibits the derivation for LabelledBox[Int, _] + 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 + | in parameter 'label' of product type magnolia.tests.LabelledBox[Int, _ <: String] + | in coproduct type magnolia.tests.Box[Int] + |""") + } } } -- cgit v1.2.3 From 834af65ed610d6b1b278b5204801306808a90645 Mon Sep 17 00:00:00 2001 From: Shadaj Laddad Date: Wed, 29 Nov 2017 12:42:07 -0800 Subject: Actually instantiate the ParentClass so unit tests run --- tests/src/main/scala/tests.scala | 2 ++ 1 file changed, 2 insertions(+) (limited to 'tests/src/main/scala') diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala index 6810fc9..727fb70 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -184,5 +184,7 @@ object Tests extends TestApp { implicitly[Show[String, InnerClass]].show(InnerClass("foo")) }.assert(_ == "InnerClass(name=foo)") } + + new ParentClass } } -- cgit v1.2.3 From 4bfd00088576c9400aa7630007ccac9670399e50 Mon Sep 17 00:00:00 2001 From: Shadaj Laddad Date: Thu, 30 Nov 2017 10:27:43 -0800 Subject: Use patchedCompanionSymbolOf from Shapeless to get companion object As suggested by @joroKr21 --- core/shared/src/main/scala/magnolia.scala | 97 ++++++++++++++++++++++--------- tests/src/main/scala/tests.scala | 13 +++-- 2 files changed, 77 insertions(+), 33 deletions(-) (limited to 'tests/src/main/scala') diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index e8350da..c935b91 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -200,6 +200,56 @@ object Magnolia { } } + // From Shapeless: https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/generic.scala#L698 + // Cut-n-pasted (with most original comments) and slightly adapted from + // https://github.com/scalamacros/paradise/blob/c14c634923313dd03f4f483be3d7782a9b56de0e/plugin/src/main/scala/org/scalamacros/paradise/typechecker/Namers.scala#L568-L613 + def patchedCompanionSymbolOf(original: c.Symbol): c.Symbol = { + // see https://github.com/scalamacros/paradise/issues/7 + // also see https://github.com/scalamacros/paradise/issues/64 + + val global = c.universe.asInstanceOf[scala.tools.nsc.Global] + val typer = c.asInstanceOf[scala.reflect.macros.runtime.Context].callsiteTyper.asInstanceOf[global.analyzer.Typer] + val ctx = typer.context + val owner = original.owner + + import global.analyzer.Context + + original.companion.orElse { + import global.{ abort => aabort, _ } + implicit class PatchedContext(ctx: Context) { + trait PatchedLookupResult { def suchThat(criterion: Symbol => Boolean): Symbol } + def patchedLookup(name: Name, expectedOwner: Symbol) = new PatchedLookupResult { + override def suchThat(criterion: Symbol => Boolean): Symbol = { + var res: Symbol = NoSymbol + var ctx = PatchedContext.this.ctx + while (res == NoSymbol && ctx.outer != ctx) { + // NOTE: original implementation says `val s = ctx.scope lookup name` + // but we can't use it, because Scope.lookup returns wrong results when the lookup is ambiguous + // and that triggers https://github.com/scalamacros/paradise/issues/64 + val s = { + val lookupResult = ctx.scope.lookupAll(name).filter(criterion).toList + lookupResult match { + case Nil => NoSymbol + case List(unique) => unique + case _ => aabort(s"unexpected multiple results for a companion symbol lookup for $original#{$original.id}") + } + } + if (s != NoSymbol && s.owner == expectedOwner) + res = s + else + ctx = ctx.outer + } + res + } + } + } + ctx.patchedLookup(original.asInstanceOf[global.Symbol].name.companionName, owner.asInstanceOf[global.Symbol]).suchThat(sym => + (original.isTerm || sym.hasModuleFlag) && + (sym isCoDefinedWith original.asInstanceOf[global.Symbol]) + ).asInstanceOf[c.universe.Symbol] + } + } + def directInferImplicit(genericType: c.Type, typeConstructor: Type): Option[Typeclass] = { val genericTypeName: String = genericType.typeSymbol.name.decodedName.toString.toLowerCase @@ -289,35 +339,24 @@ 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) - val noDefaults = constructorParams.forall(!_.isParamWithDefault) - - if (noDefaults) { - constructorParams.map(_ => q"$scalaPkg.None") - } else { - val caseClassCompanion = genericType.companion - - // 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) { - val method = TermName("apply$default$" + (idx + 1)) - q"$scalaPkg.Some(${genericType.typeSymbol.companion.asTerm}.$method)" - } else q"$scalaPkg.None" - } - + val caseClassCompanion = patchedCompanionSymbolOf(genericType.typeSymbol).asModule.info + + // 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) { + val method = TermName("apply$default$" + (idx + 1)) + q"$scalaPkg.Some(${genericType.typeSymbol.companion.asTerm}.$method)" + } else q"$scalaPkg.None" } } else List(q"$scalaPkg.None") diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala index 6d883da..836a00e 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -204,13 +204,18 @@ object Tests extends TestApp { Show.gen[Length].show(new Length(100)) }.assert(_ == "100") - class ParentClass { - case class InnerClass(name: String) + case class LocalClass(name: String) test("serialize a case class inside another class") { - implicitly[Show[String, InnerClass]].show(InnerClass("foo")) - }.assert(_ == "InnerClass(name=foo)") + implicitly[Show[String, LocalClass]].show(LocalClass("foo")) + }.assert(_ == "LocalClass(name=foo)") + + case class LocalClassWithDefault(name: String = "foo") + + test("construct a default case class inside another class") { + Default.gen[LocalClassWithDefault].default + }.assert(_ == LocalClassWithDefault("foo")) } new ParentClass -- cgit v1.2.3 From 7df3e96d3137e78813d5bdcb4daac8531db6b860 Mon Sep 17 00:00:00 2001 From: Shadaj Laddad Date: Thu, 30 Nov 2017 10:30:16 -0800 Subject: Remove extra line added to tests.scala --- tests/src/main/scala/tests.scala | 1 - 1 file changed, 1 deletion(-) (limited to 'tests/src/main/scala') diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala index 836a00e..d6fe051 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -231,6 +231,5 @@ object Tests extends TestApp { 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 From 13fcfba5aa5d6a90b90c22088cb9b8da5e2823fe Mon Sep 17 00:00:00 2001 From: Jon Pretty Date: Thu, 30 Nov 2017 18:32:07 +0000 Subject: typeName is now the full name of the type, in original case --- core/shared/src/main/scala/magnolia.scala | 10 +++++----- examples/shared/src/main/scala/typename.scala | 19 +++++++++++++++++++ tests/src/main/scala/tests.scala | 14 +++++++++++++- 3 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 examples/shared/src/main/scala/typename.scala (limited to 'tests/src/main/scala') diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index c4c13e6..6528db0 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -223,10 +223,11 @@ object Magnolia { val isValueClass = genericType <:< typeOf[AnyVal] && !primitives.exists(_ =:= genericType) val resultType = appliedType(typeConstructor, genericType) + + val className = s"${genericType.typeSymbol.owner.fullName}.${genericType.typeSymbol.name.decodedName}" val result = if (isCaseObject) { val obj = companionRef(genericType) - val className = genericType.typeSymbol.name.decodedName.toString val impl = q""" ${c.prefix}.combine($magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType]( @@ -239,7 +240,6 @@ object Magnolia { case m: MethodSymbol if m.isCaseAccessor || (isValueClass && m.isParamAccessor) => m.asMethod } - val className = genericType.typeSymbol.name.decodedName.toString case class CaseParam(sym: c.universe.MethodSymbol, repeated: Boolean, @@ -372,13 +372,13 @@ object Magnolia { val assignments = typeclasses.zipWithIndex.map { case ((typ, typeclass), idx) => q"""$subtypesVal($idx) = $magnoliaPkg.Magnolia.subtype[$typeConstructor, $genericType, $typ]( - ${typ.typeSymbol.fullName.toString}, + ${s"${typ.typeSymbol.owner.fullName}.${typ.typeSymbol.name.decodedName}"}, $typeclass, (t: $genericType) => t.isInstanceOf[$typ], (t: $genericType) => t.asInstanceOf[$typ] )""" } - + Some { Typeclass( genericType, @@ -389,7 +389,7 @@ object Magnolia { ..$assignments ${c.prefix}.dispatch(new $magnoliaPkg.SealedTrait( - $genericTypeName, + $className, $subtypesVal: $scalaPkg.Array[$magnoliaPkg.Subtype[$typeConstructor, $genericType]]) ): $resultType }""" diff --git a/examples/shared/src/main/scala/typename.scala b/examples/shared/src/main/scala/typename.scala new file mode 100644 index 0000000..c1e1fd2 --- /dev/null +++ b/examples/shared/src/main/scala/typename.scala @@ -0,0 +1,19 @@ +package magnolia.examples + +import language.experimental.macros + +import magnolia._ + +trait TypeName[T] { def name: String } + +object TypeName { + type Typeclass[T] = TypeName[T] + def combine[T](ctx: CaseClass[TypeName, T]): TypeName[T] = + new TypeName[T] { def name: String = ctx.typeName } + + def dispatch[T](ctx: SealedTrait[TypeName, T]): TypeName[T] = + new TypeName[T] { def name: String = ctx.typeName } + + implicit def gen[T]: TypeName[T] = macro Magnolia.gen[T] +} + diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala index 4c2a48e..96a2c2b 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -1,5 +1,7 @@ package magnolia.tests +import language.experimental.macros + import magnolia._ import estrapade._ import contextual.data.scalac._ @@ -51,7 +53,7 @@ case class Portfolio(companies: Company*) object Tests extends TestApp { - def tests() = for (i <- 1 to 1000) { + def tests() = for (i <- 1 to 1) { import examples._ test("construct a Show product instance with alternative apply functions") { @@ -215,5 +217,15 @@ object Tests extends TestApp { 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)])") + + test("sealed trait typeName should be complete and unchanged") { + TypeName.gen[Color].name + }.assert(_ == "magnolia.tests.Color") + + test("case class typeName should be complete and unchanged") { + implicit val stringTypeName: TypeName[String] = new TypeName[String] { def name = "" } + TypeName.gen[Fruit].name + }.assert(_ == "magnolia.tests.Fruit") + } } -- cgit v1.2.3