Skip to content

Commit

Permalink
Introduce a new MessageBuilder class for each message.
Browse files Browse the repository at this point in the history
MessageBuilder is a mutable class providing the ability to merge a
message from a CodedInputStream into the mutable state, and build an
instance of a message.

MessageCompanion.newBuilder returns a new Builder initialized with empty
values.

MessageCompanion.newBuilder(msg) returns a new Builder initialized
with values from a given instance of a message.

This new functionality allows us to create builders with partially
initialized objects that can not be represented by the actual message.

See #1013 and scalapb/scalapb-validate#38
  • Loading branch information
thesamet committed Dec 24, 2020
1 parent 23f2582 commit c9e2ceb
Show file tree
Hide file tree
Showing 124 changed files with 7,888 additions and 4,527 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ lazy val e2e = (project in file("e2e"))
.settings(e2eCommonSettings)
.settings(
scalacOptions ++= Seq(
"-P:silencer:globalFilters=value deprecatedInt32 in class TestDeprecatedFields is deprecated",
"-P:silencer:globalFilters=eprecatedInt32 in class TestDeprecatedFields is deprecated",
"-P:silencer:pathFilters=custom_options_use;CustomAnnotationProto.scala;changed/scoped;ServerReflectionGrpc.scala",
"-P:silencer:lineContentFilters=import com.thesamet.pb.MisplacedMapper.weatherMapper"
),
Expand Down
179 changes: 121 additions & 58 deletions compiler-plugin/src/main/scala/scalapb/compiler/ProtobufGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -716,45 +716,124 @@ class ProtobufGenerator(
printer.addWithDelimiter(",")(constructorFields(message).map(_.fullString))
}

def generateMerge(message: Descriptor)(printer: FunctionalPrinter): FunctionalPrinter = {
def generateBuilder(message: Descriptor)(printer: FunctionalPrinter): FunctionalPrinter = {
val myFullScalaName = message.scalaType.fullNameWithMaybeRoot(message)
val requiredFieldMap: Map[FieldDescriptor, Int] =
message.fields.filter(_.isRequired).zipWithIndex.toMap
case class Field(name: String, typeName: String, default: String, accessor: String)

val fields = message.fieldsWithoutOneofs.map { field =>
if (!field.isRepeated)
Field(
s"__${field.scalaName}",
field.scalaTypeName,
defaultValueForDefaultInstance(field),
s"_message__.${field.scalaName.asSymbol}"
)
else
Field(
s"__${field.scalaName}",
s"collection.mutable.Builder[${field.singleScalaTypeName}, ${field.scalaTypeName}]",
field.collectionBuilder,
s"${field.collectionBuilder} ++= _message__.${field.scalaName.asSymbol}"
)
} ++ message.getOneofs.asScala.map { oneof =>
Field(
s"__${oneof.scalaName.name}",
oneof.scalaType.fullName,
oneof.empty.fullName,
s"_message__.${oneof.scalaName.nameSymbol}"
)
} ++ (if (message.preservesUnknownFields)
Seq(
Field(
"`_unknownFields__`",
"_root_.scalapb.UnknownFieldSet.Builder",
"null",
"new _root_.scalapb.UnknownFieldSet.Builder(_message__.unknownFields)"
)
)
else Seq.empty)

printer
.add(
s"def merge(`_message__`: $myFullScalaName, `_input__`: _root_.com.google.protobuf.CodedInputStream): $myFullScalaName = {"
.add(s"final class Builder private (")
.indented(
_.addWithDelimiter(",")(fields.map(f => s"private var ${f.name}: ${f.typeName}"))
)
.indent
.print(message.fieldsWithoutOneofs)((printer, field) =>
if (!field.isRepeated)
printer.add(s"var __${field.scalaName} = `_message__`.${field.scalaName.asSymbol}")
else
printer.add(
s"val __${field.scalaName} = (${field.collectionBuilder} ++= " +
s"`_message__`.${field.scalaName.asSymbol})"
.add(s") extends _root_.scalapb.MessageBuilder[$myFullScalaName] {")
.indented(
_.when(requiredFieldMap.nonEmpty) { fp =>
// Sets the bit 0...(n-1) inclusive to 1.
def hexBits(n: Int): String = "0x%xL".format((0 to (n - 1)).map(i => (1L << i)).sum)
val requiredFieldCount = requiredFieldMap.size
val fullWords = (requiredFieldCount - 1) / 64
val bits: Seq[String] = (1 to fullWords).map(_ => hexBits(64)) :+ hexBits(
requiredFieldCount - 64 * fullWords
)
fp.print(bits.zipWithIndex) {
case (fp, (bn, index)) =>
fp.add(s"private var __requiredFields$index: _root_.scala.Long = $bn")
}
}.call(generateBuilderMerge(message))
.add(s"def result(): ${myFullScalaName} = {")
.indented(
_.when(requiredFieldMap.nonEmpty) { p =>
val r = (0 until (requiredFieldMap.size + 63) / 64)
.map(i => s"__requiredFields$i != 0L")
.mkString(" || ")
p.add(
s"""if (${r}) { throw new _root_.com.google.protobuf.InvalidProtocolBufferException("Message missing required fields.") } """
)
}.add(s"$myFullScalaName(")
.indented(
_.addWithDelimiter(",")(
(message.fieldsWithoutOneofs ++ message.getOneofs.asScala).map {
case e: FieldDescriptor if e.isRepeated =>
s" ${e.scalaName.asSymbol} = __${e.scalaName}.result()"
case e: FieldDescriptor =>
s" ${e.scalaName.asSymbol} = __${e.scalaName}"
case e: OneofDescriptor =>
s" ${e.scalaName.nameSymbol} = __${e.scalaName.name}"
} ++ (if (message.preservesUnknownFields)
Seq(
" unknownFields = if (_unknownFields__ == null) _root_.scalapb.UnknownFieldSet.empty else _unknownFields__.result()"
)
else Seq())
)
)
.add(")")
)
.add("}")
)
.when(message.preservesUnknownFields)(
_.add(
"var `_unknownFields__`: _root_.scalapb.UnknownFieldSet.Builder = null"
)
.add("}")
.add(
s"object Builder extends _root_.scalapb.MessageBuilderCompanion[$myFullScalaName, $myFullScalaName.Builder] {"
)
.when(requiredFieldMap.nonEmpty) { fp =>
// Sets the bit 0...(n-1) inclusive to 1.
def hexBits(n: Int): String = "0x%xL".format((0 to (n - 1)).map(i => (1L << i)).sum)
val requiredFieldCount = requiredFieldMap.size
val fullWords = (requiredFieldCount - 1) / 64
val bits: Seq[String] = (1 to fullWords).map(_ => hexBits(64)) :+ hexBits(
requiredFieldCount - 64 * fullWords
)
fp.print(bits.zipWithIndex) {
case (fp, (bn, index)) =>
fp.add(s"var __requiredFields$index: _root_.scala.Long = $bn")
}
}
.print(message.getOneofs.asScala)((printer, oneof) =>
printer.add(s"var __${oneof.scalaName.name} = `_message__`.${oneof.scalaName.nameSymbol}")
.indented(
_.add("def apply(): Builder = new Builder(")
.indented(
_.addWithDelimiter(",")(fields.map(f => s"${f.name} = ${f.default}"))
)
.add(")")
.add(s"def apply(`_message__`: $myFullScalaName): Builder = new Builder(")
.indented(
_.addWithDelimiter(",")(fields.map(f => s"${f.name} = ${f.accessor}"))
)
.add(")")
)
.add("}")
.add(s"def newBuilder: Builder = $myFullScalaName.Builder()")
.add(s"def newBuilder(a: $myFullScalaName): Builder = $myFullScalaName.Builder(a)")
}

def generateBuilderMerge(message: Descriptor)(printer: FunctionalPrinter): FunctionalPrinter = {
val requiredFieldMap: Map[FieldDescriptor, Int] =
message.fields.filter(_.isRequired).zipWithIndex.toMap
printer
.add(
s"def merge(`_input__`: _root_.com.google.protobuf.CodedInputStream): this.type = {"
)
.indent
.add(s"""var _done__ = false
|while (!_done__) {
| val _tag__ = _input__.readTag()
Expand All @@ -770,7 +849,7 @@ class ProtobufGenerator(
else {
val expr =
if (field.isInOneof)
s"_message__.${fieldAccessorSymbol(field)}"
s"__${fieldAccessorSymbol(field)}"
else s"__${field.scalaName}"
val mappedType =
toBaseFieldType(field).apply(expr, field.enclosingType)
Expand Down Expand Up @@ -829,43 +908,26 @@ class ProtobufGenerator(
_.add(
""" case tag =>
| if (_unknownFields__ == null) {
| _unknownFields__ = new _root_.scalapb.UnknownFieldSet.Builder(_message__.unknownFields)
| _unknownFields__ = new _root_.scalapb.UnknownFieldSet.Builder()
| }
| _unknownFields__.parseField(tag, _input__)""".stripMargin
)
)
.add(" }")
.add("}")
.when(requiredFieldMap.nonEmpty) { p =>
val r = (0 until (requiredFieldMap.size + 63) / 64)
.map(i => s"__requiredFields$i != 0L")
.mkString(" || ")
p.add(
s"""if (${r}) { throw new _root_.com.google.protobuf.InvalidProtocolBufferException("Message missing required fields.") } """
)
}
.add(s"$myFullScalaName(")
.indent
.addWithDelimiter(",")(
(message.fieldsWithoutOneofs ++ message.getOneofs.asScala).map {
case e: FieldDescriptor if e.isRepeated =>
s" ${e.scalaName.asSymbol} = __${e.scalaName}.result()"
case e: FieldDescriptor =>
s" ${e.scalaName.asSymbol} = __${e.scalaName}"
case e: OneofDescriptor =>
s" ${e.scalaName.nameSymbol} = __${e.scalaName.name}"
} ++ (if (message.preservesUnknownFields)
Seq(
" unknownFields = if (_unknownFields__ == null) _message__.unknownFields else _unknownFields__.result()"
)
else Seq())
)
.outdent
.add(")")
.add("this")
.outdent
.add("}")
}

def generateMerge(message: Descriptor)(printer: FunctionalPrinter): FunctionalPrinter = {
val myFullScalaName = message.scalaType.fullNameWithMaybeRoot(message)
printer
.add(
s"def merge(`_message__`: $myFullScalaName, `_input__`: _root_.com.google.protobuf.CodedInputStream): $myFullScalaName = newBuilder(_message__).merge(_input__).result()"
)
}

def generateToJavaProto(message: Descriptor)(printer: FunctionalPrinter): FunctionalPrinter = {
val myFullScalaName = message.scalaType.fullName
printer
Expand Down Expand Up @@ -1353,6 +1415,7 @@ class ProtobufGenerator(
.call(generateNestedMessagesCompanions(message))
.call(generateEnumCompanionForField(message))
.call(generateDefaultInstance(message))
.call(generateBuilder(message))
.print(message.getEnumTypes.asScala)(printEnum)
.print(message.getOneofs.asScala)(printOneof)
.print(message.nestedTypes)(printMessage)
Expand Down
63 changes: 41 additions & 22 deletions docs/src/main/scala/com/thesamet/docs/json/MyContainer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,28 +60,7 @@ final case class MyContainer(

object MyContainer extends scalapb.GeneratedMessageCompanion[com.thesamet.docs.json.MyContainer] {
implicit def messageCompanion: scalapb.GeneratedMessageCompanion[com.thesamet.docs.json.MyContainer] = this
def merge(`_message__`: com.thesamet.docs.json.MyContainer, `_input__`: _root_.com.google.protobuf.CodedInputStream): com.thesamet.docs.json.MyContainer = {
var __myAny = `_message__`.myAny
var `_unknownFields__`: _root_.scalapb.UnknownFieldSet.Builder = null
var _done__ = false
while (!_done__) {
val _tag__ = _input__.readTag()
_tag__ match {
case 0 => _done__ = true
case 10 =>
__myAny = Option(_root_.scalapb.LiteParser.readMessage(_input__, __myAny.getOrElse(com.google.protobuf.any.Any.defaultInstance)))
case tag =>
if (_unknownFields__ == null) {
_unknownFields__ = new _root_.scalapb.UnknownFieldSet.Builder(_message__.unknownFields)
}
_unknownFields__.parseField(tag, _input__)
}
}
com.thesamet.docs.json.MyContainer(
myAny = __myAny,
unknownFields = if (_unknownFields__ == null) _message__.unknownFields else _unknownFields__.result()
)
}
def merge(`_message__`: com.thesamet.docs.json.MyContainer, `_input__`: _root_.com.google.protobuf.CodedInputStream): com.thesamet.docs.json.MyContainer = newBuilder(_message__).merge(_input__).result()
implicit def messageReads: _root_.scalapb.descriptors.Reads[com.thesamet.docs.json.MyContainer] = _root_.scalapb.descriptors.Reads{
case _root_.scalapb.descriptors.PMessage(__fieldsMap) =>
_root_.scala.Predef.require(__fieldsMap.keys.forall(_.containingMessage == scalaDescriptor), "FieldDescriptor does not match message type.")
Expand All @@ -104,6 +83,46 @@ object MyContainer extends scalapb.GeneratedMessageCompanion[com.thesamet.docs.j
lazy val defaultInstance = com.thesamet.docs.json.MyContainer(
myAny = _root_.scala.None
)
final class Builder private (
private var __myAny: _root_.scala.Option[com.google.protobuf.any.Any],
private var `_unknownFields__`: _root_.scalapb.UnknownFieldSet.Builder
) extends _root_.scalapb.MessageBuilder[com.thesamet.docs.json.MyContainer] {
def merge(`_input__`: _root_.com.google.protobuf.CodedInputStream): this.type = {
var _done__ = false
while (!_done__) {
val _tag__ = _input__.readTag()
_tag__ match {
case 0 => _done__ = true
case 10 =>
__myAny = Option(_root_.scalapb.LiteParser.readMessage(_input__, __myAny.getOrElse(com.google.protobuf.any.Any.defaultInstance)))
case tag =>
if (_unknownFields__ == null) {
_unknownFields__ = new _root_.scalapb.UnknownFieldSet.Builder()
}
_unknownFields__.parseField(tag, _input__)
}
}
this
}
def result(): com.thesamet.docs.json.MyContainer = {
com.thesamet.docs.json.MyContainer(
myAny = __myAny,
unknownFields = if (_unknownFields__ == null) _root_.scalapb.UnknownFieldSet.empty else _unknownFields__.result()
)
}
}
object Builder extends _root_.scalapb.MessageBuilderCompanion[com.thesamet.docs.json.MyContainer, com.thesamet.docs.json.MyContainer.Builder] {
def apply(): Builder = new Builder(
__myAny = _root_.scala.None,
`_unknownFields__` = null
)
def apply(`_message__`: com.thesamet.docs.json.MyContainer): Builder = new Builder(
__myAny = _message__.myAny,
`_unknownFields__` = new _root_.scalapb.UnknownFieldSet.Builder(_message__.unknownFields)
)
}
def newBuilder: Builder = com.thesamet.docs.json.MyContainer.Builder()
def newBuilder(a: com.thesamet.docs.json.MyContainer): Builder = com.thesamet.docs.json.MyContainer.Builder(a)
implicit class MyContainerLens[UpperPB](_l: _root_.scalapb.lenses.Lens[UpperPB, com.thesamet.docs.json.MyContainer]) extends _root_.scalapb.lenses.ObjectLens[UpperPB, com.thesamet.docs.json.MyContainer](_l) {
def myAny: _root_.scalapb.lenses.Lens[UpperPB, com.google.protobuf.any.Any] = field(_.getMyAny)((c_, f_) => c_.copy(myAny = Option(f_)))
def optionalMyAny: _root_.scalapb.lenses.Lens[UpperPB, _root_.scala.Option[com.google.protobuf.any.Any]] = field(_.myAny)((c_, f_) => c_.copy(myAny = f_))
Expand Down
63 changes: 41 additions & 22 deletions docs/src/main/scala/com/thesamet/docs/json/MyMessage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,28 +64,7 @@ final case class MyMessage(

object MyMessage extends scalapb.GeneratedMessageCompanion[com.thesamet.docs.json.MyMessage] {
implicit def messageCompanion: scalapb.GeneratedMessageCompanion[com.thesamet.docs.json.MyMessage] = this
def merge(`_message__`: com.thesamet.docs.json.MyMessage, `_input__`: _root_.com.google.protobuf.CodedInputStream): com.thesamet.docs.json.MyMessage = {
var __x = `_message__`.x
var `_unknownFields__`: _root_.scalapb.UnknownFieldSet.Builder = null
var _done__ = false
while (!_done__) {
val _tag__ = _input__.readTag()
_tag__ match {
case 0 => _done__ = true
case 8 =>
__x = _input__.readInt32()
case tag =>
if (_unknownFields__ == null) {
_unknownFields__ = new _root_.scalapb.UnknownFieldSet.Builder(_message__.unknownFields)
}
_unknownFields__.parseField(tag, _input__)
}
}
com.thesamet.docs.json.MyMessage(
x = __x,
unknownFields = if (_unknownFields__ == null) _message__.unknownFields else _unknownFields__.result()
)
}
def merge(`_message__`: com.thesamet.docs.json.MyMessage, `_input__`: _root_.com.google.protobuf.CodedInputStream): com.thesamet.docs.json.MyMessage = newBuilder(_message__).merge(_input__).result()
implicit def messageReads: _root_.scalapb.descriptors.Reads[com.thesamet.docs.json.MyMessage] = _root_.scalapb.descriptors.Reads{
case _root_.scalapb.descriptors.PMessage(__fieldsMap) =>
_root_.scala.Predef.require(__fieldsMap.keys.forall(_.containingMessage == scalaDescriptor), "FieldDescriptor does not match message type.")
Expand All @@ -102,6 +81,46 @@ object MyMessage extends scalapb.GeneratedMessageCompanion[com.thesamet.docs.jso
lazy val defaultInstance = com.thesamet.docs.json.MyMessage(
x = 0
)
final class Builder private (
private var __x: _root_.scala.Int,
private var `_unknownFields__`: _root_.scalapb.UnknownFieldSet.Builder
) extends _root_.scalapb.MessageBuilder[com.thesamet.docs.json.MyMessage] {
def merge(`_input__`: _root_.com.google.protobuf.CodedInputStream): this.type = {
var _done__ = false
while (!_done__) {
val _tag__ = _input__.readTag()
_tag__ match {
case 0 => _done__ = true
case 8 =>
__x = _input__.readInt32()
case tag =>
if (_unknownFields__ == null) {
_unknownFields__ = new _root_.scalapb.UnknownFieldSet.Builder()
}
_unknownFields__.parseField(tag, _input__)
}
}
this
}
def result(): com.thesamet.docs.json.MyMessage = {
com.thesamet.docs.json.MyMessage(
x = __x,
unknownFields = if (_unknownFields__ == null) _root_.scalapb.UnknownFieldSet.empty else _unknownFields__.result()
)
}
}
object Builder extends _root_.scalapb.MessageBuilderCompanion[com.thesamet.docs.json.MyMessage, com.thesamet.docs.json.MyMessage.Builder] {
def apply(): Builder = new Builder(
__x = 0,
`_unknownFields__` = null
)
def apply(`_message__`: com.thesamet.docs.json.MyMessage): Builder = new Builder(
__x = _message__.x,
`_unknownFields__` = new _root_.scalapb.UnknownFieldSet.Builder(_message__.unknownFields)
)
}
def newBuilder: Builder = com.thesamet.docs.json.MyMessage.Builder()
def newBuilder(a: com.thesamet.docs.json.MyMessage): Builder = com.thesamet.docs.json.MyMessage.Builder(a)
implicit class MyMessageLens[UpperPB](_l: _root_.scalapb.lenses.Lens[UpperPB, com.thesamet.docs.json.MyMessage]) extends _root_.scalapb.lenses.ObjectLens[UpperPB, com.thesamet.docs.json.MyMessage](_l) {
def x: _root_.scalapb.lenses.Lens[UpperPB, _root_.scala.Int] = field(_.x)((c_, f_) => c_.copy(x = f_))
}
Expand Down
Loading

0 comments on commit c9e2ceb

Please sign in to comment.