From 89478a78f807d9bb28dcf6bdc60a16a391a3524c Mon Sep 17 00:00:00 2001 From: Jon Pretty Date: Thu, 28 Dec 2017 11:56:11 +0000 Subject: Scala-fmt updates --- benchmarks/2.12/src/adt1.scala | 63 ++--- benchmarks/2.12/src/foo.scala | 1 + benchmarks/2.12/src/kittens/one.scala | 53 ++++ benchmarks/2.12/src/magnolia/one.scala | 49 ++++ benchmarks/2.12/src/magnolia/show_tree_50.scala | 100 +++---- benchmarks/2.12/src/runtime.scala | 66 +++++ benchmarks/2.12/src/scalaz-deriving/one.scala | 55 ++++ core/shared/src/main/scala/interface.scala | 6 +- core/shared/src/main/scala/magnolia.scala | 330 ++++++++++++++---------- examples/shared/src/main/scala/default.scala | 2 +- examples/shared/src/main/scala/show.scala | 19 +- examples/shared/src/main/scala/typename.scala | 3 +- tests/src/main/scala/tests.scala | 30 ++- 13 files changed, 551 insertions(+), 226 deletions(-) create mode 100644 benchmarks/2.12/src/foo.scala create mode 100644 benchmarks/2.12/src/kittens/one.scala create mode 100644 benchmarks/2.12/src/magnolia/one.scala create mode 100644 benchmarks/2.12/src/runtime.scala create mode 100644 benchmarks/2.12/src/scalaz-deriving/one.scala diff --git a/benchmarks/2.12/src/adt1.scala b/benchmarks/2.12/src/adt1.scala index f9ab65f..2efbdd4 100644 --- a/benchmarks/2.12/src/adt1.scala +++ b/benchmarks/2.12/src/adt1.scala @@ -20,40 +20,45 @@ import Scalaz._ @deriving(Show, Equal) sealed trait Alphabet @deriving(Show, Equal) case class Greek(άλφα: Letter, - βήτα: Letter, - γάμα: Letter, - δέλτα: Letter, - έψιλον: Letter, - ζήτα: Letter, - ήτα: Letter, - θήτα: Letter) + βήτα: Letter, + γάμα: Letter, + δέλτα: Letter, + έψιλον: Letter, + ζήτα: Letter, + ήτα: Letter, + θήτα: Letter) extends Alphabet -@deriving(Show, Equal) case class Cyrillic(б: Letter, в: Letter, г: Letter, д: Letter, ж: Letter, з: Letter) +@deriving(Show, Equal) case class Cyrillic(б: Letter, + в: Letter, + г: Letter, + д: Letter, + ж: Letter, + з: Letter) extends Alphabet @deriving(Show, Equal) case class Latin(a: Letter, - b: Letter, - c: Letter, - d: Letter, - e: Letter, - f: Letter, - g: Letter, - h: Letter, - i: Letter, - j: Letter, - k: Letter, - l: Letter, - m: Letter, - n: Letter, - o: Letter, - p: Letter, - q: Letter, - r: Letter, - s: Letter, - t: Letter, - u: Letter, - v: Letter) + b: Letter, + c: Letter, + d: Letter, + e: Letter, + f: Letter, + g: Letter, + h: Letter, + i: Letter, + j: Letter, + k: Letter, + l: Letter, + m: Letter, + n: Letter, + o: Letter, + p: Letter, + q: Letter, + r: Letter, + s: Letter, + t: Letter, + u: Letter, + v: Letter) extends Alphabet //@deriving(Show, Equal) case class Letter(name: String, phonetic: String) diff --git a/benchmarks/2.12/src/foo.scala b/benchmarks/2.12/src/foo.scala new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/benchmarks/2.12/src/foo.scala @@ -0,0 +1 @@ + diff --git a/benchmarks/2.12/src/kittens/one.scala b/benchmarks/2.12/src/kittens/one.scala new file mode 100644 index 0000000..9e54511 --- /dev/null +++ b/benchmarks/2.12/src/kittens/one.scala @@ -0,0 +1,53 @@ +import adt._ +import cats._ +import derived._ +import cats.Show +import cats.instances.all._ + +sealed trait Alphabet + +case class Greek(άλφα: Letter, + βήτα: Letter, + γάμα: Letter, + δέλτα: Letter, + έψιλον: Letter, + ζήτα: Letter, + ήτα: Letter, + θήτα: Letter) + extends Alphabet + +case class Cyrillic(б: Letter, в: Letter, г: Letter, д: Letter, ж: Letter, з: Letter) + extends Alphabet + +case class Latin(a: Letter, + b: Letter, + c: Letter, + d: Letter, + e: Letter, + f: Letter, + g: Letter, + h: Letter, + i: Letter, + j: Letter, + k: Letter, + l: Letter, + m: Letter, + n: Letter, + o: Letter, + p: Letter, + q: Letter, + r: Letter, + s: Letter, + t: Letter, + u: Letter, + v: Letter) + extends Alphabet + +case class Letter(name: String, phonetic: String) +case class Country(name: String, language: Language) +case class Language(name: String, code: String, alphabet: Latin) + +object Gen { + derive.show[Alphabet] + derive.eq[Alphabet] +} diff --git a/benchmarks/2.12/src/magnolia/one.scala b/benchmarks/2.12/src/magnolia/one.scala new file mode 100644 index 0000000..4fb325c --- /dev/null +++ b/benchmarks/2.12/src/magnolia/one.scala @@ -0,0 +1,49 @@ +import magnolia._, examples._ + +sealed trait Alphabet + +case class Greek(άλφα: Letter, + βήτα: Letter, + γάμα: Letter, + δέλτα: Letter, + έψιλον: Letter, + ζήτα: Letter, + ήτα: Letter, + θήτα: Letter) + extends Alphabet + +case class Cyrillic(б: Letter, в: Letter, г: Letter, д: Letter, ж: Letter, з: Letter) + extends Alphabet + +case class Latin(a: Letter, + b: Letter, + c: Letter, + d: Letter, + e: Letter, + f: Letter, + g: Letter, + h: Letter, + i: Letter, + j: Letter, + k: Letter, + l: Letter, + m: Letter, + n: Letter, + o: Letter, + p: Letter, + q: Letter, + r: Letter, + s: Letter, + t: Letter, + u: Letter, + v: Letter) + extends Alphabet + +case class Letter(name: String, phonetic: String) +case class Country(name: String, language: Language) +case class Language(name: String, code: String, alphabet: Latin) + +object Gen { + Show.gen[adt.Alphabet] + Eq.gen[adt.Alphabet] +} diff --git a/benchmarks/2.12/src/magnolia/show_tree_50.scala b/benchmarks/2.12/src/magnolia/show_tree_50.scala index a06513a..3e593e7 100644 --- a/benchmarks/2.12/src/magnolia/show_tree_50.scala +++ b/benchmarks/2.12/src/magnolia/show_tree_50.scala @@ -1,58 +1,58 @@ import magnolia._, examples._ object Gen { - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] - Show.generic[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] + Show.gen[adt.Tree] } diff --git a/benchmarks/2.12/src/runtime.scala b/benchmarks/2.12/src/runtime.scala new file mode 100644 index 0000000..6a94163 --- /dev/null +++ b/benchmarks/2.12/src/runtime.scala @@ -0,0 +1,66 @@ +import adt._ +import cats._ +import derived._ +import cats.Show +import cats.instances.all._ +import estrapade._ +import magnolia._ + +object Gen extends TestApp { + + val latin = Latin( + Letter("one", "two"), + Letter("three", "four"), + Letter("five", "six"), + Letter("seven", "eight"), + Letter("nine", "ten"), + Letter("eleven", "twelve"), + Letter("one", "two"), + Letter("three", "four"), + Letter("five", "six"), + Letter("seven", "eight"), + Letter("nine", "ten"), + Letter("eleven", "twelve"), + Letter("one", "two"), + Letter("three", "four"), + Letter("five", "six"), + Letter("seven", "eight"), + Letter("nine", "ten"), + Letter("eleven", "twelve"), + Letter("one", "two"), + Letter("three", "four"), + Letter("five", "six"), + Letter("seven", "eight") + ) + + def tests() = { + println("Warming up JVM") + + var n = 0 + var s: Any = null + while (n < 5000000) { + s = derive.show[Latin] //.show(latin) + s = magnolia.examples.Show.generic[Latin] //.show(latin) + n += 1 + } + + println("Warm") + test("Kittens") { + var n = 0 + var s: Any = null + while (n < 1000000) { + s = derive.show[Latin] //.show(latin) + n += 1 + } + }.returns() + + test("Magnolia") { + var n = 0 + var s: Any = null + while (n < 1000000) { + s = magnolia.examples.Show.generic[Latin] //.show(latin) + n += 1 + } + }.returns() + } +} diff --git a/benchmarks/2.12/src/scalaz-deriving/one.scala b/benchmarks/2.12/src/scalaz-deriving/one.scala new file mode 100644 index 0000000..ecd0da2 --- /dev/null +++ b/benchmarks/2.12/src/scalaz-deriving/one.scala @@ -0,0 +1,55 @@ +import scalaz._ +import Scalaz._ + +@deriving(Show, Equal) sealed trait Alphabet + +@deriving(Show, Equal) case class Greek(άλφα: Letter, + βήτα: Letter, + γάμα: Letter, + δέλτα: Letter, + έψιλον: Letter, + ζήτα: Letter, + ήτα: Letter, + θήτα: Letter) + extends Alphabet + +@deriving(Show, Equal) case class Cyrillic(б: Letter, + в: Letter, + г: Letter, + д: Letter, + ж: Letter, + з: Letter) + extends Alphabet + +@deriving(Show, Equal) case class Latin(a: Letter, + b: Letter, + c: Letter, + d: Letter, + e: Letter, + f: Letter, + g: Letter, + h: Letter, + i: Letter, + j: Letter, + k: Letter, + l: Letter, + m: Letter, + n: Letter, + o: Letter, + p: Letter, + q: Letter, + r: Letter, + s: Letter, + t: Letter, + u: Letter, + v: Letter) + extends Alphabet + +@deriving(Show, Equal) case class Letter(name: String, phonetic: String) +@deriving(Show, Equal) case class Country(name: String, language: Language) +@deriving(Show, Equal) case class Language(name: String, code: String, alphabet: Latin) + +object Gen { + Show[Alphabet] + Equal[Alphabet] +} diff --git a/core/shared/src/main/scala/interface.scala b/core/shared/src/main/scala/interface.scala index 5043680..c3037ee 100644 --- a/core/shared/src/main/scala/interface.scala +++ b/core/shared/src/main/scala/interface.scala @@ -102,13 +102,15 @@ trait Param[Typeclass[_], Type] { * @param parametersArray an array of [[Param]] values for this case class * @tparam Typeclass type constructor for the typeclass being derived * @tparam Type generic type of this parameter */ -abstract class CaseClass[Typeclass[_], Type, ParamType] private[magnolia] ( +abstract class CaseClass[Typeclass[_], Type, PType] private[magnolia] ( val typeName: String, val isObject: Boolean, val isValueClass: Boolean, - parametersArray: Array[ParamType] + parametersArray: Array[PType] ) { + type ParamType = PType + /** constructs a new instance of the case class type * * This method will be implemented by the Magnolia macro to make it possible to construct diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index 9f3bda8..7e0600c 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -75,6 +75,9 @@ object Magnolia { val prefixType = c.prefix.tree.tpe + def fail(msg: String): Nothing = c.abort(c.enclosingPosition, s"magnolia: $msg") + def info(msg: String): Unit = c.info(c.enclosingPosition, s"magnolia: $msg", true) + def companionRef(tpe: Type): Tree = { val global = c.universe match { case global: scala.tools.nsc.Global => global } val globalTpe = tpe.asInstanceOf[global.Type] @@ -84,7 +87,6 @@ object Magnolia { else q"${tpe.typeSymbol.name.toTermName}" } - def getTypeMember(name: String) = { val typeDefs = prefixType.baseClasses.flatMap { cls => cls.asType.toType.decls.filter(_.isType).find(_.name.toString == name).map { tpe => @@ -94,10 +96,8 @@ object Magnolia { val typeConstructorOpt = typeDefs.headOption.map(_.typeConstructor) - typeConstructorOpt.getOrElse { - c.abort(c.enclosingPosition, - s"magnolia: the derivation object does not define the $name type constructor") - } + typeConstructorOpt getOrElse + fail("the derivation object does not define the $name type constructor") } val typeConstructor = getTypeMember("Typeclass") @@ -111,20 +111,30 @@ object Magnolia { } // FIXME: Only run these methods if they're used, particularly `dispatch` - getMethod("combine").getOrElse(c.abort(c.enclosingPosition, - s"magnolia: the method `dispatch` should take a single parameter of type CaseClass[Typeclass, _]") - ) - - getMethod("dispatch").getOrElse(c.abort(c.enclosingPosition, - s"magnolia: the method `combine` should take a single parameter of type SealedTrait[Typeclass, _]") - ) + lazy val combineMethod = getMethod("combine") + .map { m => + q"${c.prefix}.combine" + } + .getOrElse { + fail("the method `combine` should be defined, taking a single parameter of type CaseClass[Typeclass, _]") + } + + lazy val dispatchMethod = getMethod("dispatch") + .map { m => + q"${c.prefix}.dispatch" + } + .getOrElse { + fail( + s"the method `dispatch` should be defined, taking a single parameter of type SealedTrait[Typeclass, _]" + ) + } def findType(key: Type): Option[TermName] = recursionStack(c.enclosingPosition).frames.find(_.genericType == key).map(_.termName(c)) - case class Typeclass(typ: c.Type, tree: c.Tree) + case class Implicit(typ: c.Type, tree: c.Tree) - def recurse[T](path: TypePath, key: Type, value: TermName)(fn: => T): Option[T] = { + def search[T](path: TypePath, key: Type, value: TermName)(fn: => T): Option[T] = { val oldRecursionStack = recursionStack.get(c.enclosingPosition) recursionStack = recursionStack.updated( c.enclosingPosition, @@ -170,7 +180,7 @@ object Magnolia { val assignedName: TermName = TermName(c.freshName(s"${genericTypeName}Typeclass")) - recurse(ChainedImplicit(genericType.toString), genericType, assignedName) { + search(ChainedImplicit(genericType.toString), genericType, assignedName) { c.inferImplicitValue(searchType, false, false) }.get } @@ -195,8 +205,7 @@ object Magnolia { val stackPaths = recursionStack(c.enclosingPosition).frames.map(_.path) val stack = stackPaths.mkString(" in ", "\n in ", "\n") - c.abort(c.enclosingPosition, - s"magnolia: could not find typeclass for type $genericType\n$stack") + fail(s"could not find typeclass for type $genericType\n$stack") } } @@ -208,18 +217,24 @@ object Magnolia { // 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 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, _ } + import global._ 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 = { + def suchThat(criterion: Symbol => Boolean): Symbol = { var res: Symbol = NoSymbol var ctx = PatchedContext.this.ctx while (res == NoSymbol && ctx.outer != ctx) { @@ -229,9 +244,12 @@ object Magnolia { val s = { val lookupResult = ctx.scope.lookupAll(name).filter(criterion).toList lookupResult match { - case Nil => NoSymbol + case Nil => NoSymbol case List(unique) => unique - case _ => aabort(s"unexpected multiple results for a companion symbol lookup for $original#{$original.id}") + case _ => + fail( + s"unexpected multiple results for a companion symbol lookup for $original#{$original.id}" + ) } } if (s != NoSymbol && s.owner == expectedOwner) @@ -243,16 +261,24 @@ object Magnolia { } } } - 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] + + 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] = { + def directInferImplicit(genericType: c.Type, typeConstructor: Type): Option[Implicit] = { + + lazy val genericTypeName: String = + genericType.typeSymbol.name.decodedName.toString.toLowerCase - val genericTypeName: String = genericType.typeSymbol.name.decodedName.toString.toLowerCase lazy val assignedName: TermName = TermName(c.freshName(s"${genericTypeName}Typeclass")) lazy val typeSymbol = genericType.typeSymbol lazy val classType = if (typeSymbol.isClass) Some(typeSymbol.asClass) else None @@ -261,58 +287,78 @@ object Magnolia { lazy val isSealedTrait = classType.exists(_.isSealed) lazy val primitives = Set(typeOf[Double], - typeOf[Float], - typeOf[Short], - typeOf[Byte], - typeOf[Int], - typeOf[Long], - typeOf[Char], - typeOf[Boolean], - typeOf[Unit]) - - lazy val isValueClass = genericType <:< typeOf[AnyVal] && !primitives.exists(_ =:= genericType) + typeOf[Float], + typeOf[Short], + typeOf[Byte], + typeOf[Int], + typeOf[Long], + typeOf[Char], + typeOf[Boolean], + typeOf[Unit]) + + lazy val isValueClass = genericType <:< typeOf[AnyVal] && !primitives.exists( + _ =:= genericType + ) lazy val resultType = appliedType(typeConstructor, genericType) - lazy val liftedParamType: Tree = getMethod("param").map { sym => - tq"${c.prefix}.ParamType[$genericType, _]" - }.getOrElse(tq"$magnoliaPkg.Param[$typeConstructor, $genericType]") - - lazy val liftedSubtypeType: Tree = getMethod("subtype").map { sym => - tq"${c.prefix}.SubtypeType[$genericType, _]" - }.getOrElse(tq"$magnoliaPkg.Subtype[$typeConstructor, $genericType]") - - lazy val caseClassMethod = getMethod("caseClass").map { sym => - q"${c.prefix}.caseClass[$genericType, $liftedParamType]" - }.getOrElse(q"$magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType, $liftedParamType]") - - lazy val caseClassParamNames = getMethod("caseClass").map { sym => - sym.paramLists.head.map(_.name.decodedName.toString) - }.getOrElse(List("name", "isCaseObject", "isValueClass", "parameters", "constructor")) - - lazy val sealedTraitMethod = getMethod("sealedTrait").map { sym => - q"${c.prefix}.sealedTrait[$genericType, $liftedParamType]" - }.getOrElse(q"$magnoliaPkg.Magnolia.sealedTrait[$typeConstructor, $genericType, $liftedParamType]") - - lazy val sealedTraitParamNames = getMethod("sealedTrait").map { sym => - sym.paramLists.head.map(_.name.decodedName.toString) - }.getOrElse(List("name", "subtypes")) - - val className = s"${genericType.typeSymbol.owner.fullName}.${genericType.typeSymbol.name.decodedName}" + lazy val liftedParamType: Tree = getMethod("param") + .map { sym => + tq"${c.prefix}.ParamType[$genericType, _]" + } + .getOrElse(tq"$magnoliaPkg.Param[$typeConstructor, $genericType]") + + lazy val liftedSubtypeType: Tree = getMethod("subtype") + .map { sym => + tq"${c.prefix}.SubtypeType[$genericType, _]" + } + .getOrElse(tq"$magnoliaPkg.Subtype[$typeConstructor, $genericType]") + + lazy val caseClassMethod = getMethod("caseClass") + .map { sym => + q"${c.prefix}.caseClass[$genericType, $liftedParamType]" + } + .getOrElse( + q"$magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType, $liftedParamType]" + ) + + lazy val caseClassParamNames = getMethod("caseClass") + .map { sym => + sym.paramLists.head.map(_.name.decodedName.toString) + } + .getOrElse(List("name", "isCaseObject", "isValueClass", "parameters", "constructor")) + + lazy val sealedTraitMethod = getMethod("sealedTrait") + .map { sym => + q"${c.prefix}.sealedTrait" + } + .getOrElse( + q"$magnoliaPkg.Magnolia.sealedTrait[$typeConstructor, $genericType, $liftedParamType]" + ) + + lazy val sealedTraitParamNames = getMethod("sealedTrait") + .map { sym => + sym.paramLists.head.map(_.name.decodedName.toString) + } + .getOrElse(List("name", "subtypes")) + + val className = + s"${genericType.typeSymbol.owner.fullName}.${genericType.typeSymbol.name.decodedName}" val result = if (isCaseObject) { val obj = companionRef(genericType) val parameters = caseClassParamNames.map { - case "name" => q"$className" + case "name" => q"$className" case "isCaseObject" => q"true" case "isValueClass" => q"false" - case "parameters" => q"new $scalaPkg.Array(0)" - case "constructor" => q"(_ => $obj)" + case "parameters" => q"new $scalaPkg.Array(0)" + case "constructor" => q"(_ => $obj)" } - val impl = q"${c.prefix}.combine($caseClassMethod(..$parameters))" - Some(Typeclass(genericType, impl)) + val impl = q"$combineMethod($caseClassMethod(..$parameters))" + + Some(Implicit(genericType, impl)) } else if (isCaseClass || isValueClass) { val caseClassParameters = genericType.decls.collect { case m: MethodSymbol if m.isCaseAccessor || (isValueClass && m.isParamAccessor) => @@ -345,10 +391,10 @@ object Magnolia { caseParamOpt.getOrElse { val derivedImplicit = - recurse(ProductType(paramName, genericType.toString), genericType, assignedName) { + search(ProductType(paramName, genericType.toString), genericType, assignedName) { typeclassTree(Some(paramName), paramType, typeConstructor, assignedName) }.getOrElse( - c.abort(c.enclosingPosition, s"failed to get implicit for type $genericType") + fail(s"failed to get implicit for type $genericType") ) val ref = TermName(c.freshName("paramTypeclass")) @@ -388,44 +434,54 @@ object Magnolia { val assignments = caseParams.zip(defaults).zipWithIndex.map { case ((CaseParam(param, repeated, typeclass, paramType, ref), defaultVal), idx) => - val paramMethod: Tree = getMethod("param").map { sym => - q"${c.prefix}.param" - }.getOrElse(q"$magnoliaPkg.Magnolia.param[$typeConstructor, $genericType, $paramType]") + val paramMethod: Tree = getMethod("param") + .map { sym => + q"${c.prefix}.param" + } + .getOrElse(q"$magnoliaPkg.Magnolia.param[$typeConstructor, $genericType, $paramType]") - val paramNames = getMethod("param").map { sym => - sym.paramLists.head.map(_.name.decodedName.toString) - }.getOrElse(List("name", "repeated", "typeclass", "default", "dereference")) + val paramNames = getMethod("param") + .map { sym => + sym.paramLists.head.map(_.name.decodedName.toString) + } + .getOrElse(List("name", "repeated", "typeclass", "default", "dereference")) val parameters: List[Tree] = paramNames.map { - case "name" => q"${param.name.decodedName.toString}" - case "repeated" => q"$repeated" - case "typeclass" => q"$ref" - case "default" => q"$defaultVal" + case "name" => q"${param.name.decodedName.toString}" + case "repeated" => q"$repeated" + case "typeclass" => q"$ref" + case "default" => q"$defaultVal" case "dereference" => q"_.${param.name}" case other => - c.abort(c.enclosingPosition, s"magnolia: method 'param' has an unexpected parameter with name '$other'; permitted parameter names: default, dereference, name, repeated, typeclass") + fail( + s"method 'param' has an unexpected parameter with name '$other'; permitted parameter names: default, dereference, name, repeated, typeclass" + ) } q"""$paramsVal($idx) = $paramMethod(..$parameters)""" } val parameters = caseClassParamNames.map { - case "name" => q"$className" + case "name" => q"$className" case "isCaseObject" => q"false" case "isValueClass" => q"$isValueClass" - case "parameters" => q"$paramsVal" - case "constructor" => q""" + case "parameters" => q"$paramsVal" + case "constructor" => + q""" ($fnVal: $liftedParamType => Any) => new $genericType(..${caseParams.zipWithIndex.map { - case (typeclass, idx) => - val arg = q"$fnVal($paramsVal($idx)).asInstanceOf[${typeclass.paramType}]" - if (typeclass.repeated) q"$arg: _*" else arg - }}) - """ + case (typeclass, idx) => + val arg = q"$fnVal($paramsVal($idx)).asInstanceOf[${typeclass.paramType}]" + if (typeclass.repeated) q"$arg: _*" else arg + }})""" + case other => + fail( + s"method 'caseClass' has an unexpected parameter with name '$other'; permitted parameter names: name, isCaseClass, isValueClass, parameters, constructor" + ) } - val impl = q"${c.prefix}.combine($caseClassMethod(..$parameters))" + val impl = q"$combineMethod($caseClassMethod(..$parameters))" Some( - Typeclass( + Implicit( genericType, q"""{ ..$preAssignments @@ -433,7 +489,7 @@ object Magnolia { new $scalaPkg.Array(${assignments.length}) ..$assignments - ${c.prefix}.combine($caseClassMethod(..$parameters)) + $combineMethod($caseClassMethod(..$parameters)) }""" ) ) @@ -450,50 +506,60 @@ object Magnolia { } if (subtypes.isEmpty) { - c.info(c.enclosingPosition, - s"magnolia: could not find any direct subtypes of $typeSymbol", - true) - - c.abort(c.enclosingPosition, "") + info(s"could not find any direct subtypes of $typeSymbol") + fail("") } val subtypesVal: TermName = TermName(c.freshName("subtypes")) val typeclasses = subtypes.map { searchType => - recurse(CoproductType(genericType.toString), genericType, assignedName) { + search(CoproductType(genericType.toString), genericType, assignedName) { (searchType, typeclassTree(None, searchType, typeConstructor, assignedName)) }.getOrElse { - c.abort(c.enclosingPosition, s"failed to get implicit for type $searchType") + fail(s"failed to get implicit for type $searchType") } } val assignments = typeclasses.zipWithIndex.map { case ((subtype, typeclass), idx) => - val subtypeMethod: Tree = getMethod("subtype").map { sym => - q"${c.prefix}.subtype" - }.getOrElse(q"$magnoliaPkg.Magnolia.subtype[$typeConstructor, $genericType, $subtype]") - - val subtypeParamNames = getMethod("subtype").map { sym => - sym.paramLists.head.map(_.name.decodedName.toString) - }.getOrElse(List("name", "typeclass", "isType", "asType")) - + val subtypeMethod: Tree = getMethod("subtype") + .map { sym => + q"${c.prefix}.subtype" + } + .getOrElse(q"$magnoliaPkg.Magnolia.subtype[$typeConstructor, $genericType, $subtype]") + + val subtypeParamNames = getMethod("subtype") + .map { sym => + sym.paramLists.head.map(_.name.decodedName.toString) + } + .getOrElse(List("name", "typeclass", "isType", "asType")) + val parameters = subtypeParamNames.map { - case "name" => q"${subtype.typeSymbol.fullName.toString}" + case "name" => q"${subtype.typeSymbol.fullName.toString}" case "typeclass" => q"$typeclass" - case "isType" => q"(t: $genericType) => t.isInstanceOf[$subtype]" - case "asType" => q"(t: $genericType) => t.asInstanceOf[$subtype]" + case "isType" => q"(t: $genericType) => t.isInstanceOf[$subtype]" + case "asType" => q"(t: $genericType) => t.asInstanceOf[$subtype]" + case other => + fail( + s"method 'subtype' has an unexpected parameter with name '$other'; permitted parameter names: name, typeclass, isType, asType" + ) } - + q"""$subtypesVal($idx) = $subtypeMethod(..$parameters)""" } val parameters = sealedTraitParamNames.map { - case "name" => q"""${s"${genericType.typeSymbol.owner.fullName}.${genericType.typeSymbol.name.decodedName}"}""" + case "name" => + q"""${s"${genericType.typeSymbol.owner.fullName}.${genericType.typeSymbol.name.decodedName}"}""" case "subtypes" => q"$subtypesVal: $scalaPkg.Array[$liftedSubtypeType]" + case other => + fail( + s"method 'sealedTrait' has an unexpected parameter with name '$other'; permitted parameter names: name, subtypes" + ) } - + Some { - Typeclass( + Implicit( genericType, q"""{ val $subtypesVal: $scalaPkg.Array[$liftedSubtypeType] = @@ -501,15 +567,15 @@ object Magnolia { ..$assignments - ${c.prefix}.dispatch($sealedTraitMethod(..$parameters)): $resultType + $dispatchMethod($sealedTraitMethod(..$parameters)): $resultType }""" ) } } else None result.map { - case Typeclass(t, r) => - Typeclass(t, q"""{ + case Implicit(t, r) => + Implicit(t, q"""{ lazy val $assignedName: $resultType = $r $assignedName }""") @@ -530,10 +596,9 @@ object Magnolia { emittedErrors += error val trace = error.path.mkString("\n in ", "\n in ", "\n \n") - val msg = s"magnolia: could not derive $typeConstructor instance for type " + - s"${error.genericType}" + val msg = s"could not derive $typeConstructor instance for type ${error.genericType}" - c.info(c.enclosingPosition, msg + trace, true) + info(msg + trace) } } @@ -555,7 +620,7 @@ object Magnolia { } dereferencedResult.getOrElse { - c.abort(c.enclosingPosition, s"magnolia: could not infer typeclass for type $genericType") + fail(s"could not infer typeclass for type $genericType") } } @@ -563,7 +628,10 @@ object Magnolia { * * This method is intended to be called only from code generated by the Magnolia macro, and * should not be called directly from users' code. */ - def subtype[Tc[_], T, S <: T](name: String, typeclass: => Tc[S], isType: T => Boolean, asType: T => S) = { + def subtype[Tc[_], T, S <: T](name: String, + typeclass: => Tc[S], + isType: T => Boolean, + asType: T => S) = { lazy val typeclassVal = typeclass new Subtype[Tc, T] { type SType = S @@ -589,7 +657,7 @@ object Magnolia { val defaultVal = default val dereferenceVal = dereference val repeatedVal = repeated - + new Param[Tc, T] { type PType = P def label: String = name @@ -604,15 +672,15 @@ object Magnolia { * * This method is intended to be called only from code generated by the Magnolia macro, and * should not be called directly from users' code. */ - def caseClass[Tc[_], T, ParamType](name: String, - isCaseObject: Boolean, - isValueClass: Boolean, - parameters: Array[ParamType], - constructor: (ParamType => Any) => T) = - new CaseClass[Tc, T, ParamType](name, isCaseObject, isValueClass, parameters) { - def construct[R](param: ParamType => R): T = constructor(param) + def caseClass[Tc[_], T, PType](name: String, + isCaseObject: Boolean, + isValueClass: Boolean, + parameters: Array[PType], + constructor: (PType => Any) => T): CaseClass[Tc, T, PType] = + new CaseClass[Tc, T, PType](name, isCaseObject, isValueClass, parameters) { + def construct[R](param: PType => R): T = constructor(param) } - + /** constructs a new [[CaseClass]] instance * * This method is intended to be called only from code generated by the Magnolia macro, and diff --git a/examples/shared/src/main/scala/default.scala b/examples/shared/src/main/scala/default.scala index bce11d6..e05af52 100644 --- a/examples/shared/src/main/scala/default.scala +++ b/examples/shared/src/main/scala/default.scala @@ -14,7 +14,7 @@ object Default { /** constructs a default for each parameter, using the constructor default (if provided), * otherwise using a typeclass-provided default */ - def combine[T](ctx: CaseClass[Default, T, Param[Default, T]]): Default[T] = new Default[T] { + def combine[T](ctx: CaseClass[Default, T]): Default[T] = new Default[T] { def default = ctx.construct { param => param.default.getOrElse(param.typeclass.default) } diff --git a/examples/shared/src/main/scala/show.scala b/examples/shared/src/main/scala/show.scala index ee43dfc..b6b546d 100644 --- a/examples/shared/src/main/scala/show.scala +++ b/examples/shared/src/main/scala/show.scala @@ -34,22 +34,33 @@ trait GenericShow[Out] { def join(typeName: String, strings: Seq[String]): Out - def param[T, P](name: String, typeclass: Show[Out, P], dereference: T => P)(implicit auto: Dflt[P]) = + def param[T, P](name: String, typeclass: Show[Out, P], dereference: T => P)( + implicit auto: Dflt[P] + ) = ShowParam[Out, T, P](name, typeclass, dereference) def caseClass[T, P](name: String, parameters: Array[P], isValueClass: Boolean): Derivation[T, P] = Derivation(name, isValueClass, parameters) - /*def subtype[T, S <: T](name: String, typeclassParam: => Show[Out, S], isType: T => Boolean, asType: T => S): Subtype[Typeclass, T] = + def subtype[T, S <: T](name: String, + typeclass: => Show[Out, S], + isType: T => Boolean, + asType: T => S): Subtype[Typeclass, T] = { + def typeclassVal = typeclass new Subtype[Typeclass, T] { type SType = S def label = name - def typeclass = typeclassParam + def typeclass = typeclassVal def cast = new PartialFunction[T, S] { def isDefinedAt(t: T) = isType(t) def apply(t: T): SType = asType(t) } - }*/ + } + } + + def sealedTrait[T](name: String, + subtypes: Array[Subtype[Typeclass, T]]): SealedTrait[Typeclass, T] = + new SealedTrait[Typeclass, T](name, subtypes) /** creates a new [[Show]] instance by labelling and joining (with `mkString`) the result of * showing each parameter, and prefixing it with the class name */ diff --git a/examples/shared/src/main/scala/typename.scala b/examples/shared/src/main/scala/typename.scala index aaa40f7..cdc4b01 100644 --- a/examples/shared/src/main/scala/typename.scala +++ b/examples/shared/src/main/scala/typename.scala @@ -8,7 +8,7 @@ trait TypeName[T] { def name: String } object TypeName { type Typeclass[T] = TypeName[T] - def combine[T](ctx: CaseClass[TypeName, T, _]): TypeName[T] = + def combine[T](ctx: CaseClass[TypeName, T]): TypeName[T] = new TypeName[T] { def name: String = ctx.typeName } def dispatch[T](ctx: SealedTrait[TypeName, T]): TypeName[T] = @@ -16,4 +16,3 @@ object TypeName { implicit def gen[T]: TypeName[T] = macro Magnolia.gen[T] } - diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala index 7b1fbe3..9e82d91 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -59,10 +59,18 @@ case class Account(id: String, emails: String*) case class Portfolio(companies: Company*) +trait ProductOnly[T] +object ProductOnly { + type Typeclass[T] = ProductOnly[T] + def combine[T](ctx: CaseClass[ProductOnly, T, _]): ProductOnly[T] = null + implicit def gen[T]: ProductOnly[T] = macro Magnolia.gen[T] + + implicit val productOnlyString: ProductOnly[String] = null +} object Tests extends TestApp { - def tests() = for(i <- 1 to 1000) { + def tests() = for (i <- 1 to 100) { import examples._ test("construct a Show product instance with alternative apply functions") { @@ -223,12 +231,10 @@ object Tests extends TestApp { // LabelledBox being invariant in L <: String prohibits the derivation for LabelledBox[Int, _] test("can't show a Box with invariant label") { scalac"Show.gen[Box[Int]]" - }.assert { _ == TypecheckError( - txt"""magnolia: could not find typeclass for type L + }.assert { _ == TypecheckError(txt"""magnolia: could not find typeclass for type L | in parameter 'label' of product type magnolia.tests.LabelledBox[Int, _ <: String] | in coproduct type magnolia.tests.Box[Int] - |""") - } + |""") } class ParentClass() { case class LocalClass(name: String) @@ -243,7 +249,7 @@ object Tests extends TestApp { Default.gen[LocalClassWithDefault].default }.assert(_ == LocalClassWithDefault("foo")) } - + new ParentClass() test("show an Account") { @@ -257,7 +263,7 @@ 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)])") - + test("sealed trait typeName should be complete and unchanged") { TypeName.gen[Color].name }.assert(_ == "magnolia.tests.Color") @@ -266,6 +272,16 @@ object Tests extends TestApp { implicit val stringTypeName: TypeName[String] = new TypeName[String] { def name = "" } TypeName.gen[Fruit].name }.assert(_ == "magnolia.tests.Fruit") + + test("attempt to derive typeclass without `dispatch` works for products") { + ProductOnly.gen[Fruit] + }.returns() + + test("attempt to derive typeclass without `dispatch` fails for coproducts") { + scalac"ProductOnly.gen[Color]" + }.assert(_ == TypecheckError("""magnolia: the method `dispatch` should be defined, taking a """+ + """single parameter of type SealedTrait[Typeclass, _]""")) + () } } -- cgit v1.2.3