From c91b7cac849237062eea8a75134783e826dc21a0 Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Wed, 13 Dec 2017 00:40:06 +0100 Subject: Move pathedCompanionRef to a GlobalUtil object That method is a workaround for a bug. It doesn't belong in the core Magnolia macro. Also: * Use `patchedCompanionRef` consistently * Add a test for the workaround (method-local classes) --- core/shared/src/main/scala/globalutil.scala | 59 ++++++++++++++++++++++++ core/shared/src/main/scala/magnolia.scala | 69 +++-------------------------- 2 files changed, 64 insertions(+), 64 deletions(-) create mode 100644 core/shared/src/main/scala/globalutil.scala (limited to 'core') 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 f55dfa7..7e99192 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -75,15 +75,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) @@ -201,56 +192,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 @@ -278,8 +219,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) @@ -338,12 +278,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 @@ -354,7 +295,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") -- cgit v1.2.3