diff --git a/build.sbt b/build.sbt index fc72216..c68273a 100644 --- a/build.sbt +++ b/build.sbt @@ -15,7 +15,7 @@ lazy val `teleproto` = project .settings(Project.inConfig(Test)(sbtprotoc.ProtocPlugin.protobufConfigSettings): _*) .settings( name := "teleproto", - version := "1.12.1", + version := "1.13.0", versionScheme := Some("early-semver"), libraryDependencies ++= Seq( library.scalaPB % "protobuf;compile", diff --git a/src/main/scala/io/moia/protos/teleproto/FormatImpl.scala b/src/main/scala/io/moia/protos/teleproto/FormatImpl.scala index 286eafa..1510ba4 100644 --- a/src/main/scala/io/moia/protos/teleproto/FormatImpl.scala +++ b/src/main/scala/io/moia/protos/teleproto/FormatImpl.scala @@ -37,8 +37,8 @@ trait FormatImpl { def error(message: String, pos: Position = c.enclosingPosition): Unit = c.error(pos, message) def abort(message: String, pos: Position = c.enclosingPosition): Nothing = c.abort(pos, message) - private[teleproto] val mapping = - q"io.moia.protos.teleproto" + protected def objectRef[T: TypeTag]: Symbol = + typeOf[T].termSymbol /** A `oneof` proto definition is mapped to a `sealed trait` in Scala. * Each variant of the `oneof` definition is mapped to a `case class` with exactly one field `value` diff --git a/src/main/scala/io/moia/protos/teleproto/Reader.scala b/src/main/scala/io/moia/protos/teleproto/Reader.scala index 25508f6..b71b0af 100644 --- a/src/main/scala/io/moia/protos/teleproto/Reader.scala +++ b/src/main/scala/io/moia/protos/teleproto/Reader.scala @@ -43,39 +43,40 @@ trait Reader[-P, +M] { * Transforms successfully read results. */ def map[N](f: M => N): Reader[P, N] = - read(_).map(f) + Reader.instance(read(_).map(f)) /** * Transforms the protobuf before reading. */ final def contramap[Q](f: Q => P): Reader[Q, M] = - protobuf => read(f(protobuf)) + Reader.instance(protobuf => read(f(protobuf))) /** * Transforms successfully read results with the option to fail. */ final def pbmap[N](f: M => PbResult[N]): Reader[P, N] = - read(_).flatMap(f) + Reader.instance(read(_).flatMap(f)) @deprecated("Use a function that returns a Reader with flatMap or one that returns a PbResult with emap", "1.8.0") protected def flatMap[N](f: M => PbSuccess[N]): Reader[P, N] = - read(_).flatMap(f) + Reader.instance(read(_).flatMap(f)) /** * Transforms successfully read results by stacking another reader on top of the original protobuf. */ final def flatMap[Q <: P, N](f: M => Reader[Q, N])(implicit dummy: DummyImplicit): Reader[Q, N] = - protobuf => read(protobuf).flatMap(f(_).read(protobuf)) + Reader.instance(protobuf => read(protobuf).flatMap(f(_).read(protobuf))) /** * Combines two readers with a specified function. */ final def zipWith[Q <: P, N, O](that: Reader[Q, N])(f: (M, N) => O): Reader[Q, O] = - protobuf => + Reader.instance { protobuf => for { m <- this.read(protobuf) n <- that.read(protobuf) } yield f(m, n) + } /** * Combines two readers into a reader of a tuple. @@ -87,7 +88,7 @@ trait Reader[-P, +M] { * Chain `that` reader after `this` one. */ final def andThen[N](that: Reader[M, N]): Reader[P, N] = - read(_).flatMap(that.read) + Reader.instance(read(_).flatMap(that.read)) /** * Chain `this` reader after `that` one. @@ -100,6 +101,8 @@ object Reader extends LowPriorityReads { def apply[P, M](implicit reader: Reader[P, M]): Reader[P, M] = reader + def instance[P, M](f: P => PbResult[M]): Reader[P, M] = f(_) + /* Combinators */ def transform[PV, MV](protobuf: PV, path: String)(implicit valueReader: Reader[PV, MV]): PbResult[MV] = @@ -111,17 +114,17 @@ object Reader extends LowPriorityReads { def required[PV, MV](protobuf: Option[PV], path: String)(implicit valueReader: Reader[PV, MV]): PbResult[MV] = protobuf.map(valueReader.read).getOrElse(PbFailure("Value is required.")).withPathPrefix(path) - def sequence[F[_], PV, MV](protobufs: Seq[PV], path: String)(implicit factory: Factory[MV, F[MV]], - valueReader: Reader[PV, MV]): PbResult[F[MV]] = { + def sequence[F[_], PV, MV](protobufs: Seq[PV], path: String)( + implicit factory: Factory[MV, F[MV]], + valueReader: Reader[PV, MV] + ): PbResult[F[MV]] = { val results = protobufs.map(valueReader.read).zipWithIndex - - val errors = - results.flatMap { - case (PbFailure(innerErrors), index) => - PbFailure(innerErrors).withPathPrefix(s"$path($index)").errors - case _ => - Seq.empty - } + val errors = results.flatMap { + case (PbFailure(innerErrors), index) => + PbFailure(innerErrors).withPathPrefix(s"$path($index)").errors + case _ => + Seq.empty + } if (errors.nonEmpty) PbFailure(errors) @@ -209,10 +212,13 @@ object Reader extends LowPriorityReads { * Tries to read a map of Protobuf key/values to a sorted map of Scala key/values if reader exists between both types * and an ordering is defined on the Scala key. */ - implicit def treeMapReader[PK, PV, MK, MV](implicit keyReader: Reader[PK, MK], - valueReader: Reader[PV, MV], - ordering: Ordering[MK]): Reader[Map[PK, PV], TreeMap[MK, MV]] = - (protobuf: Map[PK, PV]) => mapReader(keyReader, valueReader).read(protobuf).map(entries => TreeMap[MK, MV](entries.toSeq: _*)) + implicit def treeMapReader[PK, PV, MK, MV]( + implicit keyReader: Reader[PK, MK], + valueReader: Reader[PV, MV], + ordering: Ordering[MK] + ): Reader[Map[PK, PV], TreeMap[MK, MV]] = instance { protobuf => + mapReader(keyReader, valueReader).read(protobuf).map(entries => TreeMap[MK, MV](entries.toSeq: _*)) + } /** * A reader that gives access to the inner [[PbResult]]. @@ -220,7 +226,7 @@ object Reader extends LowPriorityReads { * to always deserialize an event to an `Envelope[PbResult[A]]` even if the actual payload of type `A` fails to parse. */ implicit def pbResultReader[PB <: GeneratedMessage, A](implicit reader: Reader[PB, A]): Reader[PB, PbResult[A]] = - pb => PbSuccess(reader.read(pb)) + instance(pb => PbSuccess(reader.read(pb))) } private[teleproto] trait LowPriorityReads extends LowestPriorityReads { @@ -229,31 +235,25 @@ private[teleproto] trait LowPriorityReads extends LowestPriorityReads { * Tries to read a map of Protobuf key/values to a sorted map of Scala key/values if reader exists between both types * and an ordering is defined on the Scala key. */ - implicit def mapReader[PK, PV, MK, MV](implicit keyReader: Reader[PK, MK], - valueReader: Reader[PV, MV]): Reader[Map[PK, PV], Map[MK, MV]] = - (protobuf: Map[PK, PV]) => { - val modelResults = - for ((protoKey, protoValue) <- protobuf.toSeq) - yield { - for { - key <- keyReader.read(protoKey).withPathPrefix(s"/$protoKey") - value <- valueReader.read(protoValue).withPathPrefix(s"/$key") - } yield { - (key, value) - } - } - - val errors = - modelResults.flatMap { - case PbFailure(innerErrors) => innerErrors - case _ => Seq.empty - } - - if (errors.nonEmpty) - PbFailure(errors) - else - PbSuccess(Map[MK, MV](modelResults.map(_.get): _*)) + implicit def mapReader[PK, PV, MK, MV]( + implicit keyReader: Reader[PK, MK], + valueReader: Reader[PV, MV] + ): Reader[Map[PK, PV], Map[MK, MV]] = Reader.instance { protobuf => + val modelResults = for ((protoKey, protoValue) <- protobuf.toSeq) yield { + for { + key <- keyReader.read(protoKey).withPathPrefix(s"/$protoKey") + value <- valueReader.read(protoValue).withPathPrefix(s"/$key") + } yield (key, value) + } + + val errors = modelResults.flatMap { + case PbFailure(innerErrors) => innerErrors + case _ => Seq.empty } + + if (errors.nonEmpty) PbFailure(errors) + else PbSuccess(Map[MK, MV](modelResults.map(_.get): _*)) + } } private[teleproto] trait LowestPriorityReads { @@ -262,5 +262,5 @@ private[teleproto] trait LowestPriorityReads { * Keeps a value of same type in protobuf and model. */ implicit def identityReader[T]: Reader[T, T] = - (value: T) => PbSuccess(value) + Reader.instance(PbSuccess.apply) } diff --git a/src/main/scala/io/moia/protos/teleproto/ReaderImpl.scala b/src/main/scala/io/moia/protos/teleproto/ReaderImpl.scala index 6c1ebc3..e51bd1f 100644 --- a/src/main/scala/io/moia/protos/teleproto/ReaderImpl.scala +++ b/src/main/scala/io/moia/protos/teleproto/ReaderImpl.scala @@ -22,6 +22,10 @@ import scala.reflect.macros.blackbox class ReaderImpl(val c: blackbox.Context) extends FormatImpl { import c.universe._ + private[this] val readerObj = objectRef[Reader.type] + private[this] val pbSuccessObj = objectRef[PbSuccess.type] + private[this] val pbFailureObj = objectRef[PbFailure.type] + def reader_impl[P: WeakTypeTag, M: WeakTypeTag]: Expr[Reader[P, M]] = c.Expr(compile[P, M]) @@ -107,56 +111,53 @@ class ReaderImpl(val c: blackbox.Context) extends FormatImpl { */ private def compileClassMapping(protobufType: Type, modelType: Type): Compiled = { val modelCompanion = modelType.typeSymbol.companion - - val protobufCons = protobufType.member(termNames.CONSTRUCTOR).asMethod - val modelCons = modelType.member(termNames.CONSTRUCTOR).asMethod - + val protobufCons = protobufType.member(termNames.CONSTRUCTOR).asMethod + val modelCons = modelType.member(termNames.CONSTRUCTOR).asMethod val protobufParams = protobufCons.paramLists.headOption.getOrElse(Nil).map(_.asTerm) val modelParams = modelCons.paramLists.headOption.getOrElse(Nil).map(_.asTerm) - val mapping = q"io.moia.protos.teleproto" - /* * For each parameter creates a value assignment with the name of the parameter, e.g. * `val targetParameter = transformationExpression` */ - def valueDefinitions(parameters: List[(TermSymbol, MatchingParam)]): List[Compiled] = { + def valueDefinitions(protobuf: TermName, parameters: List[(TermSymbol, MatchingParam)]): List[Compiled] = { parameters.flatMap { - case (termSymbol, matchingParam) => - val path = q""""/"+${termSymbol.name.decodedName.toString}""" + case (paramSym, matchingParam) => + val param = paramSym.name + val path = q""""/"+${param.decodedName.toString}""" def assign(compiled: Compiled): Compiled = - (q"val ${termSymbol.name} = ${compiled._1}", compiled._2) + (q"val $param = ${compiled._1}", compiled._2) matchingParam match { case TransformParam(from, to) if from <:< to => - Some(q"val ${termSymbol.name} = protobuf.${termSymbol.name}" -> Compatibility.full) + Some(q"val $param = $protobuf.$param" -> Compatibility.full) case TransformParam(from, to) if from <:< weakTypeOf[Option[_]] && !(to <:< weakTypeOf[Option[_]]) => val innerFrom = innerType(from) - Some(assign(withImplicitReader(innerFrom, to) { readerExpr => - q"""$mapping.Reader.required[$innerFrom, $to](protobuf.${termSymbol.name}, $path)($readerExpr)""" + Some(assign(withImplicitReader(innerFrom, to) { reader => + q"$readerObj.required[$innerFrom, $to]($protobuf.$param, $path)($reader)" })) case TransformParam(from, to) if from <:< weakTypeOf[Option[_]] && (to <:< weakTypeOf[Option[_]]) => val innerFrom = innerType(from) val innerTo = innerType(to) - Some(assign(withImplicitReader(innerFrom, innerTo) { readerExpr => - q"""$mapping.Reader.optional[$innerFrom, $innerTo](protobuf.${termSymbol.name}, $path)($readerExpr)""" + Some(assign(withImplicitReader(innerFrom, innerTo) { reader => + q"$readerObj.optional[$innerFrom, $innerTo]($protobuf.$param, $path)($reader)" })) case TransformParam(from, to) if from <:< weakTypeOf[Seq[_]] && to <:< weakTypeOf[Iterable[_]] => val innerFrom = innerType(from) val innerTo = innerType(to) - Some(assign(withImplicitReader(innerFrom, innerTo) { readerExpr => + Some(assign(withImplicitReader(innerFrom, innerTo) { reader => // sequence also needs an implicit collection generator which must be looked up since the implicit for the value reader is passed explicitly val canBuildFrom = VersionSpecific.lookupFactory(c)(innerTo, to) - q"""$mapping.Reader.sequence[${to.typeConstructor}, $innerFrom, $innerTo](protobuf.${termSymbol.name}, $path)($canBuildFrom, $readerExpr)""" + q"$readerObj.sequence[${to.typeConstructor}, $innerFrom, $innerTo]($protobuf.$param, $path)($canBuildFrom, $reader)" })) case TransformParam(from, to) => - Some(assign(withImplicitReader(from, to) { readerExpr => - q"""$mapping.Reader.transform[$from, $to](protobuf.${termSymbol.name}, $path)($readerExpr)""" + Some(assign(withImplicitReader(from, to) { reader => + q"$readerObj.transform[$from, $to]($protobuf.$param, $path)($reader)" })) case ExplicitDefaultParam(expr) => - Some(q"val ${termSymbol.name} = $expr" -> Compatibility.full) + Some(q"val $param = $expr" -> Compatibility.full) case SkippedDefaultParam => Option.empty[Compiled] } @@ -200,7 +201,7 @@ class ReaderImpl(val c: blackbox.Context) extends FormatImpl { Some(termSymbol.name) } - q"$mapping.PbFailure.combine(..$convertedValues)" + q"$pbFailureObj.combine(..$convertedValues)" } def transformation(parameters: Seq[MatchingParam], ownCompatibility: Compatibility): Compiled = { @@ -216,23 +217,15 @@ class ReaderImpl(val c: blackbox.Context) extends FormatImpl { case (param, _) => Some(q"${param.name} = ${param.name}") }) - val (valDefs, parameterCompatibilities) = valueDefinitions(matchedParameters).unzip + val protobuf = c.freshName(TermName("protobuf")) + val (valDefs, parameterCompatibilities) = valueDefinitions(protobuf, matchedParameters).unzip // expression that constructs the successful result: `PbSuccess(ModelClass(transformedParameter..))` - val cons = q"""$mapping.PbSuccess[$modelType](${modelCompanion.asTerm}.apply(..$passedArgumentNames))""" - - val errorsHandled = q"""val result = ${forLoop(matchedParameters, cons)}; result.orElse(${combineErrors(matchedParameters)})""" - - val transformed = valDefs.foldRight(errorsHandled)((t1, t2) => q"$t1; $t2") - + val cons = q"$pbSuccessObj[$modelType](${modelCompanion.asTerm}.apply(..$passedArgumentNames))" + val errorsHandled = q"${forLoop(matchedParameters, cons)}.orElse(${combineErrors(matchedParameters)})" + val transformed = valDefs.foldRight(errorsHandled)((t1, t2) => q"$t1; $t2") val parameterCompatibility = parameterCompatibilities.fold(Compatibility.full)(_ merge _) - - val result = - q""" - new $mapping.Reader[$protobufType, $modelType] { - def read(protobuf: $protobufType) = $transformed - }""" - + val result = q"$readerObj.instance[$protobufType, $modelType] { case $protobuf => $transformed }" (result, ownCompatibility.merge(parameterCompatibility)) } @@ -366,15 +359,11 @@ class ReaderImpl(val c: blackbox.Context) extends FormatImpl { val (cases, compatibility) = subTypes.unzip val emptyCase = for (protobufClass <- protobufSubClasses.get(EmptyOneOf) if !modelSubclasses.contains(EmptyOneOf)) - yield cq"""_: ${objectReferenceTo(protobufClass)}.type => $mapping.PbFailure("Oneof field is empty!")""" + yield cq"""_: ${objectReferenceTo(protobufClass)}.type => $pbFailureObj("Oneof field is empty!")""" - val reader = c.freshName(TermName("reader")) - val result = q"""{ - val $reader: $mapping.Reader[$protobufType, $modelType] = { - case ..$cases - case ..${emptyCase.toList} - } - $reader + val result = q"""$readerObj.instance[$protobufType, $modelType] { + case ..$cases + case ..${emptyCase.toList} }""" (result, compatibility.fold(ownCompatibility)(_ merge _)) @@ -419,23 +408,19 @@ class ReaderImpl(val c: blackbox.Context) extends FormatImpl { val cases = for { (optionName, modelOption) <- modelOptions.toList protobufOption <- protobufOptions.get(optionName) - } yield cq"_: ${objectReferenceTo(protobufOption)}.type => $mapping.PbSuccess(${objectReferenceTo(modelOption)})" + } yield cq"_: ${objectReferenceTo(protobufOption)}.type => $pbSuccessObj(${objectReferenceTo(modelOption)})" val invalidCase = for (protobufOption <- protobufOptions.get(InvalidEnum) if !modelOptions.contains(InvalidEnum)) yield { val reference = objectReferenceTo(protobufOption) - cq"""_: $reference.type => $mapping.PbFailure(s"Enumeration value $${$reference} is invalid!")""" + cq"""_: $reference.type => $pbFailureObj(s"Enumeration value $${$reference} is invalid!")""" } - val reader = c.freshName(TermName("reader")) val other = c.freshName(TermName("other")) - val result = q"""{ - val $reader: $mapping.Reader[$protobufType, $modelType] = { - case ..$cases - case ..${invalidCase.toList} - case ${protobufCompanion.asTerm}.Unrecognized($other) => - $mapping.PbFailure(s"Enumeration value $${$other} is unrecognized!") - } - $reader + val result = q"""$readerObj.instance[$protobufType, $modelType] { + case ..$cases + case ..${invalidCase.toList} + case ${protobufCompanion.asTerm}.Unrecognized($other) => + $pbFailureObj(s"Enumeration value $${$other} is unrecognized!") }""" (result, compatibility) diff --git a/src/main/scala/io/moia/protos/teleproto/Writer.scala b/src/main/scala/io/moia/protos/teleproto/Writer.scala index 28a50b3..e417f95 100644 --- a/src/main/scala/io/moia/protos/teleproto/Writer.scala +++ b/src/main/scala/io/moia/protos/teleproto/Writer.scala @@ -43,25 +43,25 @@ trait Writer[-M, +P] { * Transforms each written result. */ def map[Q](f: P => Q): Writer[M, Q] = - model => f(write(model)) + Writer.instance(model => f(write(model))) /** * Transforms the model before writing. */ final def contramap[N](f: N => M): Writer[N, P] = - model => write(f(model)) + Writer.instance(model => write(f(model))) /** * Transforms written results by stacking another writer on top of the original model. */ final def flatMap[N <: M, Q](f: P => Writer[N, Q]): Writer[N, Q] = - model => f(write(model)).write(model) + Writer.instance(model => f(write(model)).write(model)) /** * Combines two writers with a specified function. */ final def zipWith[N <: M, Q, R](that: Writer[N, Q])(f: (P, Q) => R): Writer[N, R] = - model => f(this.write(model), that.write(model)) + Writer.instance(model => f(this.write(model), that.write(model))) /** * Combines two writers into a writer of a tuple. @@ -73,7 +73,7 @@ trait Writer[-M, +P] { * Chain `that` writer after `this` one. */ final def andThen[Q](that: Writer[P, Q]): Writer[M, Q] = - model => that.write(this.write(model)) + Writer.instance(model => that.write(this.write(model))) /** * Chain `this` writer after `that` one. @@ -86,6 +86,8 @@ object Writer extends LowPriorityWrites { def apply[M, P](implicit writer: Writer[M, P]): Writer[M, P] = writer + def instance[M, P](f: M => P): Writer[M, P] = f(_) + /* Combinators */ def transform[MV, PV](model: MV)(implicit valueWriter: Writer[MV, PV]): PV = @@ -172,14 +174,20 @@ object Writer extends LowPriorityWrites { /** * Transforms a Scala map into a corresponding map with Protobuf types if writers exists between key and value types. */ - implicit def mapWriter[MK, MV, PK, PV](implicit keyWriter: Writer[MK, PK], - valueWriter: Writer[MV, PV]): Writer[Map[MK, MV], Map[PK, PV]] = - (model: Map[MK, MV]) => for ((key, value) <- model) yield (keyWriter.write(key), valueWriter.write(value)) + implicit def mapWriter[MK, MV, PK, PV]( + implicit keyWriter: Writer[MK, PK], + valueWriter: Writer[MV, PV] + ): Writer[Map[MK, MV], Map[PK, PV]] = instance { model => + for ((key, value) <- model) yield (keyWriter.write(key), valueWriter.write(value)) + } - implicit def treeMapWriter[MK, MV, PK, PV](implicit keyWriter: Writer[MK, PK], - valueWriter: Writer[MV, PV], - ordering: Ordering[PK]): Writer[TreeMap[MK, MV], Map[PK, PV]] = - (model: TreeMap[MK, MV]) => for ((key, value) <- model) yield (keyWriter.write(key), valueWriter.write(value)) + implicit def treeMapWriter[MK, MV, PK, PV]( + implicit keyWriter: Writer[MK, PK], + valueWriter: Writer[MV, PV], + ordering: Ordering[PK] + ): Writer[TreeMap[MK, MV], Map[PK, PV]] = instance { model => + for ((key, value) <- model) yield (keyWriter.write(key), valueWriter.write(value)) + } } trait LowPriorityWrites extends LowestPriorityWrites { @@ -187,9 +195,8 @@ trait LowPriorityWrites extends LowestPriorityWrites { def sequence[MV, PV](models: IterableOnce[MV])(implicit valueWriter: Writer[MV, PV]): Seq[PV] = models.iterator.map(valueWriter.write).toSeq - def collection[MV, PV, Col[_]](models: IterableOnce[MV])(implicit cbf: Factory[PV, Col[PV]], valueWriter: Writer[MV, PV]): Col[PV] = { + def collection[MV, PV, Col[_]](models: IterableOnce[MV])(implicit cbf: Factory[PV, Col[PV]], valueWriter: Writer[MV, PV]): Col[PV] = models.iterator.map(valueWriter.write).iterator.to(cbf) - } } trait LowestPriorityWrites { @@ -198,5 +205,5 @@ trait LowestPriorityWrites { * Keeps a value of same type in protobuf and model. */ implicit def identityWriter[T]: Writer[T, T] = - (value: T) => value + Writer.instance(identity) } diff --git a/src/main/scala/io/moia/protos/teleproto/WriterImpl.scala b/src/main/scala/io/moia/protos/teleproto/WriterImpl.scala index 78abc6d..d685e43 100644 --- a/src/main/scala/io/moia/protos/teleproto/WriterImpl.scala +++ b/src/main/scala/io/moia/protos/teleproto/WriterImpl.scala @@ -23,6 +23,9 @@ import scala.reflect.macros.blackbox class WriterImpl(val c: blackbox.Context) extends FormatImpl { import c.universe._ + private[this] val writerObj = objectRef[Writer.type] + private[this] val seqTpe = typeOf[scala.collection.immutable.Seq[_]].typeConstructor + /** * Validates if business model type can be written to the Protocol Buffers type * (matching case classes or matching sealed trait hierarchy). @@ -107,70 +110,57 @@ class WriterImpl(val c: blackbox.Context) extends FormatImpl { private def compileClassMapping(protobufType: Type, modelType: Type): Compiled = { // at this point all errors are assumed to be due to evolution val protobufCompanion = protobufType.typeSymbol.companion - - val protobufCons = protobufType.member(termNames.CONSTRUCTOR).asMethod - val modelCons = modelType.member(termNames.CONSTRUCTOR).asMethod - - val protobufParams = protobufCons.paramLists.headOption.getOrElse(Nil).map(_.asTerm) - val modelParams = modelCons.paramLists.headOption.getOrElse(Nil).map(_.asTerm) - - val mapping = q"io.moia.protos.teleproto" + val protobufCons = protobufType.member(termNames.CONSTRUCTOR).asMethod + val modelCons = modelType.member(termNames.CONSTRUCTOR).asMethod + val protobufParams = protobufCons.paramLists.headOption.getOrElse(Nil).map(_.asTerm) + val modelParams = modelCons.paramLists.headOption.getOrElse(Nil).map(_.asTerm) def transformation(parameters: Seq[MatchingParam], ownCompatibility: Compatibility): Compiled = { + val model = c.freshName(TermName("model")) + + val namedArguments = protobufParams.zip(parameters).flatMap { + // unmatched parameters with default values are not passed: they get their defaults + case (_, SkippedDefaultParam) => None + case (paramSym, TransformParam(from, to)) => + val param = paramSym.name + val arg = if (from <:< to) { + (q"$model.$param", Compatibility.full) + } else if (to <:< weakTypeOf[Option[_]] && !(from <:< weakTypeOf[Option[_]])) { + withImplicitWriter(from, innerType(to)) { writer => + q"$writerObj.present[$from, ${innerType(to)}]($model.$param)($writer)" + } + } else if (to <:< weakTypeOf[Option[_]] && from <:< weakTypeOf[Option[_]]) { + withImplicitWriter(innerType(from), innerType(to)) { writer => + q"$writerObj.optional[${innerType(from)}, ${innerType(to)}]($model.$param)($writer)" + } + } else if (from <:< weakTypeOf[IterableOnce[_]] && to <:< weakTypeOf[scala.collection.immutable.Seq[_]]) { + val innerFrom = innerType(from) + val innerTo = innerType(to) + withImplicitWriter(innerFrom, innerTo) { writer => + // collection also needs an implicit sequence generator which must be looked up since the implicit for the value writer is passed explicitly + val canBuildFrom = VersionSpecific.lookupFactory(c)(innerTo, to) + q"$writerObj.collection[$innerFrom, $innerTo, $seqTpe]($model.$param)($canBuildFrom, $writer)" + } + } else if (from <:< weakTypeOf[IterableOnce[_]] && to <:< weakTypeOf[Seq[_]]) { + val innerFrom = innerType(from) + val innerTo = innerType(to) + withImplicitWriter(innerFrom, innerTo) { writer => + q"$writerObj.sequence[$innerFrom, $innerTo]($model.$param)($writer)" + } + } else { + withImplicitWriter(from, to) { writer => + q"$writerObj.transform[$from, $to]($model.$param)($writer)" + } + } + + Some(param -> arg) + } - val namedArguments = - protobufParams - .zip(parameters) - .flatMap(_ match { - // unmatched parameters with default values are not passed: they get their defaults - case (_, SkippedDefaultParam) => None - case (param, TransformParam(from, to)) => - val arg = - if (from <:< to) { - (q"""model.${param.name}""", Compatibility.full) - } else if (to <:< weakTypeOf[Option[_]] && !(from <:< weakTypeOf[Option[_]])) { - withImplicitWriter(from, innerType(to))( - writerExpr => q"""$mapping.Writer.present[$from, ${innerType(to)}](model.${param.name})($writerExpr)""" - ) - } else if (to <:< weakTypeOf[Option[_]] && from <:< weakTypeOf[Option[_]]) { - withImplicitWriter(innerType(from), innerType(to))( - writerExpr => q"""$mapping.Writer.optional[${innerType(from)}, ${innerType(to)}](model.${param.name})($writerExpr)""" - ) - } else if (from <:< weakTypeOf[IterableOnce[_]] && to <:< weakTypeOf[scala.collection.immutable.Seq[_]]) { - val innerFrom = innerType(from) - val innerTo = innerType(to) - withImplicitWriter(innerFrom, innerTo) { writerExpr => - // collection also needs an implicit sequence generator which must be looked up since the implicit for the value writer is passed explicitly - val canBuildFrom = VersionSpecific.lookupFactory(c)(innerTo, to) - q"""$mapping.Writer.collection[$innerFrom, $innerTo, scala.collection.immutable.Seq](model.${param.name})($canBuildFrom, $writerExpr)""" - } - } else if (from <:< weakTypeOf[IterableOnce[_]] && to <:< weakTypeOf[Seq[_]]) { - val innerFrom = innerType(from) - val innerTo = innerType(to) - withImplicitWriter(innerFrom, innerTo)( - writerExpr => q"""$mapping.Writer.sequence[$innerFrom, $innerTo](model.${param.name})($writerExpr)""" - ) - } else { - withImplicitWriter(from, to)( - writerExpr => q"""$mapping.Writer.transform[$from, $to](model.${param.name})($writerExpr)""" - ) - } - - Some(param.name -> arg) - }) - - val cons = - q"""${protobufCompanion.asTerm}.apply(..${for ((name, (arg, _)) <- namedArguments) yield q"$name = $arg"})""" - + val args = for ((name, (arg, _)) <- namedArguments) yield q"$name = $arg" + val cons = q"${protobufCompanion.asTerm}.apply(..$args)" val innerCompatibilities = for ((_, (_, innerCompatibility)) <- namedArguments) yield innerCompatibility val compatibility = innerCompatibilities.foldRight(ownCompatibility)(_.merge(_)) - - val result = - q""" - new $mapping.Writer[$modelType, $protobufType] { - def write(model: $modelType) = $cons - }""" - + val result = q"$writerObj.instance[$modelType, $protobufType] { case $model => $cons }" (result, compatibility) } @@ -267,27 +257,19 @@ class WriterImpl(val c: blackbox.Context) extends FormatImpl { val surplusProtobufClasses = protobufSubclasses.keySet - EmptyOneOf diff modelSubclasses.keySet val ownCompatibility = Compatibility(Nil, Nil, surplusProtobufClasses.map(name => (protobufType, name.toString))) val valueMethod = protobufType.member(ValueMethod).asMethod + val model = c.freshName(TermName("model")) val subTypes = for { (className, protobufSubclass) <- protobufSubclasses modelSubclass <- modelSubclasses.get(className) } yield { withImplicitWriter(classTypeOf(modelSubclass), valueMethod.infoIn(classTypeOf(protobufSubclass))) { writer => - val model = c.freshName(TermName("model")) cq"$model: $modelSubclass => new $protobufSubclass($writer.write($model))" } } val (cases, compatibility) = subTypes.unzip - - val writer = c.freshName(TermName("writer")) - val result = q"""{ - val $writer: $mapping.Writer[$modelType, $protobufType] = { - case ..$cases - } - $writer - }""" - + val result = q"$writerObj.instance[$modelType, $protobufType] { case ..$cases }" (result, compatibility.fold(ownCompatibility)(_ merge _)) } @@ -327,14 +309,7 @@ class WriterImpl(val c: blackbox.Context) extends FormatImpl { modelOption <- modelOptions.get(optionName) } yield cq"_: ${objectReferenceTo(modelOption)}.type => ${objectReferenceTo(protobufOption)}" - val writer = c.freshName(TermName("writer")) - val result = q"""{ - val $writer: $mapping.Writer[$modelType, $protobufType] = { - case ..$cases - } - $writer - }""" - + val result = q"$writerObj.instance[$modelType, $protobufType] { case ..$cases }" (result, compatibility) }