From 6660480a75b240b7ac613ed308e5b7f7ddfc6459 Mon Sep 17 00:00:00 2001 From: Kevin Wright Date: Thu, 1 Feb 2018 14:42:05 +0000 Subject: Added top-level annotation capture --- core/shared/src/main/scala/interface.scala | 30 +++++++++++++++++++++++---- core/shared/src/main/scala/magnolia.scala | 33 +++++++++++++++++++++--------- examples/shared/src/main/scala/show.scala | 5 ++++- tests/src/main/scala/tests.scala | 4 ++-- 4 files changed, 55 insertions(+), 17 deletions(-) diff --git a/core/shared/src/main/scala/interface.scala b/core/shared/src/main/scala/interface.scala index 427c84c..97ea5b7 100644 --- a/core/shared/src/main/scala/interface.scala +++ b/core/shared/src/main/scala/interface.scala @@ -92,7 +92,13 @@ trait Param[Typeclass[_], Type] { * @return the parameter value */ def dereference(param: Type): PType - def annotations: Seq[Any] + def annotationsArray: Array[Any] + + /** a sequence of objects representing all of the annotations on the case class + * + * For efficiency, this sequence is implemented by an `Array`, but upcast to a + * [[scala.collection.Seq]] to hide the mutable collection API. */ + final def annotations: Seq[Any] = annotationsArray override def toString: String = s"Param($label)" } @@ -113,7 +119,8 @@ abstract class CaseClass[Typeclass[_], Type] private[magnolia] ( val typeName: TypeName, val isObject: Boolean, val isValueClass: Boolean, - parametersArray: Array[Param[Typeclass, Type]] + parametersArray: Array[Param[Typeclass, Type]], + annotationsArray: Array[Any] ) { override def toString: String = s"CaseClass(${typeName.full}, ${parameters.mkString(",")})" @@ -152,6 +159,12 @@ abstract class CaseClass[Typeclass[_], Type] private[magnolia] ( * For efficiency, this sequence is implemented by an `Array`, but upcast to a * [[scala.collection.Seq]] to hide the mutable collection API. */ final def parameters: Seq[Param[Typeclass, Type]] = parametersArray + + /** a sequence of objects representing all of the annotations on the case class + * + * For efficiency, this sequence is implemented by an `Array`, but upcast to a + * [[scala.collection.Seq]] to hide the mutable collection API. */ + final def annotations: Seq[Any] = annotationsArray } /** represents a sealed trait and the context required to construct a new typeclass instance @@ -163,8 +176,11 @@ abstract class CaseClass[Typeclass[_], Type] private[magnolia] ( * @param subtypesArray an array of [[Subtype]] instances for each subtype in the sealed trait * @tparam Typeclass type constructor for the typeclass being derived * @tparam Type generic type of this parameter */ -final class SealedTrait[Typeclass[_], Type](val typeName: TypeName, - subtypesArray: Array[Subtype[Typeclass, Type]]) { +final class SealedTrait[Typeclass[_], Type]( + val typeName: TypeName, + subtypesArray: Array[Subtype[Typeclass, Type]], + annotationsArray: Array[Any] +) { override def toString: String = s"SealedTrait($typeName, Array[${subtypes.mkString(",")}])" @@ -192,6 +208,12 @@ final class SealedTrait[Typeclass[_], Type](val typeName: TypeName, ) rec(0) } + + /** a sequence of objects representing all of the annotations on the topmost trait + * + * For efficiency, this sequence is implemented by an `Array`, but upcast to a + * [[scala.collection.Seq]] to hide the mutable collection API. */ + final def annotations: Seq[Any] = annotationsArray } /** diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index 2e13061..00cf5ec 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -73,8 +73,6 @@ object Magnolia { val magnoliaPkg = c.mirror.staticPackage("magnolia") val scalaPkg = c.mirror.staticPackage("scala") - val sciPkg = c.mirror.staticPackage("scala.collection.immutable") - val repeatedParamClass = definitions.RepeatedParamClass val scalaSeqType = typeOf[Seq[_]].typeConstructor @@ -163,6 +161,8 @@ object Magnolia { val isCaseObject = classType.exists(_.isModuleClass) val isSealedTrait = classType.exists(_.isSealed) + val classAnnotationTrees = typeSymbol.annotations.map(_.tree) + val primitives = Set(typeOf[Double], typeOf[Float], typeOf[Short], @@ -187,8 +187,13 @@ object Magnolia { val impl = q""" $typeNameDef ${c.prefix}.combine($magnoliaPkg.Magnolia.caseClass[$typeConstructor, $genericType]( - $typeName, true, false, new $scalaPkg.Array(0), _ => ${genericType.typeSymbol.asClass.module}) - ) + $typeName, + true, + false, + new $scalaPkg.Array(0), + $scalaPkg.Array(..$classAnnotationTrees), + _ => ${genericType.typeSymbol.asClass.module} + )) """ Some(impl) } else if (isCaseClass || isValueClass) { @@ -277,7 +282,12 @@ object Magnolia { case (((CaseParam(param, repeated, typeclass, paramType, ref), defaultVal), annList), idx) => q"""$paramsVal($idx) = $magnoliaPkg.Magnolia.param[$typeConstructor, $genericType, $paramType]( - ${param.name.decodedName.toString}, $repeated, $ref, $defaultVal, _.${param.name}, $sciPkg.List(..$annList) + ${param.name.decodedName.toString}, + $repeated, + $ref, + $defaultVal, + _.${param.name}, + $scalaPkg.Array(..$annList) )""" } @@ -294,6 +304,7 @@ object Magnolia { false, $isValueClass, $paramsVal, + $scalaPkg.Array(..$classAnnotationTrees), ($fieldValues: $scalaPkg.Seq[Any]) => { if ($fieldValues.lengthCompare($paramsVal.length) != 0) { val msg = "`" + $typeName.full + "` has " + $paramsVal.length + " fields, not " + $fieldValues.size @@ -355,8 +366,9 @@ object Magnolia { ${c.prefix}.dispatch(new $magnoliaPkg.SealedTrait( $typeName, - $subtypesVal: $scalaPkg.Array[$magnoliaPkg.Subtype[$typeConstructor, $genericType]]) - ): $resultType + $subtypesVal: $scalaPkg.Array[$magnoliaPkg.Subtype[$typeConstructor, $genericType]], + $scalaPkg.Array(..$classAnnotationTrees) + )): $resultType }""") } else None @@ -422,7 +434,7 @@ object Magnolia { typeclassParam: => Tc[P], defaultVal: => Option[P], deref: T => P, - annotationsParam: List[Any] + annotationsArrayParam: Array[Any] ): Param[Tc, T] = new Param[Tc, T] { type PType = P def label: String = name @@ -430,7 +442,7 @@ object Magnolia { def default: Option[PType] = defaultVal def typeclass: Tc[PType] = typeclassParam def dereference(t: T): PType = deref(t) - def annotations: Seq[Any] = annotationsParam + def annotationsArray: Array[Any] = annotationsArrayParam } /** constructs a new [[CaseClass]] instance @@ -441,8 +453,9 @@ object Magnolia { obj: Boolean, valClass: Boolean, params: Array[Param[Tc, T]], + annotations: Array[Any], constructor: Seq[Any] => T): CaseClass[Tc, T] = - new CaseClass[Tc, T](name, obj, valClass, params) { + new CaseClass[Tc, T](name, obj, valClass, params, annotations) { def rawConstruct(fieldValues: Seq[Any]): T = constructor(fieldValues) } } diff --git a/examples/shared/src/main/scala/show.scala b/examples/shared/src/main/scala/show.scala index 74914cb..9afc4c9 100644 --- a/examples/shared/src/main/scala/show.scala +++ b/examples/shared/src/main/scala/show.scala @@ -32,7 +32,10 @@ trait GenericShow[Out] { s"${param.label}$attribStr=${param.typeclass.show(param.dereference(value))}" } - join(ctx.typeName.short, paramStrings) + val anns = ctx.annotations.filterNot(_.isInstanceOf[scala.SerialVersionUID]) + val annotationStr = if (anns.isEmpty) "" else anns.mkString("{", ",", "}") + + join(ctx.typeName.short + annotationStr, paramStrings) } } diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala index 0f962d2..9153325 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -44,7 +44,7 @@ case object Blue extends Color case class MyAnnotation(order: Int) extends StaticAnnotation -case class Attributed( +@MyAnnotation(0) case class Attributed( @MyAnnotation(1) p1: String, @MyAnnotation(2) p2: Int ) @@ -369,7 +369,7 @@ object Tests extends TestApp { test("capture attributes against params") { Show.gen[Attributed].show(Attributed("xyz", 100)) - }.assert(_ == "Attributed(p1{MyAnnotation(1)}=xyz,p2{MyAnnotation(2)}=100)") + }.assert(_ == "Attributed{MyAnnotation(0)}(p1{MyAnnotation(1)}=xyz,p2{MyAnnotation(2)}=100)") } } -- cgit v1.2.3