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') 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 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') 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') 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') 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