scalapb.compiler.ProtobufGenerator.scala Maven / Gradle / Ivy
The newest version!
package scalapb.compiler
import com.google.protobuf.Descriptors._
import com.google.protobuf.{CodedOutputStream, DescriptorProtos, ByteString => GoogleByteString}
import com.google.protobuf.Descriptors.FieldDescriptor.Type
import scalapb.compiler.FunctionalPrinter.PrinterEndo
import scalapb.options.Scalapb
import scalapb.options.Scalapb.FieldOptions
import scala.jdk.CollectionConverters._
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse
import protocgen.CodeGenRequest
import protocgen.CodeGenResponse
// Exceptions that are caught and passed upstreams as errors.
case class GeneratorException(message: String) extends Exception(message)
class ProtobufGenerator(
params: GeneratorParams,
implicits: DescriptorImplicits
) {
import implicits._
import DescriptorImplicits.AsSymbolExtension
import ProtobufGenerator._
def printEnum(printer: FunctionalPrinter, e: EnumDescriptor): FunctionalPrinter = {
val name = e.scalaType.nameSymbol
val valuesByNumber = e.getValues().asScala.groupBy(_.getNumber())
printer
.when(e.getOptions.getDeprecated) {
_.add(ProtobufGenerator.deprecatedAnnotation)
}
.call(generateScalaDoc(e))
.seq(e.baseAnnotationList)
.add(
s"sealed abstract class $name(val value: _root_.scala.Int) extends ${e.baseTraitExtends.mkString(" with ")} {"
)
.indent
.add(s"type EnumType = ${e.scalaType.fullName}")
.add(s"type RecognizedType = ${e.recognizedEnum.fullName}")
.print(e.getValues.asScala) { case (p, v) =>
p.add(s"def ${v.isName}: _root_.scala.Boolean = false")
}
.add(s"def companion: _root_.scalapb.GeneratedEnumCompanion[$name] = ${e.scalaType.fullName}")
.add(
s"final def asRecognized: _root_.scala.Option[${e.recognizedEnum.fullName}] = if (isUnrecognized) _root_.scala.None else _root_.scala.Some(this.asInstanceOf[${e.recognizedEnum.fullName}])"
)
.outdent
.add("}")
.add("")
.when(e.getOptions.getDeprecated) {
_.add(ProtobufGenerator.deprecatedAnnotation)
}
.add(s"object $name extends ${e.companionExtends.mkString(" with ")} {")
.indent
.seq(e.recognizedAnnotationList)
.add(s"sealed trait ${e.recognizedEnum.nameSymbol} extends $name")
.add(s"implicit def enumCompanion: _root_.scalapb.GeneratedEnumCompanion[$name] = this")
.newline
.print(e.getValues().asScala) { case (p, v) =>
val firstVal = valuesByNumber(v.getNumber()).head
if (firstVal == v)
p.call(generateScalaDoc(v))
.add("@SerialVersionUID(0L)")
.seq(v.annotationList)
.add(s"""case object ${v.scalaName.asSymbol} extends ${v.valueExtends.mkString(
" with "
)} {""")
.indented(
_.add(s"val index = ${v.getIndex}")
.add(s"""val name = "${v.getName}"""")
.print(valuesByNumber(v.getNumber)) { case (p, u) =>
p.add(s"override def ${u.isName}: _root_.scala.Boolean = true")
}
)
.add("}")
.add("")
else
p.call(generateScalaDoc(v))
.seq(v.annotationList)
.add(s"@transient val ${v.scalaName.asSymbol} = ${firstVal.scalaName.asSymbol}")
.add("")
}
.add("@SerialVersionUID(0L)")
.seq(e.unrecognizedAnnotationList)
.add(
s"""final case class Unrecognized(unrecognizedValue: _root_.scala.Int) extends $name(unrecognizedValue) with _root_.scalapb.UnrecognizedEnum
|lazy val values: scala.collection.immutable.Seq[ValueType] = scala.collection.immutable.Seq(${e.getValues.asScala
.map(_.scalaName.asSymbol)
.mkString(", ")})
|def fromValue(__value: _root_.scala.Int): $name = __value match {""".stripMargin
)
.print(e.valuesWithNoDuplicates) { case (p, v) =>
p.add(s" case ${v.getNumber} => ${v.scalaName.asSymbol}")
}
.add(
s""" case __other => Unrecognized(__other)
|}
|def javaDescriptor: _root_.com.google.protobuf.Descriptors.EnumDescriptor = ${e.javaDescriptorSource}
|def scalaDescriptor: _root_.scalapb.descriptors.EnumDescriptor = ${e.scalaDescriptorSource}""".stripMargin
)
.when(e.javaConversions) {
_.add(
s"""|def fromJavaValue(pbJavaSource: ${e.javaTypeName}): $name = fromValue(pbJavaSource.getNumber)
|def toJavaValue(pbScalaSource: $name): ${e.javaTypeName} = {
| _root_.scala.Predef.require(!pbScalaSource.isUnrecognized, "Unrecognized enum values can not be converted to Java")
| ${e.javaTypeName}.forNumber(pbScalaSource.value)
|}""".stripMargin
)
}
.outdent
.add("}")
}
def printOneof(printer: FunctionalPrinter, e: OneofDescriptor): FunctionalPrinter = {
printer
.add(
s"sealed abstract class ${e.scalaType.nameSymbol} extends ${e.baseClasses.mkString(" with ")} {"
)
.indent
.add(s"def isEmpty: _root_.scala.Boolean = false")
.add(s"def isDefined: _root_.scala.Boolean = true")
.print(e.fields) { case (p, v) =>
p.add(s"def is${v.upperScalaName}: _root_.scala.Boolean = false")
}
.print(e.fields) { case (p, v) =>
p.add(s"def ${v.scalaName.asSymbol}: _root_.scala.Option[${v.scalaTypeName}] = ${C.None}")
}
.outdent
.add(s"""}
|object ${e.scalaType.nameSymbol} {
| @SerialVersionUID(0L)
| case object Empty extends ${e.scalaType.fullName} {
| type ValueType = _root_.scala.Nothing
| override def isEmpty: _root_.scala.Boolean = true
| override def isDefined: _root_.scala.Boolean = false
| override def number: _root_.scala.Int = 0
| override def value: _root_.scala.Nothing = throw new java.util.NoSuchElementException("Empty.value")
| }
|""".stripMargin)
.indent
.print(e.fields) { case (p, v) =>
p.add(
s"""@SerialVersionUID(0L)${if (v.getOptions.getDeprecated) {
" " + ProtobufGenerator.deprecatedAnnotation
} else ""}
|final case class ${v.upperScalaName}(value: ${v.scalaTypeName}) extends ${e.scalaType.fullName} {
| type ValueType = ${v.scalaTypeName}
| override def is${v.upperScalaName}: _root_.scala.Boolean = true
| override def ${v.scalaName.asSymbol}: _root_.scala.Option[${v.scalaTypeName}] = Some(value)
| override def number: _root_.scala.Int = ${v.getNumber}
|}""".stripMargin
)
}
.outdent
.add("}")
}
def defaultValueForGet(field: FieldDescriptor, uncustomized: Boolean = false) = {
// Needs to be 'def' and not val since for some of the cases it's invalid to call it.
def defaultValue = field.getDefaultValue
val baseDefaultValue: String = field.getJavaType match {
case FieldDescriptor.JavaType.INT => defaultValue.toString
case FieldDescriptor.JavaType.LONG => defaultValue.toString + "L"
case FieldDescriptor.JavaType.FLOAT =>
val f = defaultValue.asInstanceOf[Float]
if (f.isPosInfinity) "Float.PositiveInfinity"
else if (f.isNegInfinity) "Float.NegativeInfinity"
else if (f.isNaN) "Float.NaN"
else f.toString + "f"
case FieldDescriptor.JavaType.DOUBLE =>
val d = defaultValue.asInstanceOf[Double]
if (d.isPosInfinity) "Double.PositiveInfinity"
else if (d.isNegInfinity) "Double.NegativeInfinity"
else if (d.isNaN) "Double.NaN"
else d.toString
case FieldDescriptor.JavaType.BOOLEAN =>
Boolean.unbox(defaultValue.asInstanceOf[java.lang.Boolean]).toString
case FieldDescriptor.JavaType.BYTE_STRING =>
val d = defaultValue.asInstanceOf[GoogleByteString]
if (d.isEmpty)
"_root_.com.google.protobuf.ByteString.EMPTY"
else
d.asScala
.map(_.toString)
.mkString("_root_.com.google.protobuf.ByteString.copyFrom(Array[Byte](", ", ", "))")
case FieldDescriptor.JavaType.STRING => escapeScalaString(defaultValue.asInstanceOf[String])
case FieldDescriptor.JavaType.MESSAGE =>
field.getMessageType.scalaType
.fullNameWithMaybeRoot(field.getContainingType) + ".defaultInstance"
case FieldDescriptor.JavaType.ENUM =>
field.getEnumType.scalaType
.fullNameWithMaybeRoot(field.getContainingType) + "." + defaultValue
.asInstanceOf[EnumValueDescriptor]
.scalaName
.asSymbol
}
if (!uncustomized && field.customSingleScalaTypeName.isDefined)
s"${field.typeMapper.fullName}.toCustom($baseDefaultValue)"
else baseDefaultValue
}
def defaultValueForDefaultInstance(field: FieldDescriptor) =
if (field.supportsPresence) C.None
else if (field.isRepeated) field.collection.empty
else defaultValueForGet(field)
def javaToScalaConversion(field: FieldDescriptor) = {
val baseValueConversion = field.getJavaType match {
case FieldDescriptor.JavaType.INT => MethodApplication("intValue")
case FieldDescriptor.JavaType.LONG => MethodApplication("longValue")
case FieldDescriptor.JavaType.FLOAT => MethodApplication("floatValue")
case FieldDescriptor.JavaType.DOUBLE => MethodApplication("doubleValue")
case FieldDescriptor.JavaType.BOOLEAN => MethodApplication("booleanValue")
case FieldDescriptor.JavaType.BYTE_STRING => Identity
case FieldDescriptor.JavaType.STRING => Identity
case FieldDescriptor.JavaType.MESSAGE =>
FunctionApplication(field.getMessageType.scalaType.fullName + ".fromJavaProto")
case FieldDescriptor.JavaType.ENUM =>
if (!field.legacyEnumFieldTreatedAsClosed())
MethodApplication("intValue") andThen FunctionApplication(
field.getEnumType.scalaType.fullName + ".fromValue"
)
else FunctionApplication(field.getEnumType.scalaType.fullName + ".fromJavaValue")
}
baseValueConversion andThen toCustomTypeExpr(field)
}
def javaFieldToScala(javaHazzer: String, javaGetter: String, field: FieldDescriptor): String = {
val valueConversion: Expression = javaToScalaConversion(field)
if (field.supportsPresence)
s"if ($javaHazzer) Some(${valueConversion.apply(javaGetter, EnclosingType.None)}) else ${C.None}"
else if (field.isRepeated)
valueConversion(
javaGetter + ".asScala",
sourceType = EnclosingType.Collection(DescriptorImplicits.ScalaSeq, None),
targetType = field.enclosingType,
mustCopy = true
)
else valueConversion(javaGetter, EnclosingType.None)
}
def javaFieldToScala(container: String, field: FieldDescriptor): String = {
val javaHazzer = container + ".has" + field.upperJavaName
val upperJavaName =
if (field.isEnum && !field.legacyEnumFieldTreatedAsClosed()) (field.upperJavaName + "Value")
else field.upperJavaName
val javaGetter =
if (field.isRepeated)
container + ".get" + upperJavaName + "List"
else
container + ".get" + upperJavaName
javaFieldToScala(javaHazzer, javaGetter, field)
}
def javaMapFieldToScala(container: String, field: FieldDescriptor): String = {
// TODO(thesamet): if both unit conversions are NoOp, we can omit the map call.
def unitConversion(n: String, field: FieldDescriptor) =
javaToScalaConversion(field).apply(n, EnclosingType.None)
val upperJavaName =
if (
field.mapType.valueField.isEnum && !field.mapType.valueField.legacyEnumFieldTreatedAsClosed
)
(field.upperJavaName + "Value")
else field.upperJavaName
ExpressionBuilder.convertCollection(
s"${container}.get${upperJavaName}Map.asScala.iterator.map(__pv => (${unitConversion(
"__pv._1",
field.mapType.keyField
)}, ${unitConversion("__pv._2", field.mapType.valueField)}))",
field.enclosingType
)
}
def scalaToJava(field: FieldDescriptor, boxPrimitives: Boolean): Expression = {
def maybeBox(name: String) = if (boxPrimitives) FunctionApplication(name) else Identity
field.getJavaType match {
case FieldDescriptor.JavaType.INT => maybeBox("_root_.scala.Int.box")
case FieldDescriptor.JavaType.LONG => maybeBox("_root_.scala.Long.box")
case FieldDescriptor.JavaType.FLOAT => maybeBox("_root_.scala.Float.box")
case FieldDescriptor.JavaType.DOUBLE => maybeBox("_root_.scala.Double.box")
case FieldDescriptor.JavaType.BOOLEAN => maybeBox("_root_.scala.Boolean.box")
case FieldDescriptor.JavaType.BYTE_STRING => Identity
case FieldDescriptor.JavaType.STRING => Identity
case FieldDescriptor.JavaType.MESSAGE =>
FunctionApplication(field.getMessageType.scalaType.fullName + ".toJavaProto")
case FieldDescriptor.JavaType.ENUM =>
if (!field.legacyEnumFieldTreatedAsClosed())
(MethodApplication("value") andThen maybeBox("_root_.scala.Int.box"))
else
FunctionApplication(field.getEnumType.scalaType.fullName + ".toJavaValue")
}
}
def assignScalaMapToJava(
scalaObject: String,
javaObject: String,
field: FieldDescriptor
): String = {
def valueConvert(v: String, field: FieldDescriptor) =
(toBaseTypeExpr(field) andThen scalaToJava(field, boxPrimitives = true))
.apply(v, EnclosingType.None)
val putAll =
s"putAll${field.upperScalaName}" + (if (
field.mapType.valueField.isEnum && !field.mapType.valueField
.legacyEnumFieldTreatedAsClosed()
)
"Value"
else "")
s"""$javaObject
| .$putAll(${field.collection.iterator(
scalaObject + "." + field.scalaName.asSymbol,
EnclosingType.None
)}.map {
| __kv => (${valueConvert("__kv._1", field.mapType.keyField)}, ${valueConvert(
"__kv._2",
field.mapType.valueField
)})
| }.toMap.asJava)""".stripMargin
}
def assignScalaFieldToJava(
scalaObject: String,
javaObject: String,
field: FieldDescriptor
): String =
if (field.isMapField) assignScalaMapToJava(scalaObject, javaObject, field)
else {
val javaSetter = javaObject +
(if (field.isRepeated) ".addAll"
else
".set") + field.upperJavaName + (if (
field.isEnum && !field
.legacyEnumFieldTreatedAsClosed()
) "Value"
else "")
val scalaGetter = scalaObject + "." + fieldAccessorSymbol(field)
val scalaExpr =
(toBaseTypeExpr(field) andThen scalaToJava(field, boxPrimitives = field.isRepeated))
.apply(
scalaGetter,
sourceType = field.enclosingType,
targetType = field.enclosingType match {
case _: EnclosingType.Collection =>
EnclosingType.Collection(DescriptorImplicits.ScalaIterable, None)
case o => o
}
)
if (field.supportsPresence || field.isInOneof)
s"$scalaExpr.foreach($javaSetter)"
else if (field.isRepeated)
s"$javaSetter($scalaExpr.asJava)"
else
s"$javaSetter($scalaExpr)"
}
def generateGetField(message: Descriptor)(fp: FunctionalPrinter) = {
val signature = "def getFieldByNumber(__fieldNumber: _root_.scala.Int): _root_.scala.Any = "
if (message.fields.nonEmpty)
fp.add(signature + "{")
.indent
.add("(__fieldNumber: @_root_.scala.unchecked) match {")
.indent
.print(message.fields) { case (fp, f) =>
val e = toBaseFieldType(f)
.apply(
fieldAccessorSymbol(f),
sourceType = f.enclosingType,
targetType = f.fieldMapEnclosingType
)
if (f.supportsPresence || f.isInOneof)
fp.add(s"case ${f.getNumber} => $e.orNull")
else if (f.isOptional && !f.noBoxRequired) {
// In proto3, drop default value
fp.add(s"case ${f.getNumber} => {")
.indent
.add(s"val __t = $e")
.add({
val cond =
if (!f.isEnum)
s"__t != ${defaultValueForGet(f, uncustomized = true)}"
else
s"__t.getNumber() != 0"
s"if ($cond) __t else null"
})
.outdent
.add("}")
} else fp.add(s"case ${f.getNumber} => $e")
}
.outdent
.add("}")
.outdent
.add("}")
else fp.add(signature + "throw new MatchError(__fieldNumber)")
}
def singleFieldAsPvalue(fd: FieldDescriptor): LiteralExpression = {
val d = "_root_.scalapb.descriptors"
fd.getJavaType match {
case FieldDescriptor.JavaType.INT => FunctionApplication(s"$d.PInt")
case FieldDescriptor.JavaType.LONG => FunctionApplication(s"$d.PLong")
case FieldDescriptor.JavaType.FLOAT => FunctionApplication(s"$d.PFloat")
case FieldDescriptor.JavaType.DOUBLE => FunctionApplication(s"$d.PDouble")
case FieldDescriptor.JavaType.BOOLEAN => FunctionApplication(s"$d.PBoolean")
case FieldDescriptor.JavaType.BYTE_STRING => FunctionApplication(s"$d.PByteString")
case FieldDescriptor.JavaType.STRING => FunctionApplication(s"$d.PString")
case FieldDescriptor.JavaType.ENUM => FunctionApplication(s"$d.PEnum")
case FieldDescriptor.JavaType.MESSAGE => MethodApplication("toPMessage")
}
}
def generateGetFieldPValue(message: Descriptor)(fp: FunctionalPrinter) = {
val signature =
"def getField(__field: _root_.scalapb.descriptors.FieldDescriptor): _root_.scalapb.descriptors.PValue = "
if (message.fields.nonEmpty)
fp.add(signature + "{")
.indent
.add("_root_.scala.Predef.require(__field.containingMessage eq companion.scalaDescriptor)")
.add("(__field.number: @_root_.scala.unchecked) match {")
.indent
.print(message.fields) { case (fp, f) =>
val e = toBaseFieldTypeWithScalaDescriptors(f)
.andThen(singleFieldAsPvalue(f))
.apply(
fieldAccessorSymbol(f),
sourceType = f.enclosingType,
targetType = f.enclosingType match {
case _: EnclosingType.Collection =>
EnclosingType.Collection(DescriptorImplicits.ScalaVector, None)
case other => other
}
)
if (f.supportsPresence || f.isInOneof) {
fp.add(s"case ${f.getNumber} => $e.getOrElse(_root_.scalapb.descriptors.PEmpty)")
} else if (f.isRepeated) {
fp.add(s"case ${f.getNumber} => _root_.scalapb.descriptors.PRepeated($e)")
} else {
fp.add(s"case ${f.getNumber} => $e")
}
}
.outdent
.add("}")
.outdent
.add("}")
else fp.add(signature + "throw new MatchError(__field)")
}
def generateWriteSingleValue(field: FieldDescriptor, valueExpr: String)(
fp: FunctionalPrinter
): FunctionalPrinter = {
if (field.isMessage) {
fp.add(s"""_output__.writeTag(${field.getNumber}, 2)
|_output__.writeUInt32NoTag($valueExpr.serializedSize)
|$valueExpr.writeTo(_output__)""".stripMargin)
} else {
val capTypeName = Types.capitalizedType(field.getType)
fp.add(s"_output__.write$capTypeName(${field.getNumber}, $valueExpr)")
}
}
def sizeExpressionForSingleField(field: FieldDescriptor, expr: String): String =
if (field.isMessage) {
val size = s"$expr.serializedSize"
CodedOutputStream
.computeTagSize(field.getNumber)
.toString + s" + _root_.com.google.protobuf.CodedOutputStream.computeUInt32SizeNoTag($size) + $size"
} else {
val capTypeName = Types.capitalizedType(field.getType)
s"_root_.com.google.protobuf.CodedOutputStream.compute${capTypeName}Size(${field.getNumber}, ${expr})"
}
def fieldAccessorSymbol(field: FieldDescriptor) =
if (field.isInOneof)
(field.getContainingOneof.scalaName.nameSymbol + "." + field.scalaName.asSymbol)
else
field.scalaName.asSymbol
def toBaseTypeExpr(field: FieldDescriptor) =
if (field.customSingleScalaTypeName.isDefined)
FunctionApplication(field.typeMapper.fullName + ".toBase")
else Identity
def toBaseFieldType(field: FieldDescriptor) =
if (field.isEnum)
(toBaseTypeExpr(field) andThen MethodApplication("javaValueDescriptor"))
else toBaseTypeExpr(field)
def toBaseFieldTypeWithScalaDescriptors(field: FieldDescriptor) =
if (field.isEnum)
(toBaseTypeExpr(field) andThen MethodApplication("scalaValueDescriptor"))
else toBaseTypeExpr(field)
def toBaseType(field: FieldDescriptor)(expr: String) = {
val suffix = if (field.isEnum) ".value" else ""
toBaseTypeExpr(field).apply(expr, EnclosingType.None) + suffix
}
def toCustomTypeExpr(field: FieldDescriptor) =
if (field.customSingleScalaTypeName.isEmpty) Identity
else FunctionApplication(s"${field.typeMapper.fullName}.toCustom")
def toCustomType(field: FieldDescriptor)(expr: String) =
toCustomTypeExpr(field).apply(expr, EnclosingType.None)
def generateSerializedSizeForField(
fp: FunctionalPrinter,
field: FieldDescriptor
): FunctionalPrinter = {
val fieldNameSymbol = fieldAccessorSymbol(field)
if (field.isRequired || field.noBoxRequired) {
fp.add(s"""
|{
| val __value = ${toBaseType(field)(fieldNameSymbol)}
| __size += ${sizeExpressionForSingleField(field, "__value")}
|};""".stripMargin)
} else if (field.isSingular) {
fp.add(
s"""
|{
| val __value = ${toBaseType(field)(fieldNameSymbol)}
| if (${isNonEmpty("__value", field)}) {
| __size += ${sizeExpressionForSingleField(field, "__value")}
| }
|};""".stripMargin
)
} else if (field.isOptional) {
fp.add(
s"""if ($fieldNameSymbol.isDefined) {
| val __value = ${toBaseType(field)(fieldNameSymbol + ".get")}
| __size += ${sizeExpressionForSingleField(field, "__value")}
|};""".stripMargin
)
} else if (field.isRepeated) {
val tagSize = CodedOutputStream.computeTagSize(field.getNumber)
if (!field.isPacked) {
Types.fixedSize(field.getType) match {
case Some(size) =>
fp.add(
s"__size += ${size + tagSize} * ${field.collection.size(field.scalaName.asSymbol, EnclosingType.None)}"
)
case None =>
fp.add(s"""${field.collection.foreach} { __item =>
| val __value = ${toBaseType(field)("__item")}
| __size += ${sizeExpressionForSingleField(field, "__value")}
|}""".stripMargin)
}
} else {
val fieldName = field.scalaName
fp.add(
s"""if (${field.collection.nonEmptyCheck(fieldNameSymbol)}) {
| val __localsize = ${fieldName}SerializedSize
| __size += $tagSize + _root_.com.google.protobuf.CodedOutputStream.computeUInt32SizeNoTag(__localsize) + __localsize
|}""".stripMargin
)
}
} else throw new RuntimeException("Should not reach here.")
}
def generateSerializedSize(message: Descriptor)(fp: FunctionalPrinter) = {
if (message.fields.nonEmpty || message.preservesUnknownFields) {
fp.when(!message.isValueClass) {
_.add(
s"""@transient
|${message.V.PrivateThis} var __serializedSizeMemoized: _root_.scala.Int = 0""".stripMargin
)
}.add(s"${message.V.PrivateThis} def __computeSerializedSize(): _root_.scala.Int = {")
.indent
.add("var __size = 0")
.print(message.fields)(generateSerializedSizeForField)
.when(message.preservesUnknownFields)(_.add("__size += unknownFields.serializedSize"))
.add("__size")
.outdent
.add("}")
.add("override def serializedSize: _root_.scala.Int = {")
.indent
.when(message.isValueClass) {
_.add("__computeSerializedSize()")
}
.when(!message.isValueClass) {
// Since zero is a valid value, we actually store the serialized
// size plus one. Zero means that the memoized value was not initialized.
_.add("""var __size = __serializedSizeMemoized
|if (__size == 0) {
| __size = __computeSerializedSize() + 1
| __serializedSizeMemoized = __size
|}
|__size - 1
|""".stripMargin)
}
.outdent
.add("}")
} else {
fp.add("final override def serializedSize: _root_.scala.Int = 0")
}
}
def generateSerializedSizeForPackedFields(message: Descriptor)(fp: FunctionalPrinter) =
fp.print(message.fields.filter(_.isPacked)) { case (printer, field) =>
val methodName = s"${field.scalaName}SerializedSize"
printer
.add(s"${message.V.PrivateThis} def $methodName = {") // closing brace is in each case
.call({ fp =>
Types.fixedSize(field.getType) match {
case Some(size) =>
fp.add(
s" $size * ${field.collection.size(field.scalaName.asSymbol, EnclosingType.None)}"
).add("}")
case None =>
val capTypeName = Types.capitalizedType(field.getType)
val sizeFunc = FunctionApplication(
s"_root_.com.google.protobuf.CodedOutputStream.compute${capTypeName}SizeNoTag"
)
val fromEnum = if (field.isEnum) MethodApplication("value") else Identity
val fromCustom =
if (field.customSingleScalaTypeName.isDefined)
FunctionApplication(s"${field.typeMapper.fullName}.toBase")
else Identity
val funcs = List(sizeFunc, fromEnum, fromCustom)
val sizeExpr = ExpressionBuilder.runSingleton(funcs)("__i")
fp.indent
.add(s"if (__${methodName}Field == 0) __${methodName}Field = {")
.add(s" var __s: _root_.scala.Int = 0")
.add(s" ${field.collection.foreach}(__i => __s += $sizeExpr)")
.add(s" __s")
.add(s"}")
.add(s"__${methodName}Field")
.outdent
.add("}") // closing brace for the method
.add(
s"@transient ${message.V.PrivateThis} var __${methodName}Field: _root_.scala.Int = 0"
)
}
})
}
private def composeGen(funcs: Seq[String], emitScala3Sources: Boolean) = {
if (funcs.length == 1) {
funcs(0)
} else {
if (emitScala3Sources) {
s"(${funcs(0)})" + funcs.tail.map(func => s".compose($func)").mkString
} else {
s"(${funcs(0)} _)" + funcs.tail.map(func => s".compose($func)").mkString
}
}
}
private def isNonEmpty(expr: String, field: FieldDescriptor): String = {
if (field.getType == Type.BYTES | field.getType == Type.STRING) s"!${expr}.isEmpty"
else if (field.getType == Type.ENUM) s"${expr} != 0"
else if (field.getType == Type.MESSAGE) s"${expr}.serializedSize != 0"
else s"${expr} != ${defaultValueForGet(field, uncustomized = true)}"
}
def generateWriteTo(message: Descriptor)(fp: FunctionalPrinter) =
fp.add(
s"def writeTo(`_output__`: _root_.com.google.protobuf.CodedOutputStream): _root_.scala.Unit = {"
).indent
.print(message.fields.sortBy(_.getNumber)) { case (printer, field) =>
val fieldNameSymbol = fieldAccessorSymbol(field)
val capTypeName = Types.capitalizedType(field.getType)
if (field.isPacked) {
val writeFunc = composeGen(
Seq(s"_output__.write${capTypeName}NoTag") ++ (
if (field.isEnum) Seq(s"(_: ${field.baseSingleScalaTypeName}).value") else Nil
) ++ (
if (field.customSingleScalaTypeName.isDefined)
Seq(s"${field.typeMapper.fullName}.toBase")
else Nil
),
message.getFile.emitScala3Sources
)
printer.add(s"""if (${field.collection.nonEmptyCheck(fieldNameSymbol)}) {
| _output__.writeTag(${field.getNumber}, 2)
| _output__.writeUInt32NoTag(${field.scalaName}SerializedSize)
| ${field.collection.foreach}($writeFunc)
|};""".stripMargin)
} else if (field.isRequired || field.noBoxRequired) {
printer
.add("")
.add("{")
.indent
.add(s"val __v = ${toBaseType(field)(fieldNameSymbol)}")
.call(generateWriteSingleValue(field, "__v"))
.outdent
.add("};")
} else if (field.isSingular) {
// Singular that are not required are written only if they don't equal their default
// value.
printer
.add(s"{")
.indent
.add(s"val __v = ${toBaseType(field)(fieldNameSymbol)}")
.add(s"if (${isNonEmpty("__v", field)}) {")
.indent
.call(generateWriteSingleValue(field, "__v"))
.outdent
.add("}")
.outdent
.add("};")
} else {
printer
.when(field.isRepeated)(_.add(s"${field.collection.foreach} { __v =>"))
.when(!field.isRepeated)(_.add(s"${fieldAccessorSymbol(field)}.foreach { __v =>"))
.indent
.add(s"val __m = ${toBaseType(field)("__v")}")
.call(generateWriteSingleValue(field, "__m"))
.outdent
.add("};")
}
}
.when(message.preservesUnknownFields)(_.add("unknownFields.writeTo(_output__)"))
.outdent
.add("}")
def constructorFields(message: Descriptor): Seq[ConstructorField] = {
def annotations(field: FieldDescriptor) =
if (field.annotationList.nonEmpty) field.annotationList else Nil
val regularFields = message.fields.collect {
case field if !field.isInOneof =>
val typeName = field.scalaTypeName
val ctorDefaultValue: Option[String] =
if (field.noDefaultValueInConstructor) None
else if (field.isOptional && field.supportsPresence) Some(C.None)
else if (field.isSingular && !field.isRequired && !field.noBoxRequired)
Some(defaultValueForGet(field).toString)
else if (field.isRepeated && !field.collection.nonEmptyType)
Some(s"${field.collection.empty}")
else None
ConstructorField(
name = field.scalaName.asSymbol,
typeName = typeName,
default = ctorDefaultValue,
index = field.getIndex,
annotations = annotations(field)
)
}
val oneOfFields = message.getRealOneofs.asScala.map { oneOf =>
val ctorDefaultValue: Option[String] =
if (message.getFile.noDefaultValuesInConstructor) None
else Some(oneOf.empty.fullNameWithMaybeRoot(message))
ConstructorField(
name = oneOf.scalaName.nameSymbol,
typeName = oneOf.scalaType.fullNameWithMaybeRoot(message),
default = ctorDefaultValue,
index = oneOf.getField(0).getIndex
)
}
val maybeUnknownFields =
if (message.preservesUnknownFields) {
Seq(ConstructorField.unknownFields(message.unknownFieldsAnnotations))
} else Seq()
(regularFields ++ oneOfFields ++ maybeUnknownFields).sortBy(_.index)
}
def printConstructorFieldList(
message: Descriptor
)(printer: FunctionalPrinter): FunctionalPrinter = {
printer.addWithDelimiter(",")(constructorFields(message).map(_.fullString))
}
def generateToJavaProto(message: Descriptor)(printer: FunctionalPrinter): FunctionalPrinter = {
val myFullScalaName = message.scalaType.fullName
printer
.add(s"def toJavaProto(scalaPbSource: $myFullScalaName): ${message.javaTypeName} = {")
.indent
.add(s"val javaPbOut = ${message.javaTypeName}.newBuilder")
.print(message.fields) { case (printer, field) =>
printer.add(assignScalaFieldToJava("scalaPbSource", "javaPbOut", field))
}
.add("javaPbOut.build")
.outdent
.add("}")
}
def generateFromJavaProto(message: Descriptor)(printer: FunctionalPrinter): FunctionalPrinter = {
val myFullScalaName = message.scalaType.fullName
printer
.add(
s"def fromJavaProto(javaPbSource: ${message.javaTypeName}): $myFullScalaName = $myFullScalaName("
)
.indent
.call { printer =>
val normal = message.fields.collect {
case field if !field.isInOneof =>
val conversion =
if (field.isMapField) javaMapFieldToScala("javaPbSource", field)
else javaFieldToScala("javaPbSource", field)
Seq(s"${field.scalaName.asSymbol} = $conversion")
}
val oneOfs = message.getRealOneofs.asScala.map { case oneOf =>
val head =
s"${oneOf.scalaName.nameSymbol} = javaPbSource.${oneOf.javaEnumName}.getNumber match {"
val body = oneOf.fields.map { field =>
s" case ${field.getNumber} => ${field.oneOfTypeName.fullName}(${javaFieldToScala("javaPbSource", field)})"
}
val tail = Seq(s" case _ => ${oneOf.empty.fullName}", "}")
Seq(head) ++ body ++ tail
}
printer.addGroupsWithDelimiter(",")(normal ++ oneOfs)
}
.outdent
.add(")")
}
def generateNoDefaultArgsFactory(
message: Descriptor
)(printer: FunctionalPrinter): FunctionalPrinter = {
val fields = constructorFields(message).filterNot(_.isUnknownFields)
printer
.add(
s"def of("
)
.indented(
_.addWithDelimiter(",")(fields.map(_.nameAndType))
)
.add(
s"): ${message.scalaType.fullNameWithMaybeRoot} = ${message.scalaType.fullNameWithMaybeRoot}("
)
.indented(
_.addWithDelimiter(",")(fields.map(_.name))
)
.add(")")
}
def generateMessageReads(message: Descriptor)(printer: FunctionalPrinter): FunctionalPrinter = {
def transform(field: FieldDescriptor) =
(if (!field.isEnum) Identity
else
(MethodApplication("number") andThen
FunctionApplication(field.getEnumType.scalaType.fullName + ".fromValue"))) andThen
toCustomTypeExpr(field)
val myFullScalaName = message.scalaType.fullName
printer
.add(s"""implicit def messageReads: _root_.scalapb.descriptors.Reads[${myFullScalaName}] = _root_.scalapb.descriptors.Reads{
| case _root_.scalapb.descriptors.PMessage(__fieldsMap) =>
| _root_.scala.Predef.require(__fieldsMap.keys.forall(_.containingMessage eq scalaDescriptor), \"FieldDescriptor does not match message type.\")
| ${myFullScalaName}(""".stripMargin)
.indent(3)
.call { printer =>
val fields = message.fields.collect {
case field if !field.isInOneof =>
val readsEnclosing =
if (field.supportsPresence) EnclosingType.ScalaOption
else if (field.isRepeated && field.isMapField)
EnclosingType.Collection(DescriptorImplicits.ScalaSeq, None)
else if (field.isRepeated && !field.isMapField) field.collection.adapter match {
case Some(_) => EnclosingType.Collection(DescriptorImplicits.ScalaIterator, None)
case None => EnclosingType.Collection(field.collectionType, None)
}
else EnclosingType.None
val baseTypeName = readsEnclosing.asType(
if (field.isEnum) "_root_.scalapb.descriptors.EnumValueDescriptor"
else field.baseSingleScalaTypeName
)
val value =
s"__fieldsMap.get(scalaDescriptor.findFieldByNumber(${field.getNumber}).get)"
val e =
if (field.supportsPresence)
s"$value.flatMap(_.as[$baseTypeName])"
else if (field.isRepeated) {
val empty = readsEnclosing match {
case EnclosingType.Collection(s, _) => s"$s.empty"
case _ =>
throw new GeneratorException(
"Expected a collection enclosing. Pleae report this as a bug."
)
}
s"$value.map(_.as[${baseTypeName}]).getOrElse($empty)"
} else if (field.isRequired || field.noBoxRequired)
s"$value.get.as[$baseTypeName]"
else {
// This is for proto3, no default value.
val t = defaultValueForGet(field, uncustomized = true) + (if (field.isEnum)
".scalaValueDescriptor"
else "")
s"$value.map(_.as[$baseTypeName]).getOrElse($t)"
}
val itemTypeTranform = transform(field)(
e,
sourceType = readsEnclosing,
targetType = if (field.isMapField) field.enclosingType else readsEnclosing
)
val expr = field.collection.adapter match {
case Some(tc) if (!field.isMapField()) =>
s"${tc.fullName}.fromIteratorUnsafe($itemTypeTranform)"
case _ => itemTypeTranform
}
s"${field.scalaName.asSymbol} = $expr"
}
val oneOfs = message.getRealOneofs.asScala.map { oneOf =>
val elems = oneOf.fields.map { field =>
val value =
s"__fieldsMap.get(scalaDescriptor.findFieldByNumber(${field.getNumber}).get)"
val typeName =
if (field.isEnum) "_root_.scalapb.descriptors.EnumValueDescriptor"
else field.baseSingleScalaTypeName
val e = s"$value.flatMap(_.as[_root_.scala.Option[$typeName]])"
(transform(field) andThen FunctionApplication(field.oneOfTypeName.fullName)).apply(
e,
EnclosingType.ScalaOption
)
}
val expr =
elems.reduceLeft((acc, field) =>
s"$acc\n .orElse[${oneOf.scalaType.fullName}]($field)"
)
s"${oneOf.scalaName.nameSymbol} = $expr\n .getOrElse(${oneOf.empty.fullName})"
}
printer.addWithDelimiter(",")(fields ++ oneOfs)
}
.outdent(3)
.add(s""" )
| case _ => throw new RuntimeException(\"Expected PMessage\")
|}""".stripMargin)
}
def generateDescriptors(message: Descriptor)(printer: FunctionalPrinter): FunctionalPrinter = {
printer
.add(
s"""def javaDescriptor: _root_.com.google.protobuf.Descriptors.Descriptor = ${message.javaDescriptorSource}
|def scalaDescriptor: _root_.scalapb.descriptors.Descriptor = ${message.scalaDescriptorSource}""".stripMargin
)
}
def generateDefaultInstance(
message: Descriptor
)(printer: FunctionalPrinter): FunctionalPrinter = {
val myFullScalaName = message.scalaType.fullName
printer
.add(s"lazy val defaultInstance = $myFullScalaName(")
.indent
.addWithDelimiter(",")(message.fields.collect {
case field if !field.isInOneof =>
val default = defaultValueForDefaultInstance(field)
s"${field.scalaName.asSymbol} = $default"
} ++ message.getRealOneofs.asScala.map { oneof =>
s"${oneof.scalaName.nameSymbol} = ${oneof.empty.fullName}"
})
.outdent
.add(")")
}
def generateMessageLens(message: Descriptor)(printer: FunctionalPrinter): FunctionalPrinter = {
val className = message.scalaType.name
def lensType(s: String) = s"_root_.scalapb.lenses.Lens[UpperPB, $s]"
printer
.add(
s"implicit class ${className}Lens[UpperPB](_l: _root_.scalapb.lenses.Lens[UpperPB, ${message.scalaType.fullName}]) extends ${message.V.MessageLens}[UpperPB, ${message.scalaType.fullName}](_l) {"
)
.indent
.print(message.fields) { case (printer, field) =>
val fieldName = field.scalaName.asSymbol
if (!field.isInOneof) {
if (field.supportsPresence) {
val optionLensName = "optional" + field.upperScalaName
printer
.add(
s"""def $fieldName: ${lensType(
field.singleScalaTypeName
)} = field(_.${field.getMethod})((c_, f_) => c_.copy($fieldName = _root_.scala.Option(f_)))
|def ${optionLensName}: ${lensType(
field.scalaTypeName
)} = field(_.$fieldName)((c_, f_) => c_.copy($fieldName = f_))""".stripMargin
)
} else
printer.add(
s"def $fieldName: ${lensType(field.scalaTypeName)} = field(_.$fieldName)((c_, f_) => c_.copy($fieldName = f_))"
)
} else {
val oneofName = field.getContainingOneof.scalaName.nameSymbol
printer
.add(
s"def $fieldName: ${lensType(field.scalaTypeName)} = field(_.${field.getMethod})((c_, f_) => c_.copy($oneofName = ${field.oneOfTypeName
.fullNameWithMaybeRoot(message)}(f_)))"
)
}
}
.print(message.getRealOneofs.asScala) { case (printer, oneof) =>
val oneofName = oneof.scalaName.nameSymbol
printer
.add(
s"def $oneofName: ${lensType(oneof.scalaType.fullNameWithMaybeRoot(message))} = field(_.$oneofName)((c_, f_) => c_.copy($oneofName = f_))"
)
}
.outdent
.add("}")
}
def generateFieldNumbers(message: Descriptor)(printer: FunctionalPrinter): FunctionalPrinter = {
printer
.print(message.fields) { case (printer, field) =>
printer.add(s"final val ${field.fieldNumberConstantName} = ${field.getNumber}")
}
}
def generateTypeMappers(
fields: Seq[FieldDescriptor],
generatePublicConstructorParameters: Boolean
)(printer: FunctionalPrinter): FunctionalPrinter = {
val customizedFields: Seq[(FieldDescriptor, String)] = for {
field <- fields
custom <- field.customSingleScalaTypeName
} yield (field, custom)
printer
.print(customizedFields) { case (printer, (field, customType)) =>
val modifier = {
if (generatePublicConstructorParameters) ""
else if (field.getFile().scalaPackage.fullName.isEmpty) "private "
else s"private[${field.getFile().scalaPackage.fullName.split('.').last}] "
}
printer
.add("@transient")
.add(
s"${modifier}val ${field.typeMapperValName}: _root_.scalapb.TypeMapper[${field.baseSingleScalaTypeName}, ${customType}] = implicitly[_root_.scalapb.TypeMapper[${field.baseSingleScalaTypeName}, ${customType}]]"
)
}
}
def generateCollectionAdapters(
fields: Seq[FieldDescriptor]
)(printer: FunctionalPrinter): FunctionalPrinter = {
val fieldsWithAdapter: Seq[(FieldDescriptor, String)] = for {
field <- fields
adapter <- field.collection.adapterClass
} yield (field, adapter)
printer
.print(fieldsWithAdapter) { case (printer, (field, adapter)) =>
val modifier =
if (field.getFile().scalaPackage.fullName.isEmpty) "private"
else s"private[${field.getFile().scalaPackage.fullName.split('.').last}]"
printer
.add("@transient")
.add(
s"$modifier val ${field.collection.adapter.get.nameSymbol}: _root_.scalapb.CollectionAdapter[${field.singleScalaTypeName}, ${field.scalaTypeName}] = $adapter()"
)
}
}
def generateTypeMappersForMapEntry(
message: Descriptor
)(printer: FunctionalPrinter): FunctionalPrinter = {
val pairToMessage = {
val k = if (message.mapType.keyField.supportsPresence) "Some(__p._1)" else "__p._1"
val v = if (message.mapType.valueField.supportsPresence) "Some(__p._2)" else "__p._2"
s"__p => ${message.scalaType.fullName}($k, $v)"
}
val messageToPair = {
val k = if (message.mapType.keyField.supportsPresence) "__m.getKey" else "__m.key"
val v = if (message.mapType.valueField.supportsPresence) "__m.getValue" else "__m.value"
s"__m => ($k, $v)"
}
printer
.add(
s"""@transient
|implicit val keyValueMapper: _root_.scalapb.TypeMapper[${message.scalaType.fullName}, ${message.mapType.pairType}] =
| _root_.scalapb.TypeMapper[${message.scalaType.fullName}, ${message.mapType.pairType}]($messageToPair)($pairToMessage)""".stripMargin
)
}
def generateMessageCompanionMatcher(
methodName: String,
messageNumbers: Seq[(Descriptor, Int)],
wildcardType: String
)(
fp: FunctionalPrinter
): FunctionalPrinter = {
val signature =
s"def $methodName(__number: _root_.scala.Int): _root_.scalapb.GeneratedMessageCompanion[$wildcardType] = "
// Due to https://issues.scala-lang.org/browse/SI-9111 we can't directly return the companion
// object.
val outStr =
s"var __out: _root_.scalapb.GeneratedMessageCompanion[${wildcardType}] = null"
if (messageNumbers.nonEmpty)
fp.add(signature + "{")
.indent
.add(outStr)
.add("(__number: @_root_.scala.unchecked) match {")
.indent
.print(messageNumbers) { case (fp, (f, number)) =>
fp.add(s"case $number => __out = ${f.scalaType.fullName}")
}
.outdent
.add("}")
.add("__out")
.outdent
.add("}")
else fp.add(signature + "throw new MatchError(__number)")
}
// Finding companion objects by field number
def generateMessageCompanionForField(
message: Descriptor
)(fp: FunctionalPrinter): FunctionalPrinter =
generateMessageCompanionMatcher(
"messageCompanionForFieldNumber",
message.fields.filter(_.isMessage).map(f => (f.getMessageType, f.getNumber)),
message.V.WildcardType
)(fp)
// Finding companion objects for nested types.
def generateNestedMessagesCompanions(
message: Descriptor
)(fp: FunctionalPrinter): FunctionalPrinter = {
val signature =
s"lazy val nestedMessagesCompanions: ${message.V.CompSeqType} ="
if (message.nestedTypes.isEmpty)
fp.add(signature + " Seq.empty")
else
fp.add(signature)
.indent
.add(message.V.CompSeqType + "(")
.indent
.addWithDelimiter(",")(message.nestedTypes.map(m => m.scalaType.fullNameWithMaybeRoot))
.outdent
.add(")")
.outdent
}
// Finding companion objects for top-level types.
def generateMessagesCompanions(file: FileDescriptor)(fp: FunctionalPrinter): FunctionalPrinter = {
val signature = s"lazy val messagesCompanions: ${file.V.CompSeqType} ="
if (file.getMessageTypes.isEmpty)
fp.add(signature + " Seq.empty")
else
fp.add(signature)
.indent
.add(file.V.CompSeqType + "(")
.indent
.addWithDelimiter(",")(file.getMessageTypes.asScala.map(_.scalaType.fullName).toSeq)
.outdent
.add(")")
.outdent
}
def generateEnumCompanionForField(
message: Descriptor
)(fp: FunctionalPrinter): FunctionalPrinter = {
val signature =
s"def enumCompanionForFieldNumber(__fieldNumber: _root_.scala.Int): _root_.scalapb.GeneratedEnumCompanion[${message.V.WildcardType}] = "
if (message.fields.exists(_.isEnum))
fp.add(signature + "{")
.indent
.add("(__fieldNumber: @_root_.scala.unchecked) match {")
.indent
.print(message.fields.filter(_.isEnum)) { case (fp, f) =>
fp.add(s"case ${f.getNumber} => ${f.getEnumType.scalaType.fullName}")
}
.outdent
.add("}")
.outdent
.add("}")
else fp.add(signature + "throw new MatchError(__fieldNumber)")
}
def printExtension(fp: FunctionalPrinter, fd: FieldDescriptor) = {
fp.add(
s"val ${fd.scalaName.asSymbol}: _root_.scalapb.GeneratedExtension[${fd.getContainingType.scalaType.fullName}, ${fd.scalaTypeName}] ="
).call { fp =>
val (listLens, fromFieldToBase, fromBaseToField) = fd.getType match {
case Type.DOUBLE =>
(
"fixed64Lens",
FunctionApplication("java.lang.Double.longBitsToDouble"),
FunctionApplication("java.lang.Double.doubleToLongBits")
)
case Type.FLOAT =>
(
"fixed32Lens",
FunctionApplication("java.lang.Float.intBitsToFloat"),
FunctionApplication("java.lang.Float.floatToIntBits")
)
case Type.INT64 => ("varintLens", Identity, Identity)
case Type.UINT64 => ("varintLens", Identity, Identity)
case Type.INT32 => ("varintLens", MethodApplication("toInt"), MethodApplication("toLong"))
case Type.FIXED64 => ("fixed64Lens", Identity, Identity)
case Type.FIXED32 => ("fixed32Lens", Identity, Identity)
case Type.BOOL =>
(
"varintLens",
OperatorApplication("!= 0"),
FunctionApplication("_root_.scalapb.GeneratedExtension.Internal.bool2Long")
)
case Type.STRING =>
(
"lengthDelimitedLens",
MethodApplication("toStringUtf8()"),
FunctionApplication("_root_.com.google.protobuf.ByteString.copyFromUtf8")
)
case Type.GROUP => throw new RuntimeException("Not supported")
case Type.MESSAGE =>
(
"lengthDelimitedLens",
FunctionApplication(
s"_root_.scalapb.GeneratedExtension.readMessageFromByteString(${fd.baseSingleScalaTypeName})"
),
MethodApplication(s"toByteString")
)
case Type.BYTES => ("lengthDelimitedLens", Identity, Identity)
case Type.UINT32 =>
("varintLens", MethodApplication("toInt"), MethodApplication("toLong"))
case Type.ENUM =>
(
"varintLens",
MethodApplication("toInt") andThen FunctionApplication(
fd.baseSingleScalaTypeName + ".fromValue"
),
MethodApplication("value") andThen MethodApplication("toLong")
)
case Type.SFIXED32 => ("fixed32Lens", Identity, Identity)
case Type.SFIXED64 => ("fixed64Lens", Identity, Identity)
case Type.SINT32 =>
(
"varintLens",
MethodApplication("toInt") andThen FunctionApplication(
"_root_.com.google.protobuf.CodedInputStream.decodeZigZag32"
),
FunctionApplication(
"_root_.com.google.protobuf.CodedOutputStream.encodeZigZag32"
) andThen (
MethodApplication("toLong")
)
)
case Type.SINT64 =>
(
"varintLens",
FunctionApplication("_root_.com.google.protobuf.CodedInputStream.decodeZigZag64"),
FunctionApplication("_root_.com.google.protobuf.CodedOutputStream.encodeZigZag64")
)
}
val fromFieldToCustom = fromFieldToBase andThen toCustomTypeExpr(fd)
val fromCustomToField = toBaseTypeExpr(fd) andThen fromBaseToField
val (factoryMethod, args) = {
if (fd.supportsPresence && !fd.isMessage)
("_root_.scalapb.GeneratedExtension.forOptionalUnknownField", Seq.empty)
else if (fd.supportsPresence && fd.isMessage)
("_root_.scalapb.GeneratedExtension.forOptionalUnknownMessageField", Seq.empty)
else if (fd.isRepeated && fd.isPackable)
(
"_root_.scalapb.GeneratedExtension.forRepeatedPackable",
Seq(fd.getType match {
case Type.DOUBLE | Type.FIXED64 | Type.SFIXED64 => "_.readFixed64()"
case Type.FLOAT | Type.FIXED32 | Type.SFIXED32 => "_.readFixed32()"
case Type.UINT32 | Type.UINT64 | Type.INT32 | Type.INT64 | Type.ENUM |
Type.BOOL | Type.SINT32 | Type.SINT64 =>
"_.readInt64()"
case _ =>
throw new GeneratorException(s"Unexpected packable type: ${fd.getType.name()}")
})
)
else if (fd.isRepeated && !fd.isPackable) {
("_root_.scalapb.GeneratedExtension.forRepeatedUnpackable", Seq())
} else
(
if (!fd.isMessage) "_root_.scalapb.GeneratedExtension.forSingularUnknownField"
else "_root_.scalapb.GeneratedExtension.forSingularUnknownMessageField",
Seq(defaultValueForGet(fd))
)
}
val argList = Seq(
s"{__valueIn => ${fromFieldToCustom("__valueIn", EnclosingType.None)}}",
s"{(__valueIn: ${fd.singleScalaTypeName}) => ${fromCustomToField("__valueIn", EnclosingType.None)}}"
) ++ args
fp.add(
s" $factoryMethod(${fd.getNumber}, _root_.scalapb.UnknownFieldSet.Field.$listLens)(${argList
.mkString(", ")})"
)
}
}
def generateMessageCompanion(
message: Descriptor
)(printer: FunctionalPrinter): FunctionalPrinter = {
val className = message.scalaType.nameSymbol
val companionType = message.companionBaseClasses.mkString(" with ")
val companionTypeDecl = message.companionBaseClasses.mkString(message.V.WithOperator)
printer
.seq(message.companionAnnotationList)
.add(s"""object $className extends $companionType {
| implicit def messageCompanion: $companionTypeDecl = this""".stripMargin)
.indent
.when(message.javaConversions)(generateToJavaProto(message))
.when(message.javaConversions)(generateFromJavaProto(message))
.call(ParseFromGenerator.generateParseFrom(implicits, this, message))
.call(generateMessageReads(message))
.call(generateDescriptors(message))
.call(generateMessageCompanionForField(message))
.call(generateNestedMessagesCompanions(message))
.call(generateEnumCompanionForField(message))
.call(generateDefaultInstance(message))
.print(message.getEnumTypes.asScala)(printEnum)
.print(message.getRealOneofs.asScala)(printOneof)
.print(message.nestedTypes)(printMessage)
.print(message.getExtensions.asScala)(printExtension)
.when(message.generateLenses)(generateMessageLens(message))
.call(generateFieldNumbers(message))
.call(
generateTypeMappers(
message.fields ++ message.getExtensions.asScala,
message.getFile.generatePublicConstructorParameters
)
)
.call(generateCollectionAdapters(message.fields ++ message.getExtensions.asScala))
.when(message.isMapEntry)(generateTypeMappersForMapEntry(message))
.call(generateNoDefaultArgsFactory(message))
.add(s"// @@protoc_insertion_point(${message.messageCompanionInsertionPoint.insertionPoint})")
.outdent
.add("}")
.add("")
}
def generateScalaDoc(enumDesc: EnumDescriptor): PrinterEndo = { fp =>
val lines = asScalaDocBlock(enumDesc.comment.map(_.split('\n').toSeq).getOrElse(Seq.empty))
fp.add(lines: _*)
}
def generateScalaDoc(enumValue: EnumValueDescriptor): PrinterEndo = { fp =>
val lines = asScalaDocBlock(enumValue.comment.map(_.split('\n').toSeq).getOrElse(Seq.empty))
fp.add(lines: _*)
}
def generateScalaDoc(message: Descriptor): PrinterEndo = { fp =>
val mainDoc: Seq[String] = {
val base = message.comment.map(_.split('\n').toSeq).getOrElse(Seq.empty)
// Hack: there's an heading in `any.proto` that causes a Scaladoc
// warning.
if (message.getFullName() != "google.protobuf.Any")
base
else
base.collect {
case l if l.trim == "====" => ""
case l => l
}
}
val fieldsDoc: Seq[String] = message.fields
.filterNot(_.isInOneof)
.map { fd => (fd, fd.comment.map(_.split("\n").toSeq).getOrElse(Seq.empty)) }
.filter(_._2.nonEmpty)
.flatMap { case (fd, lines) =>
Seq(s"@param ${fd.scalaName}") ++ lines.map(" " + _)
}
val sep = if (mainDoc.nonEmpty && fieldsDoc.nonEmpty) Seq("") else Seq.empty
fp.add(asScalaDocBlock(mainDoc ++ sep ++ fieldsDoc): _*)
}
def printMessage(printer: FunctionalPrinter, message: Descriptor): FunctionalPrinter = {
val fullName = message.scalaType.fullNameWithMaybeRoot(message)
val derives =
if (message.derives.nonEmpty) message.derives.mkString(" derives ", ", ", "") else ""
printer
.call(new SealedOneofsGenerator(message, implicits).generateSealedOneofTrait)
.call(generateScalaDoc(message))
.add(s"@SerialVersionUID(0L)")
.seq(message.annotationList)
.add(s"final case class ${message.scalaType.nameSymbol}(")
.indent
.indent
.call(printConstructorFieldList(message))
.add(s") extends ${message.baseClasses.mkString(" with ")}${derives} {")
.call(generateSerializedSizeForPackedFields(message))
.call(generateSerializedSize(message))
.call(generateWriteTo(message))
.print(message.fields) { case (printer, field) =>
val withMethod = "with" + field.upperScalaName
val clearMethod = "clear" + field.upperScalaName
val singleType = field.singleScalaTypeName
printer
.when(
(field.supportsPresence || field.isInOneof) && (message.generateGetters || message.generateLenses || message.isMapEntry)
) { p =>
val default = defaultValueForGet(field)
val scope = if (message.generateGetters) "" else "private "
p.add(
s"${scope}def ${field.getMethod}: ${field.singleScalaTypeName} = ${fieldAccessorSymbol(field)}.getOrElse($default)"
)
}
.when(field.supportsPresence) { p =>
p.add(
s"""def $clearMethod: ${message.scalaType.nameSymbol} = copy(${field.scalaName.asSymbol} = ${C.None})
|def $withMethod(__v: ${singleType}): ${message.scalaType.nameSymbol} = copy(${field.scalaName.asSymbol} = Option(__v))""".stripMargin
)
}
.when(field.isInOneof) { p =>
p.add(
s"""def $withMethod(__v: ${singleType}): ${message.scalaType.nameSymbol} = copy(${field.getContainingOneof.scalaName.nameSymbol} = ${field.oneOfTypeName
.fullNameWithMaybeRoot(message)}(__v))"""
)
}
.when(field.isRepeated) { p =>
val concat = field.collection.concat(field.scalaName.asSymbol, "__vs")
p.when(!field.collection.nonEmptyType)(
_.add(
s"def $clearMethod = copy(${field.scalaName.asSymbol} = ${field.collection.empty})"
)
).add(
s"""|def add${field.upperScalaName}(__vs: $singleType *): ${message.scalaType.nameSymbol} = addAll${field.upperScalaName}(__vs)
|def addAll${field.upperScalaName}(__vs: Iterable[$singleType]): ${message.scalaType.nameSymbol} = copy(${field.scalaName.asSymbol} = $concat)""".stripMargin
)
}
.when(field.isRepeated || field.isSingular) {
_.add(
s"def $withMethod(__v: ${field.scalaTypeName}): ${message.scalaType.nameSymbol} = copy(${field.scalaName.asSymbol} = __v)"
)
}
}
.print(message.getRealOneofs.asScala) { case (printer, oneof) =>
printer.add(
s"""def clear${oneof.scalaType.name}: ${message.scalaType.nameSymbol} = copy(${oneof.scalaName.nameSymbol} = ${oneof.empty
.fullNameWithMaybeRoot(message)})
|def with${oneof.scalaType.name}(__v: ${oneof.scalaType.fullNameWithMaybeRoot(
message
)}): ${message.scalaType.nameSymbol} = copy(${oneof.scalaName.nameSymbol} = __v)""".stripMargin
)
}
.when(message.preservesUnknownFields)(
_.add(
s"""def withUnknownFields(__v: ${C.UnknownFieldSet}) = copy(unknownFields = __v)
|def discardUnknownFields = copy(unknownFields = ${C.UnknownFieldSetEmpty})""".stripMargin
)
)
.call(generateGetField(message))
.call(generateGetFieldPValue(message))
.when(!params.singleLineToProtoString)(
_.add(
s"def toProtoString: _root_.scala.Predef.String = " +
"_root_.scalapb.TextFormat.printToUnicodeString(this)"
)
)
.when(params.singleLineToProtoString)(
_.add(
s"def toProtoString: _root_.scala.Predef.String = " +
"_root_.scalapb.TextFormat.printToSingleLineUnicodeString(this)"
)
)
.when(params.asciiFormatToString)(
_.add("override def toString: _root_.scala.Predef.String = toProtoString")
)
.add(s"def companion: ${fullName}.type = ${fullName}")
.when(message.isSealedOneofType) { fp =>
val scalaType = message.sealedOneofScalaType
val name = message.sealedOneofTraitScalaType.name
fp.add(
s"def to$name: $scalaType = ${message.sealedOneofTypeMapper.fullName}.toCustom(this)"
)
}
.add(s"// @@protoc_insertion_point(${message.messageClassInsertionPoint.insertionPoint})")
.outdent
.outdent
.add(s"""}
|""".stripMargin)
.call(generateMessageCompanion(message))
}
@deprecated("Use overloaded method that takes includePreamble=true", "0.11.11")
def scalaFileHeader(file: FileDescriptor, javaConverterImport: Boolean): FunctionalPrinter =
scalaFileHeader(file, javaConverterImport, true)
def scalaFileHeader(
file: FileDescriptor,
javaConverterImport: Boolean,
includePreamble: Boolean
): FunctionalPrinter = {
if (file.scalaOptions.getPreambleList.asScala.nonEmpty && !file.scalaOptions.getSingleFile) {
throw new GeneratorException(
s"${file.getName}: single_file must be true when a preamble is provided."
)
}
new FunctionalPrinter()
.add(s"""// Generated by the Scala Plugin for the Protocol Buffer Compiler.
|// Do not edit!
|""".stripMargin)
.when(file.scalaPackage.fullName.nonEmpty)(
_.add("package " + file.scalaPackage.fullName).add()
)
.when(javaConverterImport)(
_.add(s"import _root_.scalapb.internal.compat.JavaConverters.${file.V.WildcardImport}")
.add()
)
.print(file.scalaOptions.getImportList.asScala) { case (printer, i) =>
printer.add(s"import $i")
}
.newline
.when(includePreamble)(
_.seq(file.scalaOptions.getPreambleList.asScala.toSeq)
.when(file.scalaOptions.getPreambleList.asScala.nonEmpty)(_.add(""))
)
}
def updateDescriptor(tmp: FileDescriptor): DescriptorProtos.FileDescriptorProto = {
def updateField(field: FieldDescriptor): DescriptorProtos.FieldDescriptorProto = {
val fb = field.toProto.toBuilder
val extBuilder = fb.getOptions.getExtension[FieldOptions](Scalapb.field).toBuilder
assert(!extBuilder.hasScalaName || extBuilder.getScalaName == field.scalaName)
extBuilder.setScalaName(field.scalaName)
fb.getOptionsBuilder.setExtension(Scalapb.field, extBuilder.build())
fb.build()
}
def updateEnum(enumDescriptor: EnumDescriptor): DescriptorProtos.EnumDescriptorProto = {
enumDescriptor
.toProto()
.toBuilder()
.clearValue()
.addAllValue(
enumDescriptor.getValues().asScala.map(updateEnumValue(_)).asJava
)
.build()
}
def updateEnumValue(
enumValue: EnumValueDescriptor
): DescriptorProtos.EnumValueDescriptorProto = {
val ev = enumValue.toProto().toBuilder()
val extBuilder =
enumValue.getOptions().getExtension[Scalapb.EnumValueOptions](Scalapb.enumValue).toBuilder
assert(!extBuilder.hasScalaName || extBuilder.getScalaName == enumValue.scalaName)
extBuilder.setScalaName(enumValue.scalaName)
ev.getOptionsBuilder().setExtension(Scalapb.enumValue, extBuilder.build())
ev.build()
}
def updateMessageType(msg: Descriptor): DescriptorProtos.DescriptorProto = {
msg.toProto.toBuilder
.clearField()
.addAllField(msg.getFields.asScala.map(updateField(_)).asJava)
.clearNestedType()
.addAllNestedType(msg.getNestedTypes.asScala.map(updateMessageType(_)).asJava)
.clearEnumType()
.addAllEnumType(msg.getEnumTypes.asScala.map(updateEnum(_)).asJava)
.build()
}
val fileProto = tmp.toProto
fileProto.toBuilder
.clearMessageType()
.addAllMessageType(tmp.getMessageTypes().asScala.map(updateMessageType(_)).asJava)
.clearEnumType()
.addAllEnumType(tmp.getEnumTypes().asScala.map(updateEnum(_)).asJava)
.build
}
def generateFileDescriptor(file: FileDescriptor)(fp: FunctionalPrinter): FunctionalPrinter = {
val descriptor = {
val withScalaName = updateDescriptor(file)
if (file.retainSourceCodeInfo) withScalaName
else withScalaName.toBuilder.clearSourceCodeInfo.build
}
// Encoding the file descriptor proto in base64. JVM has a limit on string literal to be up
// to 64k, so we chunk it into a sequence and combining in run time. The chunks are less
// than 64k to account for indentation and new lines.
val base64: Seq[Seq[String]] = scalapb.internal.Encoding
.toBase64(descriptor.toByteArray)
.grouped(55000)
.map { group =>
val lines = ("\"\"\"" + group).grouped(100).toSeq
lines.dropRight(1) :+ (lines.last + "\"\"\"")
}
.toSeq
fp.add("private lazy val ProtoBytes: _root_.scala.Array[Byte] =")
.add(" scalapb.Encoding.fromBase64(scala.collection.immutable.Seq(")
.addGroupsWithDelimiter(",")(base64)
.add(" ).mkString)")
.add("lazy val scalaDescriptor: _root_.scalapb.descriptors.FileDescriptor = {")
.add(
" val scalaProto = com.google.protobuf.descriptor.FileDescriptorProto.parseFrom(ProtoBytes)"
)
.add(
" _root_.scalapb.descriptors.FileDescriptor.buildFrom(scalaProto, dependencies.map(_.scalaDescriptor))"
)
.add("}")
.when(file.javaConversions) {
_.add("lazy val javaDescriptor: com.google.protobuf.Descriptors.FileDescriptor =")
.add(s" ${file.javaFullOuterClassName}.getDescriptor()")
}
.when(!file.javaConversions) {
_.add("lazy val javaDescriptor: com.google.protobuf.Descriptors.FileDescriptor = {")
.add(
" val javaProto = com.google.protobuf.DescriptorProtos.FileDescriptorProto.parseFrom(ProtoBytes)"
)
.add(
" com.google.protobuf.Descriptors.FileDescriptor.buildFrom(javaProto, _root_.scala.Array("
)
.addWithDelimiter(",")(file.getDependencies.asScala.map { d =>
s" ${d.fileDescriptorObject.fullName}.javaDescriptor"
}.toSeq)
.add(" ))")
.add("}")
}
.add(
"""@deprecated("Use javaDescriptor instead. In a future version this will refer to scalaDescriptor.", "ScalaPB 0.5.47")"""
)
.add("def descriptor: com.google.protobuf.Descriptors.FileDescriptor = javaDescriptor")
}
def generateServiceFiles(file: FileDescriptor): Seq[CodeGeneratorResponse.File] = {
if (params.grpc) {
file.getServices.asScala.map { service =>
val p = new GrpcServicePrinter(service, implicits)
val code = scalaFileHeader(
file,
file.javaConversions && file.getMessageTypes.asScala.exists(
messageContainsRepeatedFields
),
includePreamble = false
).call(p.printService).result()
val b = CodeGeneratorResponse.File.newBuilder()
b.setName(file.scalaDirectory + "/" + service.companionObject.name + ".scala")
b.setContent(code)
b.build
}
}.toSeq
else Nil
}
def generateFileObject(file: FileDescriptor)(fp: FunctionalPrinter): FunctionalPrinter = {
fp.add(
s"object ${file.fileDescriptorObject.nameSymbol} extends _root_.scalapb.GeneratedFileObject {"
).indent
.when(file.getDependencies.isEmpty) {
_.add("lazy val dependencies: Seq[_root_.scalapb.GeneratedFileObject] = Seq.empty")
}
.when(!file.getDependencies.isEmpty) {
_.add("lazy val dependencies: Seq[_root_.scalapb.GeneratedFileObject] = Seq(").indent
.addWithDelimiter(",")(
file.getDependencies.asScala.map(_.fileDescriptorObject.fullName).toSeq
)
.outdent
.add(")")
}
.call(generateMessagesCompanions(file)(_))
.call(generateFileDescriptor(file)(_))
.print(file.getExtensions.asScala)(printExtension(_, _))
.call(
generateTypeMappers(
file.getExtensions.asScala.toSeq,
file.generatePublicConstructorParameters
)
)
.outdent
.add("}")
}
private def messageContainsRepeatedFields(message: Descriptor): Boolean = {
message.fields.exists(_.isRepeated) || message.nestedTypes.exists(messageContainsRepeatedFields)
}
def generateSingleScalaFileForFileDescriptor(
file: FileDescriptor
): Seq[CodeGeneratorResponse.File] = {
val code =
scalaFileHeader(
file,
file.javaConversions && file.getMessageTypes.asScala.exists(messageContainsRepeatedFields),
includePreamble = true
).print(file.getEnumTypes.asScala)(printEnum)
.print(file.getMessageTypes.asScala)(printMessage)
.call(generateFileObject(file))
.result()
val b = CodeGeneratorResponse.File.newBuilder()
b.setName(file.scalaFileName)
b.setContent(code)
generateServiceFiles(file) :+ b.build
}
def generateMultipleScalaFilesForFileDescriptor(
file: FileDescriptor
): Seq[CodeGeneratorResponse.File] = {
val serviceFiles = generateServiceFiles(file)
val enumFiles = for {
enumDesc <- file.getEnumTypes.asScala
} yield {
val b = CodeGeneratorResponse.File.newBuilder()
b.setName(file.scalaDirectory + "/" + enumDesc.getName + ".scala")
b.setContent(
scalaFileHeader(file, false, false)
.call(printEnum(_, enumDesc))
.result()
)
b.build
}
val messageFiles = for {
message <- file.getMessageTypes.asScala if !message.isSealedOneofCase
} yield {
val b = CodeGeneratorResponse.File.newBuilder()
val cases = message.sealedOneofCases.getOrElse(Seq.empty)
b.setName(message.scalaFileName)
b.setContent(
scalaFileHeader(
file,
javaConverterImport = file.javaConversions && (messageContainsRepeatedFields(
message
) || cases.exists(messageContainsRepeatedFields(_))),
true
).call(printMessage(_, message))
.print(cases)(printMessage)
.result()
)
b.build
}
val fileDescriptorObjectFile = {
val b = CodeGeneratorResponse.File.newBuilder()
b.setName(file.scalaFileName)
b.setContent(
scalaFileHeader(file, false, true)
.call(generateFileObject(file))
.result()
)
b.build
}
serviceFiles ++ enumFiles ++ messageFiles :+ fileDescriptorObjectFile
}
}
private[scalapb] object C {
val None = "_root_.scala.None"
val UnknownFieldSet = "_root_.scalapb.UnknownFieldSet"
val UnknownFieldSetEmpty = "_root_.scalapb.UnknownFieldSet.empty"
}
object ProtobufGenerator {
def parseParameters(params: String): Either[String, GeneratorParams] =
GeneratorParams.fromString(params)
def handleCodeGeneratorRequest(request: CodeGenRequest): CodeGenResponse = {
parseParameters(request.parameter) match {
case Right(params) =>
try {
val implicits = DescriptorImplicits.fromCodeGenRequest(params, request)
val generator = new ProtobufGenerator(params, implicits)
val validator = new ProtoValidation(implicits)
validator.validateFiles(request.allProtos)
import implicits.ExtendedFileDescriptor
val files = request.filesToGenerate.filterNot(_.disableOutput).flatMap { file =>
if (file.scalaOptions.getSingleFile)
generator.generateSingleScalaFileForFileDescriptor(file)
else generator.generateMultipleScalaFilesForFileDescriptor(file)
}
CodeGenResponse.succeed(
files,
Set(
CodeGeneratorResponse.Feature.FEATURE_PROTO3_OPTIONAL,
CodeGeneratorResponse.Feature.FEATURE_SUPPORTS_EDITIONS
)
)
} catch {
case e: GeneratorException =>
CodeGenResponse.fail(e.message)
}
case Left(error) =>
CodeGenResponse.fail(error)
}
}
def asScalaDocBlock(contentLines: Seq[String]): Seq[String] = {
if (contentLines.nonEmpty) {
contentLines.zipWithIndex.map { case (line, index) =>
val prefix = if (index == 0) "/**" else " *"
if (line.startsWith(" ") || line.isEmpty) (prefix + line) else (prefix + " " + line)
} :+ " */"
} else contentLines
}
val deprecatedAnnotation: String =
"""@scala.deprecated(message="Marked as deprecated in proto file", "")"""
private[scalapb] def escapeScalaString(raw: String): String =
raw
.map {
case '\b' => "\\b"
case '\f' => "\\f"
case '\n' => "\\n"
case '\r' => "\\r"
case '\t' => "\\t"
case '\\' => "\\\\"
case '\"' => "\\\""
case '\'' => "\\\'"
case u if u >= ' ' && u <= '~' => u.toString
case c: Char => "\\u%4s".format(c.toInt.toHexString).replace(' ', '0')
}
.mkString("\"", "", "\"")
}