From e9b52248855f9252520a232b28a841ea382922a1 Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Wed, 29 Nov 2017 12:36:04 +0100 Subject: Add support for repeated (vararg) parameters Added a `Param.repeated` flag to `interface`. --- core/shared/src/main/scala/interface.scala | 11 +++++++++++ core/shared/src/main/scala/magnolia.scala | 29 ++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 7 deletions(-) (limited to 'core/shared/src/main') 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, + *
+    * case class Account(id: String, emails: String*)
+    * 
+ * 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 2113dff..c4c13e6 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -69,6 +69,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 = { @@ -239,6 +242,7 @@ object Magnolia { 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) @@ -246,13 +250,21 @@ object Magnolia { val caseParamsReversed = caseClassParameters.foldLeft[List[CaseParam]](Nil) { (acc, param) => val paramName = param.name.decodedName.toString - val paramType = param.returnType.substituteTypes(genericType.etaExpand.typeParams, - genericType.typeArgs) + val paramTypeSubstituted = param.returnType.substituteTypes( + genericType.etaExpand.typeParams, + genericType.typeArgs) + + 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 { @@ -265,7 +277,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 } } @@ -299,10 +311,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} )""" } @@ -323,7 +335,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 }}) )) }""" @@ -456,11 +469,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) -- cgit v1.2.3 From 13fcfba5aa5d6a90b90c22088cb9b8da5e2823fe Mon Sep 17 00:00:00 2001 From: Jon Pretty Date: Thu, 30 Nov 2017 18:32:07 +0000 Subject: typeName is now the full name of the type, in original case --- core/shared/src/main/scala/magnolia.scala | 10 +++++----- examples/shared/src/main/scala/typename.scala | 19 +++++++++++++++++++ tests/src/main/scala/tests.scala | 14 +++++++++++++- 3 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 examples/shared/src/main/scala/typename.scala (limited to 'core/shared/src/main') diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index c4c13e6..6528db0 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -223,10 +223,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]( @@ -239,7 +240,6 @@ 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, @@ -372,13 +372,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, @@ -389,7 +389,7 @@ object Magnolia { ..$assignments ${c.prefix}.dispatch(new $magnoliaPkg.SealedTrait( - $genericTypeName, + $className, $subtypesVal: $scalaPkg.Array[$magnoliaPkg.Subtype[$typeConstructor, $genericType]]) ): $resultType }""" 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 4c2a48e..96a2c2b 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -1,5 +1,7 @@ package magnolia.tests +import language.experimental.macros + import magnolia._ import estrapade._ import contextual.data.scalac._ @@ -51,7 +53,7 @@ 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") { @@ -215,5 +217,15 @@ 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") + + 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") + } } -- cgit v1.2.3