aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShadaj Laddad <shadaj@users.noreply.github.com>2017-11-30 10:27:43 -0800
committerShadaj Laddad <shadaj@users.noreply.github.com>2017-11-30 10:27:43 -0800
commit4bfd00088576c9400aa7630007ccac9670399e50 (patch)
tree8f074ecb0ae68625ad248d1f87e1b0e467089353
parentc698d7db4b6e89392b8ae399333a89b167db20c7 (diff)
downloadmagnolia-4bfd00088576c9400aa7630007ccac9670399e50.tar.gz
magnolia-4bfd00088576c9400aa7630007ccac9670399e50.tar.bz2
magnolia-4bfd00088576c9400aa7630007ccac9670399e50.zip
Use patchedCompanionSymbolOf from Shapeless to get companion object
As suggested by @joroKr21
-rw-r--r--core/shared/src/main/scala/magnolia.scala97
-rw-r--r--tests/src/main/scala/tests.scala13
2 files changed, 77 insertions, 33 deletions
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