diff options
author | Jon Pretty <jon.pretty@propensive.com> | 2018-02-08 21:37:16 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-02-08 21:37:16 +0000 |
commit | b562503348bc36bba09bc5df9a8d846d9bf80912 (patch) | |
tree | e867d326898b1e7d0019753728b921c3a160167c /core | |
parent | 4fe64bd5ba7bdc1ea0e29923b27463c62a3c4052 (diff) | |
parent | c1b9c1f53a6bb3f62abc7141e6b6153d8208e1f0 (diff) | |
download | magnolia-b562503348bc36bba09bc5df9a8d846d9bf80912.tar.gz magnolia-b562503348bc36bba09bc5df9a8d846d9bf80912.tar.bz2 magnolia-b562503348bc36bba09bc5df9a8d846d9bf80912.zip |
Merge pull request #74 from kevinwright/feature/attribs-on-params
Feature/attribs on params
Diffstat (limited to 'core')
-rw-r--r-- | core/shared/src/main/scala/interface.scala | 42 | ||||
-rw-r--r-- | core/shared/src/main/scala/magnolia.scala | 75 |
2 files changed, 90 insertions, 27 deletions
diff --git a/core/shared/src/main/scala/interface.scala b/core/shared/src/main/scala/interface.scala index 846eee9..1dac65e 100644 --- a/core/shared/src/main/scala/interface.scala +++ b/core/shared/src/main/scala/interface.scala @@ -25,6 +25,8 @@ trait Subtype[Typeclass[_], Type] { /** partial function defined the subset of values of `Type` which have the type of this subtype */ def cast: PartialFunction[Type, SType] + + override def toString: String = s"Subtype(${typeName.full})" } /** represents a parameter of a case class @@ -89,6 +91,16 @@ trait Param[Typeclass[_], Type] { * @param param the instance of the case class to be dereferenced * @return the parameter value */ def dereference(param: Type): PType + + 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)" } /** represents a case class or case object and the context required to construct a new typeclass @@ -101,15 +113,18 @@ trait Param[Typeclass[_], Type] { * @param typeName the name of the case class * @param isObject true only if this represents a case object rather than a case class * @param parametersArray an array of [[Param]] values for this case class + * @param annotationsArray an array of instantiated annotations applied to this case class * @tparam Typeclass type constructor for the typeclass being derived * @tparam Type generic type of this parameter */ 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(",")})" /** constructs a new instance of the case class type * * This method will be implemented by the Magnolia macro to make it possible to construct @@ -128,7 +143,7 @@ abstract class CaseClass[Typeclass[_], Type] private[magnolia] ( /** constructs a new instance of the case class type * - * Like [[construct]] this method is implemented by Magnolia and let's you construct case class + * Like [[construct]] this method is implemented by Magnolia and lets you construct case class * instances generically in user code, without knowing their type concretely. * * `rawConstruct`, however, is more low-level in that it expects you to provide a [[Seq]] @@ -145,6 +160,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 @@ -152,13 +173,18 @@ abstract class CaseClass[Typeclass[_], Type] private[magnolia] ( * * Instances of `SealedTrait` provide access to all of the component subtypes of the sealed trait * which form a coproduct, and to the fully-qualified name of the sealed trait. - * * @param typeName the name of the sealed trait * @param subtypesArray an array of [[Subtype]] instances for each subtype in the sealed trait + * @param annotationsArray an array of instantiated annotations applied to this case class * @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(",")}])" /** a sequence of all the subtypes of this sealed trait */ def subtypes: Seq[Subtype[Typeclass, Type]] = subtypesArray @@ -184,6 +210,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 5318aa2..3cfda3d 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -161,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], @@ -185,11 +187,26 @@ 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) { + + val companionRef = GlobalUtil.patchedCompanionRef(c)(genericType.dealias) + + val headParamList = { + val primaryConstructor = classType map (_.primaryConstructor) + val optList: Option[List[c.universe.Symbol]] = + primaryConstructor flatMap (_.asMethod.typeSignature.paramLists.headOption) + optList.map(_.map(_.asTerm)) + } + val caseClassParameters = genericType.decls.collect { case m: MethodSymbol if m.isCaseAccessor || (isValueClass && m.isParamAccessor) => m.asMethod @@ -199,7 +216,8 @@ object Magnolia { repeated: Boolean, typeclass: Tree, paramType: Type, - ref: TermName) + ref: TermName + ) val caseParamsReversed = caseClassParameters.foldLeft[List[CaseParam]](Nil) { (acc, param) => @@ -238,20 +256,17 @@ object Magnolia { val preAssignments = caseParams.map(_.typeclass) - val defaults = if (!isValueClass) { - val companionRef = GlobalUtil.patchedCompanionRef(c)(genericType.dealias) - val companionSym = companionRef.symbol.asModule.info - // If a companion object is defined with alternative apply methods - // it is needed get all the alternatives - val constructorMethods = - companionSym.decl(TermName("apply")).alternatives.map(_.asMethod) + val defaults = headParamList map { plist => + // note: This causes the namer/typer to generate the synthetic default methods by forcing + // the typeSignature of the "default" factory method to be visited. + // It feels like it shouldn't be needed, but we'll get errors otherwise (as discovered after 6 hours debugging) - // The last apply method in the alternatives is the one that belongs - // to the case class, not the user defined companion object - val indexedConstructorParams = - constructorMethods.last.paramLists.head.map(_.asTerm).zipWithIndex + val companionSym = companionRef.symbol.asModule.info + val primaryFactoryMethod = companionSym.decl(TermName("apply")).alternatives.lastOption + primaryFactoryMethod.foreach(_.asMethod.typeSignature) + val indexedConstructorParams = plist.zipWithIndex indexedConstructorParams.map { case (p, idx) => if (p.isParamWithDefault) { @@ -259,13 +274,22 @@ object Magnolia { q"$scalaPkg.Some($companionRef.$method)" } else q"$scalaPkg.None" } - } else List(q"$scalaPkg.None") + } getOrElse List(q"$scalaPkg.None") + + val annotations: List[List[Tree]] = headParamList.toList.flatten map { param => + param.annotations map { _.tree } + } - val assignments = caseParams.zip(defaults).zipWithIndex.map { - case ((CaseParam(param, repeated, typeclass, paramType, ref), defaultVal), idx) => + val assignments = caseParams.zip(defaults).zip(annotations).zipWithIndex.map { + 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} + ${param.name.decodedName.toString}, + $repeated, + $ref, + $defaultVal, + _.${param.name}, + $scalaPkg.Array(..$annList) )""" } @@ -282,6 +306,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 @@ -343,8 +368,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 @@ -398,6 +424,7 @@ object Magnolia { def cast: PartialFunction[T, SType] = this def isDefinedAt(t: T) = isType(t) def apply(t: T): SType = asType(t) + override def toString: String = s"Subtype(${typeName.full})" } /** constructs a new [[Param]] instance @@ -408,13 +435,16 @@ object Magnolia { isRepeated: Boolean, typeclassParam: => Tc[P], defaultVal: => Option[P], - deref: T => P): Param[Tc, T] = new Param[Tc, T] { + deref: T => P, + annotationsArrayParam: Array[Any] + ): Param[Tc, T] = 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) + def annotationsArray: Array[Any] = annotationsArrayParam } /** constructs a new [[CaseClass]] instance @@ -425,8 +455,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) } } |