aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Pretty <jon.pretty@propensive.com>2017-11-30 19:47:19 +0100
committerGitHub <noreply@github.com>2017-11-30 19:47:19 +0100
commitd4bd8d7c5605d1889be5c82ff475cfb53c9deed9 (patch)
treea8f9ae506a8cdf675949c4f0716ad45aec66b74b
parentd8fb2f5ca2edc1a34ed7831f06d5eca08ba4989c (diff)
parentd88b15e99e2727558bb901fcc08398862f633c11 (diff)
downloadmagnolia-d4bd8d7c5605d1889be5c82ff475cfb53c9deed9.tar.gz
magnolia-d4bd8d7c5605d1889be5c82ff475cfb53c9deed9.tar.bz2
magnolia-d4bd8d7c5605d1889be5c82ff475cfb53c9deed9.zip
Merge branch 'master' into existentials
-rw-r--r--core/shared/src/main/scala/interface.scala11
-rw-r--r--core/shared/src/main/scala/magnolia.scala37
-rw-r--r--examples/shared/src/main/scala/default.scala3
-rw-r--r--examples/shared/src/main/scala/typename.scala19
-rw-r--r--tests/src/main/scala/tests.scala34
5 files changed, 90 insertions, 14 deletions
diff --git a/core/shared/src/main/scala/interface.scala b/core/shared/src/main/scala/interface.scala
index 02c1cc5..193a6f9 100644
--- a/core/shared/src/main/scala/interface.scala
+++ b/core/shared/src/main/scala/interface.scala
@@ -46,6 +46,17 @@ trait Param[Typeclass[_], Type] {
/** the name of the parameter */
def label: String
+ /** flag indicating a repeated (aka. vararg) parameter
+ *
+ * For example, for a case class,
+ * <pre>
+ * case class Account(id: String, emails: String*)
+ * </pre>
+ * the [[Param]] instance corresponding to the `emails` parameter would be `repeated` and have a
+ * [[PType]] equal to the type `Seq[String]`. Note that only the last parameter of a case class
+ * can be repeated. */
+ def repeated: Boolean
+
/** the typeclass instance associated with this parameter
*
* This is the instance of the type `Typeclass[PType]` which will have been discovered by
diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala
index 62a36a5..492d27f 100644
--- a/core/shared/src/main/scala/magnolia.scala
+++ b/core/shared/src/main/scala/magnolia.scala
@@ -70,6 +70,9 @@ object Magnolia {
val magnoliaPkg = q"_root_.magnolia"
val scalaPkg = q"_root_.scala"
+ val repeatedParamClass = definitions.RepeatedParamClass
+ val scalaSeqType = typeOf[Seq[_]].typeConstructor
+
val prefixType = c.prefix.tree.tpe
def companionRef(tpe: Type): Tree = {
@@ -221,10 +224,11 @@ object Magnolia {
val isValueClass = genericType <:< typeOf[AnyVal] && !primitives.exists(_ =:= genericType)
val resultType = appliedType(typeConstructor, genericType)
+
+ val className = s"${genericType.typeSymbol.owner.fullName}.${genericType.typeSymbol.name.decodedName}"
val result = if (isCaseObject) {
val obj = companionRef(genericType)
- val className = genericType.typeSymbol.name.decodedName.toString
val impl = q"""
${c.prefix}.combine($magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType](
@@ -237,9 +241,9 @@ object Magnolia {
case m: MethodSymbol if m.isCaseAccessor || (isValueClass && m.isParamAccessor) =>
m.asMethod
}
- val className = genericType.typeSymbol.name.decodedName.toString
case class CaseParam(sym: c.universe.MethodSymbol,
+ repeated: Boolean,
typeclass: c.Tree,
paramType: c.Type,
ref: c.TermName)
@@ -247,11 +251,19 @@ object Magnolia {
val caseParamsReversed = caseClassParameters.foldLeft[List[CaseParam]](Nil) {
(acc, param) =>
val paramName = param.name.decodedName.toString
- val paramType = param.typeSignatureIn(genericType).resultType
+ val paramTypeSubstituted = param.returnType.typeSignatureIn(genericType).resultType
+
+ val (repeated, paramType) = paramTypeSubstituted match {
+ case TypeRef(_, `repeatedParamClass`, typeArgs) =>
+ true -> appliedType(scalaSeqType, typeArgs)
+ case tpe =>
+ false -> tpe
+ }
+
val predefinedRef = acc.find(_.paramType == paramType)
val caseParamOpt = predefinedRef.map { backRef =>
- CaseParam(param, q"()", paramType, backRef.ref) :: acc
+ CaseParam(param, repeated, q"()", paramType, backRef.ref) :: acc
}
caseParamOpt.getOrElse {
@@ -264,7 +276,7 @@ object Magnolia {
val ref = TermName(c.freshName("paramTypeclass"))
val assigned = q"""val $ref = $derivedImplicit"""
- CaseParam(param, assigned, paramType, ref) :: acc
+ CaseParam(param, repeated, assigned, paramType, ref) :: acc
}
}
@@ -298,10 +310,10 @@ object Magnolia {
} else List(q"$scalaPkg.None")
val assignments = caseParams.zip(defaults).zipWithIndex.map {
- case ((CaseParam(param, typeclass, paramType, ref), defaultVal), idx) =>
+ case ((CaseParam(param, repeated, typeclass, paramType, ref), defaultVal), idx) =>
q"""$paramsVal($idx) = $magnoliaPkg.Magnolia.param[$typeConstructor, $genericType,
$paramType](
- ${param.name.decodedName.toString}, $ref, $defaultVal, _.${param.name}
+ ${param.name.decodedName.toString}, $repeated, $ref, $defaultVal, _.${param.name}
)"""
}
@@ -322,7 +334,8 @@ object Magnolia {
($fnVal: $magnoliaPkg.Param[$typeConstructor, $genericType] => Any) =>
new $genericType(..${caseParams.zipWithIndex.map {
case (typeclass, idx) =>
- q"$fnVal($paramsVal($idx)).asInstanceOf[${typeclass.paramType}]"
+ val arg = q"$fnVal($paramsVal($idx)).asInstanceOf[${typeclass.paramType}]"
+ if (typeclass.repeated) q"$arg: _*" else arg
}})
))
}"""
@@ -361,13 +374,13 @@ object Magnolia {
val assignments = typeclasses.zipWithIndex.map {
case ((typ, typeclass), idx) =>
q"""$subtypesVal($idx) = $magnoliaPkg.Magnolia.subtype[$typeConstructor, $genericType, $typ](
- ${typ.typeSymbol.fullName.toString},
+ ${s"${typ.typeSymbol.owner.fullName}.${typ.typeSymbol.name.decodedName}"},
$typeclass,
(t: $genericType) => t.isInstanceOf[$typ],
(t: $genericType) => t.asInstanceOf[$typ]
)"""
}
-
+
Some {
Typeclass(
genericType,
@@ -378,7 +391,7 @@ object Magnolia {
..$assignments
${c.prefix}.dispatch(new $magnoliaPkg.SealedTrait(
- $genericTypeName,
+ $className,
$subtypesVal: $scalaPkg.Array[$magnoliaPkg.Subtype[$typeConstructor, $genericType]])
): $resultType
}"""
@@ -458,11 +471,13 @@ 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 param[Tc[_], T, P](name: String,
+ isRepeated: Boolean,
typeclassParam: Tc[P],
defaultVal: => Option[P],
deref: T => P) = new Param[Tc, T] {
type PType = P
def label: String = name
+ def repeated: Boolean = isRepeated
def default: Option[PType] = defaultVal
def typeclass: Tc[PType] = typeclassParam
def dereference(t: T): PType = deref(t)
diff --git a/examples/shared/src/main/scala/default.scala b/examples/shared/src/main/scala/default.scala
index 4c1b634..4b781a5 100644
--- a/examples/shared/src/main/scala/default.scala
+++ b/examples/shared/src/main/scala/default.scala
@@ -30,6 +30,9 @@ object Default {
/** default value for ints; 0 */
implicit val int: Default[Int] = new Default[Int] { def default = 0 }
+ /** default value for sequences; the empty sequence */
+ implicit def seq[A]: Default[Seq[A]] = new Typeclass[Seq[A]] { def default = Seq.empty }
+
/** generates default instances of [[Default]] for case classes and sealed traits */
implicit def gen[T]: Default[T] = macro Magnolia.gen[T]
}
diff --git a/examples/shared/src/main/scala/typename.scala b/examples/shared/src/main/scala/typename.scala
new file mode 100644
index 0000000..c1e1fd2
--- /dev/null
+++ b/examples/shared/src/main/scala/typename.scala
@@ -0,0 +1,19 @@
+package magnolia.examples
+
+import language.experimental.macros
+
+import magnolia._
+
+trait TypeName[T] { def name: String }
+
+object TypeName {
+ type Typeclass[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] =
+ new TypeName[T] { def name: String = ctx.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 bc33969..df4dfd6 100644
--- a/tests/src/main/scala/tests.scala
+++ b/tests/src/main/scala/tests.scala
@@ -1,13 +1,13 @@
package magnolia.tests
+import language.experimental.macros
+
import magnolia._
import estrapade._
import contextual.data.scalac._
import contextual.data.fqt._
import contextual.data.txt._
-import scala.util._
-
sealed trait Tree[+T]
case class Leaf[+L](value: L) extends Tree[L]
case class Branch[+B](left: Tree[B], right: Tree[B]) extends Tree[B]
@@ -55,9 +55,14 @@ sealed trait Box[+A]
case class SimpleBox[+A](value: A) extends Box[A]
case class LabelledBox[+A, L <: String](value: A, var label: L) extends Box[A]
+case class Account(id: String, emails: String*)
+
+case class Portfolio(companies: Company*)
+
+
object Tests extends TestApp {
- def tests() = for (i <- 1 to 1000) {
+ def tests() = for (i <- 1 to 1) {
import examples._
test("construct a Show product instance with alternative apply functions") {
@@ -210,6 +215,7 @@ object Tests extends TestApp {
Show.gen[Length].show(new Length(100))
}.assert(_ == "100")
+
// Corrupt being covariant in L <: Seq[Company] enables the derivation for Corrupt[String, _]
test("show a Politician with covariant lobby") {
Show.gen[Politician[String]].show(Corrupt("wall", Seq(Company("Alice Inc"))))
@@ -224,5 +230,27 @@ object Tests extends TestApp {
| in coproduct type magnolia.tests.Box[Int]
|""")
}
+
+ test("show an Account") {
+ Show.gen[Account].show(Account("john_doe", "john.doe@yahoo.com", "john.doe@gmail.com"))
+ }.assert(_ == "Account(id=john_doe,emails=[john.doe@yahoo.com,john.doe@gmail.com])")
+
+ test("construct a default Account") {
+ Default.gen[Account].default
+ }.assert(_ == Account(""))
+
+ 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")
+
+ test("case class typeName should be complete and unchanged") {
+ implicit val stringTypeName: TypeName[String] = new TypeName[String] { def name = "" }
+ TypeName.gen[Fruit].name
+ }.assert(_ == "magnolia.tests.Fruit")
+ ()
}
}