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 ++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 29 deletions(-) (limited to 'core/shared/src') 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") -- cgit v1.2.3