From dbf2a7a68aff4a236acd4efccec5ec9d3eca5a93 Mon Sep 17 00:00:00 2001 From: Kevin Wright Date: Fri, 26 Jan 2018 15:46:04 +0000 Subject: Bumping SBT & plugin versions --- project/build.properties | 2 +- project/plugins.sbt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/project/build.properties b/project/build.properties index 9abea12..8b697bb 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.0.3 +sbt.version=1.1.0 diff --git a/project/plugins.sbt b/project/plugins.sbt index 4a92b89..595dbcc 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ -addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0-M1") -addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.0") +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0") +addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.1") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.21") -- cgit v1.2.3 From 4d1bf3ad68b3def3bbdc7ac8c45cb1a72c2c4e09 Mon Sep 17 00:00:00 2001 From: Kevin Wright Date: Fri, 26 Jan 2018 20:01:29 +0000 Subject: Made it compile under Java 9 --- build.sbt | 14 +++++++++++++- core/shared/src/main/scala/magnolia.scala | 4 ++++ tests/src/main/scala/tests.scala | 7 ++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 178ac83..fda55a8 100644 --- a/build.sbt +++ b/build.sbt @@ -29,11 +29,21 @@ lazy val tests = project addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full), libraryDependencies ++= Seq( "com.fommil" %% "deriving-macro" % "0.9.0", - "com.fommil" %% "scalaz-deriving" % "0.9.0" + "com.fommil" %% "scalaz-deriving" % "0.9.0", + // These 4 because scalaz-deriving collects dependencies like they were pokémon + // Including XML stuff that got modularised in Java 9... + // which is odd for a library that has nothing whatsoever to do with XML! + "javax.xml.bind" % "jaxb-api" % "2.3.0", + "com.sun.xml.bind" % "jaxb-impl" % "2.3.0", + "org.glassfish.jaxb" % "jaxb-runtime" % "2.3.0", + "javax.activation" % "activation" % "1.1.1" ) ) .dependsOn(examplesJVM) + + + lazy val benchmarks = project .in(file("benchmarks")) .settings(buildSettings: _*) @@ -55,6 +65,8 @@ lazy val buildSettings = Seq( "-Ywarn-inaccessible", "-Ywarn-adapted-args" ), +// javaOptions += "--add-modules=javax.xml.bind", +// javacOptions += "--add-modules=javax.xml.bind", scmInfo := Some( ScmInfo(url("https://github.com/propensive/magnolia"), "scm:git:git@github.com:propensive/magnolia.git") diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index 5318aa2..a1d4e8a 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -203,6 +203,10 @@ object Magnolia { 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 diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala index a311291..11a5d2f 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -245,6 +245,7 @@ object Tests extends TestApp { } }.assert(_ == "Cannot patch value `Person(Bob,42)`, expected 2 fields but got 3") + test("throw on an illegal patch attempt with field type mismatch") { // these two implicits can be removed once https://github.com/propensive/magnolia/issues/58 is closed implicit val stringPatcher = Patcher.forSingleValue[String] @@ -253,10 +254,14 @@ object Tests extends TestApp { try { val person = Person("Bob", 42) implicitly[Patcher[Entity]].patch(person, Seq(null, 'killer)) + "it worked" } catch { case NonFatal(e) => e.getMessage } - }.assert(_ == "scala.Symbol cannot be cast to java.lang.Integer") + }.assert{x => + //tiny hack because Java 9 inserts the "java.base/" module name in the error message + x.startsWith("scala.Symbol cannot be cast to") && x.endsWith("java.lang.Integer") + } class ParentClass { case class InnerClass(name: String) -- cgit v1.2.3 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 --- build.sbt | 23 ++++++------ core/shared/src/main/scala/interface.scala | 12 +++++-- core/shared/src/main/scala/magnolia.scala | 56 +++++++++++++++++------------ examples/shared/src/main/scala/decode.scala | 26 ++++++-------- examples/shared/src/main/scala/show.scala | 35 +++++++++--------- tests/src/main/scala/tests.scala | 23 ++++++++---- 6 files changed, 99 insertions(+), 76 deletions(-) diff --git a/build.sbt b/build.sbt index fda55a8..035b32b 100644 --- a/build.sbt +++ b/build.sbt @@ -27,16 +27,16 @@ lazy val tests = project .settings(moduleName := "magnolia-tests") .settings( addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full), + initialCommands in console := """import magnolia.tests._; import magnolia.examples._;""", libraryDependencies ++= Seq( "com.fommil" %% "deriving-macro" % "0.9.0", "com.fommil" %% "scalaz-deriving" % "0.9.0", - // These 4 because scalaz-deriving collects dependencies like they were pokémon - // Including XML stuff that got modularised in Java 9... - // which is odd for a library that has nothing whatsoever to do with XML! - "javax.xml.bind" % "jaxb-api" % "2.3.0", - "com.sun.xml.bind" % "jaxb-impl" % "2.3.0", - "org.glassfish.jaxb" % "jaxb-runtime" % "2.3.0", - "javax.activation" % "activation" % "1.1.1" + // These 4 to allow compilation under Java 9... + // Specifically to import XML stuff that got modularised + "javax.xml.bind" % "jaxb-api" % "2.3.0" % "compile", + "com.sun.xml.bind" % "jaxb-impl" % "2.3.0" % "compile", + "org.glassfish.jaxb" % "jaxb-runtime" % "2.3.0" % "compile", + "javax.activation" % "activation" % "1.1.1" % "compile" ) ) .dependsOn(examplesJVM) @@ -65,8 +65,6 @@ lazy val buildSettings = Seq( "-Ywarn-inaccessible", "-Ywarn-adapted-args" ), -// javaOptions += "--add-modules=javax.xml.bind", -// javacOptions += "--add-modules=javax.xml.bind", scmInfo := Some( ScmInfo(url("https://github.com/propensive/magnolia"), "scm:git:git@github.com:propensive/magnolia.git") @@ -96,12 +94,15 @@ lazy val publishSettings = Seq( Jon Pretty https://github.com/propensive/magnolia/ + + thecoda + Kevin Wright + https://github.com/kevinwright/ + ) ) -import java.io.File - lazy val unmanagedSettings = unmanagedBase := (scalaVersion.value .split("\\.") .map(_.toInt) 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 diff --git a/examples/shared/src/main/scala/decode.scala b/examples/shared/src/main/scala/decode.scala index 595ee6f..9e91d17 100644 --- a/examples/shared/src/main/scala/decode.scala +++ b/examples/shared/src/main/scala/decode.scala @@ -10,12 +10,10 @@ trait Decoder[T] { def decode(str: String): T } object Decoder { /** decodes strings */ - implicit val string: Decoder[String] = new Decoder[String] { - def decode(str: String): String = str - } + implicit val string: Decoder[String] = (str: String) => str /** decodes ints */ - implicit val int: Decoder[Int] = new Decoder[Int] { def decode(str: String): Int = str.toInt } + implicit val int: Decoder[Int] = (str: String) => str.toInt /** binds the Magnolia macro to this derivation object */ implicit def gen[T]: Decoder[T] = macro Magnolia.gen[T] @@ -24,22 +22,18 @@ object Decoder { type Typeclass[T] = Decoder[T] /** defines how new [[Decoder]]s for case classes should be constructed */ - def combine[T](ctx: CaseClass[Decoder, T]): Decoder[T] = new Decoder[T] { - def decode(value: String) = { - val (_, values) = parse(value) - ctx.construct { param => - param.typeclass.decode(values(param.label)) - } + def combine[T](ctx: CaseClass[Decoder, T]): Decoder[T] = (value: String) => { + val (_, values) = parse(value) + ctx.construct { param => + param.typeclass.decode(values(param.label)) } } /** defines how to choose which subtype of the sealed trait to use for decoding */ - def dispatch[T](ctx: SealedTrait[Decoder, T]): Decoder[T] = new Decoder[T] { - def decode(param: String) = { - val (name, _) = parse(param) - val subtype = ctx.subtypes.find(_.typeName.full == name).get - subtype.typeclass.decode(param) - } + def dispatch[T](ctx: SealedTrait[Decoder, T]): Decoder[T] = (param: String) => { + val (name, _) = parse(param) + val subtype = ctx.subtypes.find(_.typeName.full == name).get + subtype.typeclass.decode(param) } /** very simple extractor for grabbing an entire parameter value, assuming matching parentheses */ diff --git a/examples/shared/src/main/scala/show.scala b/examples/shared/src/main/scala/show.scala index 6f5838b..74914cb 100644 --- a/examples/shared/src/main/scala/show.scala +++ b/examples/shared/src/main/scala/show.scala @@ -20,26 +20,27 @@ trait GenericShow[Out] { /** creates a new [[Show]] instance by labelling and joining (with `mkString`) the result of * showing each parameter, and prefixing it with the class name */ - def combine[T](ctx: CaseClass[Typeclass, T]): Show[Out, T] = new Show[Out, T] { - def show(value: T) = - if (ctx.isValueClass) { - val param = ctx.parameters.head - param.typeclass.show(param.dereference(value)) - } else { - val paramStrings = ctx.parameters.map { param => - s"${param.label}=${param.typeclass.show(param.dereference(value))}" + def combine[T](ctx: CaseClass[Typeclass, T]): Show[Out, T] = (value: T) => { + if (ctx.isValueClass) { + val param = ctx.parameters.head + param.typeclass.show(param.dereference(value)) + } else { + val paramStrings = ctx.parameters.map { param => + val attribStr = if(param.annotations.isEmpty) "" else { + param.annotations.mkString("{", ", ", "}") } - - join(ctx.typeName.short, paramStrings) + s"${param.label}$attribStr=${param.typeclass.show(param.dereference(value))}" } + + join(ctx.typeName.short, paramStrings) + } } /** choose which typeclass to use based on the subtype of the sealed trait */ - def dispatch[T](ctx: SealedTrait[Typeclass, T]): Show[Out, T] = new Show[Out, T] { - def show(value: T): Out = ctx.dispatch(value) { sub => + def dispatch[T](ctx: SealedTrait[Typeclass, T]): Show[Out, T] = (value: T) => + ctx.dispatch(value) { sub => sub.typeclass.show(sub.cast(value)) } - } /** bind the Magnolia macro to this derivation object */ implicit def gen[T]: Show[Out, T] = macro Magnolia.gen[T] @@ -49,17 +50,13 @@ trait GenericShow[Out] { object Show extends GenericShow[String] { /** show typeclass for strings */ - implicit val string: Show[String, String] = new Show[String, String] { - def show(s: String): String = s - } + implicit val string: Show[String, String] = (s: String) => s def join(typeName: String, params: Seq[String]): String = params.mkString(s"$typeName(", ",", ")") /** show typeclass for integers */ - implicit val int: Show[String, Int] = new Show[String, Int] { - def show(s: Int): String = s.toString - } + implicit val int: Show[String, Int] = (s: Int) => s.toString /** show typeclass for sequences */ implicit def seq[A](implicit A: Show[String, A]): Show[String, Seq[A]] = diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala index 11a5d2f..0f962d2 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -1,12 +1,13 @@ package magnolia.tests import language.experimental.macros -import estrapade.{test, TestApp} +import estrapade.{TestApp, test} import contextual.data.scalac._ import contextual.data.fqt._ import contextual.data.txt._ import magnolia.examples._ +import scala.annotation.StaticAnnotation import scala.util.control.NonFatal sealed trait Tree[+T] @@ -29,8 +30,7 @@ class Length(val value: Int) extends AnyVal case class FruitBasket(fruits: Fruit*) case class Lunchbox(fruit: Fruit, drink: String) object Fruit { - implicit val showFruit: Show[String, Fruit] = - new Show[String, Fruit] { def show(f: Fruit): String = f.name } + implicit val showFruit: Show[String, Fruit] = (f: Fruit) => f.name } case class Fruit(name: String) @@ -41,6 +41,14 @@ case object Red extends Color case object Green extends Color case object Blue extends Color + +case class MyAnnotation(order: Int) extends StaticAnnotation + +case class Attributed( + @MyAnnotation(1) p1: String, + @MyAnnotation(2) p2: Int +) + case class `%%`(`/`: Int, `#`: String) case class Param(a: String, b: String) @@ -354,11 +362,14 @@ object Tests extends TestApp { implicit def showDefaultOption[A]( implicit showA: Show[String, A], defaultA: Default[A] - ): Show[String, Option[A]] = new Show[String, Option[A]] { - def show(optA: Option[A]) = showA.show(optA.getOrElse(defaultA.default)) - } + ): Show[String, Option[A]] = (optA: Option[A]) => showA.show(optA.getOrElse(defaultA.default)) Show.gen[Path[String]].show(OffRoad(Some(Crossroad(Destination("A"), Destination("B"))))) }.assert(_ == "OffRoad(path=Crossroad(left=Destination(value=A),right=Destination(value=B)))") + + test("capture attributes against params") { + Show.gen[Attributed].show(Attributed("xyz", 100)) + }.assert(_ == "Attributed(p1{MyAnnotation(1)}=xyz,p2{MyAnnotation(2)}=100)") + } } -- cgit v1.2.3 From bd1ca071286987bc148fda830aa3141f56891efb Mon Sep 17 00:00:00 2001 From: Kevin Wright Date: Thu, 1 Feb 2018 09:49:03 +0000 Subject: Removing a pair of jaxb dependencies that weren't actually needed --- build.sbt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 035b32b..487c103 100644 --- a/build.sbt +++ b/build.sbt @@ -31,12 +31,10 @@ lazy val tests = project libraryDependencies ++= Seq( "com.fommil" %% "deriving-macro" % "0.9.0", "com.fommil" %% "scalaz-deriving" % "0.9.0", - // These 4 to allow compilation under Java 9... + // These two to allow compilation under Java 9... // Specifically to import XML stuff that got modularised "javax.xml.bind" % "jaxb-api" % "2.3.0" % "compile", - "com.sun.xml.bind" % "jaxb-impl" % "2.3.0" % "compile", - "org.glassfish.jaxb" % "jaxb-runtime" % "2.3.0" % "compile", - "javax.activation" % "activation" % "1.1.1" % "compile" + "com.sun.xml.bind" % "jaxb-impl" % "2.3.0" % "compile" ) ) .dependsOn(examplesJVM) -- cgit v1.2.3 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 From c1b9c1f53a6bb3f62abc7141e6b6153d8208e1f0 Mon Sep 17 00:00:00 2001 From: Kevin Wright Date: Thu, 8 Feb 2018 21:20:53 +0000 Subject: post-review changes --- CONTRIBUTORS | 1 + build.sbt | 6 ------ core/shared/src/main/scala/interface.scala | 2 ++ core/shared/src/main/scala/magnolia.scala | 6 ++++-- examples/shared/src/main/scala/decode.scala | 8 ++++---- examples/shared/src/main/scala/show.scala | 2 +- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index d0d60e5..4e84fbb 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -3,3 +3,4 @@ Loïc Descotte [@loic_d](https://twitter.com/loic_d) Georgi Krastev [@joro_kr](https://twitter.com/joro_kr/) Shadaj Laddad [@shadaj](https://twitter.com/shadajl) Mathias Doenitz [@sirthias](https://twitter.com/sirthias) +Kevin Wright [@thecoda](https://twitter.com/thecoda) diff --git a/build.sbt b/build.sbt index 487c103..50ec53d 100644 --- a/build.sbt +++ b/build.sbt @@ -30,7 +30,6 @@ lazy val tests = project initialCommands in console := """import magnolia.tests._; import magnolia.examples._;""", libraryDependencies ++= Seq( "com.fommil" %% "deriving-macro" % "0.9.0", - "com.fommil" %% "scalaz-deriving" % "0.9.0", // These two to allow compilation under Java 9... // Specifically to import XML stuff that got modularised "javax.xml.bind" % "jaxb-api" % "2.3.0" % "compile", @@ -92,11 +91,6 @@ lazy val publishSettings = Seq( Jon Pretty https://github.com/propensive/magnolia/ - - thecoda - Kevin Wright - https://github.com/kevinwright/ - ) ) diff --git a/core/shared/src/main/scala/interface.scala b/core/shared/src/main/scala/interface.scala index 97ea5b7..1dac65e 100644 --- a/core/shared/src/main/scala/interface.scala +++ b/core/shared/src/main/scala/interface.scala @@ -113,6 +113,7 @@ 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] ( @@ -174,6 +175,7 @@ abstract class CaseClass[Typeclass[_], Type] private[magnolia] ( * 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]( diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index 00cf5ec..3cfda3d 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -202,7 +202,8 @@ object Magnolia { val headParamList = { val primaryConstructor = classType map (_.primaryConstructor) - val optList: Option[List[c.universe.Symbol]] = primaryConstructor flatMap (_.typeSignature.paramLists.headOption) + val optList: Option[List[c.universe.Symbol]] = + primaryConstructor flatMap (_.asMethod.typeSignature.paramLists.headOption) optList.map(_.map(_.asTerm)) } @@ -256,10 +257,11 @@ object Magnolia { val preAssignments = caseParams.map(_.typeclass) - val defaults = headParamList.filterNot(_ => isValueClass) map { plist => + 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) + val companionSym = companionRef.symbol.asModule.info val primaryFactoryMethod = companionSym.decl(TermName("apply")).alternatives.lastOption primaryFactoryMethod.foreach(_.asMethod.typeSignature) diff --git a/examples/shared/src/main/scala/decode.scala b/examples/shared/src/main/scala/decode.scala index 9e91d17..539e478 100644 --- a/examples/shared/src/main/scala/decode.scala +++ b/examples/shared/src/main/scala/decode.scala @@ -10,10 +10,10 @@ trait Decoder[T] { def decode(str: String): T } object Decoder { /** decodes strings */ - implicit val string: Decoder[String] = (str: String) => str + implicit val string: Decoder[String] = (s: String) => s /** decodes ints */ - implicit val int: Decoder[Int] = (str: String) => str.toInt + implicit val int: Decoder[Int] = _.toInt /** binds the Magnolia macro to this derivation object */ implicit def gen[T]: Decoder[T] = macro Magnolia.gen[T] @@ -22,7 +22,7 @@ object Decoder { type Typeclass[T] = Decoder[T] /** defines how new [[Decoder]]s for case classes should be constructed */ - def combine[T](ctx: CaseClass[Decoder, T]): Decoder[T] = (value: String) => { + def combine[T](ctx: CaseClass[Decoder, T]): Decoder[T] = value => { val (_, values) = parse(value) ctx.construct { param => param.typeclass.decode(values(param.label)) @@ -30,7 +30,7 @@ object Decoder { } /** defines how to choose which subtype of the sealed trait to use for decoding */ - def dispatch[T](ctx: SealedTrait[Decoder, T]): Decoder[T] = (param: String) => { + def dispatch[T](ctx: SealedTrait[Decoder, T]): Decoder[T] = param => { val (name, _) = parse(param) val subtype = ctx.subtypes.find(_.typeName.full == name).get subtype.typeclass.decode(param) diff --git a/examples/shared/src/main/scala/show.scala b/examples/shared/src/main/scala/show.scala index 9afc4c9..ecf1dec 100644 --- a/examples/shared/src/main/scala/show.scala +++ b/examples/shared/src/main/scala/show.scala @@ -20,7 +20,7 @@ trait GenericShow[Out] { /** creates a new [[Show]] instance by labelling and joining (with `mkString`) the result of * showing each parameter, and prefixing it with the class name */ - def combine[T](ctx: CaseClass[Typeclass, T]): Show[Out, T] = (value: T) => { + def combine[T](ctx: CaseClass[Typeclass, T]): Show[Out, T] = { value => if (ctx.isValueClass) { val param = ctx.parameters.head param.typeclass.show(param.dereference(value)) -- cgit v1.2.3