aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Pretty <jon.pretty@propensive.com>2017-12-28 11:56:11 +0000
committerJon Pretty <jon.pretty@propensive.com>2017-12-28 11:56:11 +0000
commit89478a78f807d9bb28dcf6bdc60a16a391a3524c (patch)
treeb48b1dd51343ae0563082b2f38ec0f5252ce0e0b
parentbd5f2dc411b1d63b9ee4dfec6476ba27d8410e2a (diff)
downloadmagnolia-virtual-params.tar.gz
magnolia-virtual-params.tar.bz2
magnolia-virtual-params.zip
Scala-fmt updatesvirtual-params
-rw-r--r--benchmarks/2.12/src/adt1.scala63
-rw-r--r--benchmarks/2.12/src/foo.scala1
-rw-r--r--benchmarks/2.12/src/kittens/one.scala53
-rw-r--r--benchmarks/2.12/src/magnolia/one.scala49
-rw-r--r--benchmarks/2.12/src/magnolia/show_tree_50.scala100
-rw-r--r--benchmarks/2.12/src/runtime.scala66
-rw-r--r--benchmarks/2.12/src/scalaz-deriving/one.scala55
-rw-r--r--core/shared/src/main/scala/interface.scala6
-rw-r--r--core/shared/src/main/scala/magnolia.scala330
-rw-r--r--examples/shared/src/main/scala/default.scala2
-rw-r--r--examples/shared/src/main/scala/show.scala19
-rw-r--r--examples/shared/src/main/scala/typename.scala3
-rw-r--r--tests/src/main/scala/tests.scala30
13 files changed, 551 insertions, 226 deletions
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, _]"""))
+
()
}
}