aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Pretty <jon.pretty@propensive.com>2017-12-29 15:57:38 +0000
committerJon Pretty <jon.pretty@propensive.com>2017-12-29 15:57:38 +0000
commit93e3ff410b93c4d2d886371937ad55c7d269ba99 (patch)
tree21ef18cfaed1c31d8ed2e011a6bc3345ea9c7bb0
parent436c04954d6973aa60c59dc835bc3573b72b6bc2 (diff)
parentf0b5356d5c181ab7bc396bf7b60ae821714fc756 (diff)
downloadmagnolia-93e3ff410b93c4d2d886371937ad55c7d269ba99.tar.gz
magnolia-93e3ff410b93c4d2d886371937ad55c7d269ba99.tar.bz2
magnolia-93e3ff410b93c4d2d886371937ad55c7d269ba99.zip
Merge branch 'joroKr21-companion-ref'
-rw-r--r--core/shared/src/main/scala/globalutil.scala59
-rw-r--r--core/shared/src/main/scala/magnolia.scala69
-rw-r--r--tests/src/main/scala/tests.scala40
3 files changed, 91 insertions, 77 deletions
diff --git a/core/shared/src/main/scala/globalutil.scala b/core/shared/src/main/scala/globalutil.scala
new file mode 100644
index 0000000..99dee77
--- /dev/null
+++ b/core/shared/src/main/scala/globalutil.scala
@@ -0,0 +1,59 @@
+package magnolia
+
+import scala.reflect.macros.{runtime, whitebox}
+import scala.tools.nsc.Global
+
+/** Workarounds that needs to use `nsc.Global`. */
+private[magnolia] object GlobalUtil {
+
+ // 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 patchedCompanionRef(c: whitebox.Context)(tpe: c.Type): c.Tree = {
+ // see https://github.com/scalamacros/paradise/issues/7
+ // also see https://github.com/scalamacros/paradise/issues/64
+
+ val global = c.universe.asInstanceOf[Global]
+ val typer = c.asInstanceOf[runtime.Context].callsiteTyper.asInstanceOf[global.analyzer.Typer]
+ val ctx = typer.context
+ val globalType = tpe.asInstanceOf[global.Type]
+ val original = globalType.typeSymbol
+ val owner = original.owner
+ val companion = original.companion.orElse {
+ import global.{ abort => aabort, _ }
+ implicit class PatchedContext(ctx: global.analyzer.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.name.companionName, owner) suchThat { sym =>
+ (original.isTerm || sym.hasModuleFlag) && (sym isCoDefinedWith original)
+ }
+ }
+
+ global.gen.mkAttributedRef(globalType.prefix, companion).asInstanceOf[c.Tree]
+ }
+}
diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala
index 3445dbc..200d38e 100644
--- a/core/shared/src/main/scala/magnolia.scala
+++ b/core/shared/src/main/scala/magnolia.scala
@@ -77,15 +77,6 @@ object Magnolia {
val prefixType = c.prefix.tree.tpe
- def companionRef(tpe: Type): Tree = {
- val global = c.universe match { case global: scala.tools.nsc.Global => global }
- val globalTpe = tpe.asInstanceOf[global.Type]
- val companion = globalTpe.typeSymbol.companionSymbol
- if (companion != NoSymbol)
- global.gen.mkAttributedRef(globalTpe.prefix, companion).asInstanceOf[Tree]
- else q"${tpe.typeSymbol.name.toTermName}"
- }
-
val typeDefs = prefixType.baseClasses.flatMap { cls =>
cls.asType.toType.decls.filter(_.isType).find(_.name.toString == "Typeclass").map { tpe =>
tpe.asType.toType.asSeenFrom(prefixType, cls)
@@ -203,56 +194,6 @@ 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
@@ -280,8 +221,7 @@ object Magnolia {
val className = s"${genericType.typeSymbol.owner.fullName}.${genericType.typeSymbol.name.decodedName}"
val result = if (isCaseObject) {
- val obj = companionRef(genericType)
-
+ val obj = GlobalUtil.patchedCompanionRef(c)(genericType)
val impl = q"""
${c.prefix}.combine($magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType](
$className, true, false, new $scalaPkg.Array(0), _ => $obj)
@@ -340,12 +280,13 @@ object Magnolia {
val preAssignments = caseParams.map(_.typeclass)
val defaults = if (!isValueClass) {
- val caseClassCompanion = patchedCompanionSymbolOf(genericType.typeSymbol).asModule.info
+ val companionRef = GlobalUtil.patchedCompanionRef(c)(genericType)
+ val companionSym = companionRef.symbol.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)
+ companionSym.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
@@ -356,7 +297,7 @@ object Magnolia {
case (p, idx) =>
if (p.isParamWithDefault) {
val method = TermName("apply$default$" + (idx + 1))
- q"$scalaPkg.Some(${genericType.typeSymbol.companion.asTerm}.$method)"
+ q"$scalaPkg.Some($companionRef.$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 ca3cc71..254a516 100644
--- a/tests/src/main/scala/tests.scala
+++ b/tests/src/main/scala/tests.scala
@@ -61,10 +61,9 @@ case class Account(id: String, emails: String*)
case class Portfolio(companies: Company*)
-
object Tests extends TestApp {
- def tests() = for (i <- 1 to 1) {
+ def tests(): Unit = for (i <- 1 to 1) {
import examples._
test("construct a Show product instance with alternative apply functions") {
@@ -276,21 +275,37 @@ object Tests extends TestApp {
}
}.assert(_ == "scala.Symbol cannot be cast to java.lang.Integer")
- class ParentClass() {
- case class LocalClass(name: String)
+ class ParentClass {
+ case class InnerClass(name: String)
+ case class InnerClassWithDefault(name: String = "foo")
+
+ def testInner(): Unit = {
+ test("serialize a case class inside another class") {
+ implicitly[Show[String, InnerClass]].show(InnerClass("foo"))
+ }.assert(_ == "InnerClass(name=foo)")
- test("serialize a case class inside another class") {
- implicitly[Show[String, LocalClass]].show(LocalClass("foo"))
- }.assert(_ == "LocalClass(name=foo)")
+ test("construct a default case class inside another class") {
+ Default.gen[InnerClassWithDefault].default
+ }.assert(_ == InnerClassWithDefault("foo"))
+ }
- case class LocalClassWithDefault(name: String = "foo")
+ def testLocal(): Unit = {
+ case class LocalClass(name: String)
+ case class LocalClassWithDefault(name: String = "foo")
- test("construct a default case class inside another class") {
- Default.gen[LocalClassWithDefault].default
- }.assert(_ == LocalClassWithDefault("foo"))
+ test("serialize a case class inside a method") {
+ implicitly[Show[String, LocalClass]].show(LocalClass("foo"))
+ }.assert(_ == "LocalClass(name=foo)")
+
+ test("construct a default case class inside a method") {
+ Default.gen[LocalClassWithDefault].default
+ }.assert(_ == LocalClassWithDefault("foo"))
+ }
}
- new ParentClass()
+ val parent = new ParentClass()
+ parent.testInner()
+ parent.testLocal()
test("show an Account") {
Show.gen[Account].show(Account("john_doe", "john.doe@yahoo.com", "john.doe@gmail.com"))
@@ -312,6 +327,5 @@ object Tests extends TestApp {
implicit val stringTypeName: TypeName[String] = new TypeName[String] { def name = "" }
TypeName.gen[Fruit].name
}.assert(_ == "magnolia.tests.Fruit")
- ()
}
}