From a346b10556f8958834c14588e3443d3922d66f89 Mon Sep 17 00:00:00 2001 From: Kevin Wright Date: Thu, 1 Feb 2018 08:30:38 +0000 Subject: Added annotation capture to params Added test for annotation capture Updated to lamdafied syntax for SAM type construction Minor changes to permit compilation under JDK 9 Added Kevin Wright as a contributor --- core/shared/src/main/scala/interface.scala | 12 +++++-- core/shared/src/main/scala/magnolia.scala | 56 ++++++++++++++++++------------ 2 files changed, 44 insertions(+), 24 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 846eee9..427c84c 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,10 @@ 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 annotations: Seq[Any] + + override def toString: String = s"Param($label)" } /** represents a case class or case object and the context required to construct a new typeclass @@ -110,6 +116,7 @@ abstract class CaseClass[Typeclass[_], Type] private[magnolia] ( parametersArray: Array[Param[Typeclass, Type]] ) { + 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 +135,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]] @@ -152,7 +159,6 @@ 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 * @tparam Typeclass type constructor for the typeclass being derived @@ -160,6 +166,8 @@ abstract class CaseClass[Typeclass[_], Type] private[magnolia] ( final class SealedTrait[Typeclass[_], Type](val typeName: TypeName, subtypesArray: Array[Subtype[Typeclass, Type]]) { + 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 diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index a1d4e8a..2e13061 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -73,6 +73,8 @@ 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 @@ -190,6 +192,15 @@ object Magnolia { """ 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 (_.typeSignature.paramLists.headOption) + optList.map(_.map(_.asTerm)) + } + val caseClassParameters = genericType.decls.collect { case m: MethodSymbol if m.isCaseAccessor || (isValueClass && m.isParamAccessor) => m.asMethod @@ -199,14 +210,11 @@ object Magnolia { repeated: Boolean, typeclass: Tree, paramType: Type, - ref: TermName) + ref: TermName + ) val caseParamsReversed = caseClassParameters.foldLeft[List[CaseParam]](Nil) { (acc, param) => - for (ann <- param.annotations) { - c.info(c.enclosingPosition, ann.tree.toString, force = false) - } - val paramName = param.name.decodedName.toString val paramTypeSubstituted = param.typeSignatureIn(genericType).resultType @@ -242,20 +250,16 @@ 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) - // 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 defaults = headParamList.filterNot(_ => isValueClass) 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) + 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) { @@ -263,13 +267,17 @@ 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}, $sciPkg.List(..$annList) )""" } @@ -402,6 +410,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 @@ -412,13 +421,16 @@ object Magnolia { isRepeated: Boolean, typeclassParam: => Tc[P], defaultVal: => Option[P], - deref: T => P): Param[Tc, T] = new Param[Tc, T] { + deref: T => P, + annotationsParam: List[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 annotations: Seq[Any] = annotationsParam } /** constructs a new [[CaseClass]] instance -- cgit v1.2.3