Skip to content

Commit f00caa4

Browse files
authored
implement proto3 field presence (#88)
1 parent 7e7dade commit f00caa4

File tree

13 files changed

+143
-24
lines changed

13 files changed

+143
-24
lines changed

protokt-codegen/src/main/kotlin/com/toasttab/protokt/Main.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.google.protobuf.DescriptorProtos.FileDescriptorProto
2323
import com.google.protobuf.ExtensionRegistry
2424
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest
2525
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse
26+
import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse.Feature
2627
import com.toasttab.protokt.codegen.generate
2728
import com.toasttab.protokt.codegen.impl.STAnnotator
2829
import com.toasttab.protokt.codegen.impl.STEffects
@@ -51,6 +52,7 @@ internal fun main(bytes: ByteArray, out: OutputStream) {
5152

5253
if (files.nonEmpty()) {
5354
CodeGeneratorResponse.newBuilder()
55+
.setSupportedFeatures(Feature.FEATURE_PROTO3_OPTIONAL.number.toLong())
5456
.addAllFile(files)
5557
.build()
5658
.writeTo(out)

protokt-codegen/src/main/kotlin/com/toasttab/protokt/codegen/impl/MessageAnnotator.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import com.toasttab.protokt.codegen.impl.Implements.doesImplement
2424
import com.toasttab.protokt.codegen.impl.Implements.implements
2525
import com.toasttab.protokt.codegen.impl.MapEntryAnnotator.Companion.annotateMapEntry
2626
import com.toasttab.protokt.codegen.impl.MessageDocumentationAnnotator.annotateMessageDocumentation
27-
import com.toasttab.protokt.codegen.impl.OneofAnnotator.Companion.annotateOneOfs
27+
import com.toasttab.protokt.codegen.impl.OneofAnnotator.Companion.annotateOneofs
2828
import com.toasttab.protokt.codegen.impl.PropertyAnnotator.Companion.annotateProperties
2929
import com.toasttab.protokt.codegen.impl.STAnnotator.Context
3030
import com.toasttab.protokt.codegen.impl.STAnnotator.annotate
@@ -49,7 +49,7 @@ internal object MessageAnnotator {
4949
MessageTemplate.render(
5050
message = messageInfo(msg, ctx),
5151
properties = annotateProperties(msg, ctx),
52-
oneofs = annotateOneOfs(msg, ctx),
52+
oneofs = annotateOneofs(msg, ctx),
5353
sizeof = annotateSizeof(msg, ctx),
5454
serialize = annotateSerializer(msg, ctx),
5555
deserialize = annotateDeserializer(msg, ctx),

protokt-codegen/src/main/kotlin/com/toasttab/protokt/codegen/impl/Nullability.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ internal object Nullability {
3636
private val Field.isKotlinRepresentationNullable
3737
get() =
3838
when (this) {
39-
is StandardField -> type == FieldType.MESSAGE && !repeated
39+
is StandardField -> (type == FieldType.MESSAGE && !repeated) || optional
4040
is Oneof -> true
4141
}
4242

protokt-codegen/src/main/kotlin/com/toasttab/protokt/codegen/impl/OneofAnnotator.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ private constructor(
3434
private val msg: Message,
3535
private val ctx: Context
3636
) {
37-
private fun annotateOneOfs() =
37+
private fun annotateOneofs() =
3838
msg.fields.map {
3939
when (it) {
4040
is Oneof ->
@@ -152,7 +152,7 @@ private constructor(
152152
msg.fields.filterIsInstance<Oneof>().map { it.name }
153153

154154
companion object {
155-
fun annotateOneOfs(msg: Message, ctx: Context) =
156-
OneofAnnotator(msg, ctx).annotateOneOfs()
155+
fun annotateOneofs(msg: Message, ctx: Context) =
156+
OneofAnnotator(msg, ctx).annotateOneofs()
157157
}
158158
}

protokt-codegen/src/main/kotlin/com/toasttab/protokt/codegen/impl/PropertyAnnotator.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ private constructor(
5959
fieldType = it.type.toString(),
6060
repeated = it.repeated,
6161
map = it.map,
62-
nullable = it.nullable,
62+
nullable = it.nullable || it.optional,
6363
nonNullOption = it.hasNonNullOption,
6464
overrides = it.overrides(ctx, msg),
6565
wrapped = it.wrapped,

protokt-codegen/src/main/kotlin/com/toasttab/protokt/codegen/protoc/Protocol.kt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ fun toProtocol(ctx: ProtocolContext) =
4848
FileDesc(
4949
name = ctx.fdp.name,
5050
packageName = ctx.fdp.`package`,
51-
version = ctx.fdp.syntax?.removePrefix("proto")?.toIntOrNull() ?: 2,
5251
options = ctx.fdp.fileOptions,
5352
context = ctx,
5453
sourceCodeInfo = ctx.fdp.sourceCodeInfo
@@ -270,8 +269,13 @@ private fun toOneof(
270269
field: FieldDescriptorProto,
271270
typeNames: Set<String>,
272271
fields: ImmutableList<Field>
273-
): Oneof {
272+
): Field {
274273
val newName = newFieldName(oneof.name, typeNames)
274+
275+
if (field.proto3Optional) {
276+
return toStandard(idx, ctx, field, typeNames)
277+
}
278+
275279
val standardTuple = desc.fieldList.filter {
276280
it.hasOneofIndex() && it.oneofIndex == field.oneofIndex
277281
}.foldIndexed(
@@ -309,22 +313,25 @@ private fun toStandard(
309313
usedFieldNames: Set<String>,
310314
alwaysRequired: Boolean = false
311315
): StandardField =
312-
toFieldType(checkNotNull(fdp.type) { "Missing field type" }).let { type ->
316+
toFieldType(fdp.type).let { type ->
313317
StandardField(
314318
number = fdp.number,
315319
name = newFieldName(fdp.name, usedFieldNames),
316320
type = type,
317321
repeated = fdp.label == LABEL_REPEATED,
318-
optional = !alwaysRequired && fdp.label == LABEL_OPTIONAL,
322+
optional =
323+
!alwaysRequired &&
324+
(fdp.label == LABEL_OPTIONAL && ctx.proto2) ||
325+
fdp.proto3Optional,
319326
packed =
320327
type.packable &&
321328
// marginal support for proto2
322-
((ctx.fdp.syntax == "proto2" && fdp.options.packed) ||
329+
((ctx.proto2 && fdp.options.packed) ||
323330
// packed if: proto3 and `packed` isn't set, or proto3
324331
// and `packed` is true. If proto3, only explicitly
325332
// setting `packed` to false disables packing, since
326333
// the default value for an unset boolean is false.
327-
(ctx.fdp.syntax == "proto3" &&
334+
(ctx.proto3 &&
328335
(!fdp.options.hasPacked() ||
329336
(fdp.options.hasPacked() && fdp.options.packed)))),
330337
mapEntry =

protokt-codegen/src/main/kotlin/com/toasttab/protokt/codegen/protoc/ProtocolContext.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ class ProtocolContext(
3535

3636
val fileName = fdp.name
3737
val version = getProtoktVersion(ProtocolContext::class)
38+
39+
val proto2 = !fdp.hasSyntax() || fdp.syntax == "proto2"
40+
val proto3 = fdp.syntax == "proto3"
3841
}
3942

4043
fun respectJavaPackage(params: Map<String, String>) =

protokt-codegen/src/main/kotlin/com/toasttab/protokt/codegen/protoc/Types.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ class OneofOptions(
142142
class FileDesc(
143143
val name: String,
144144
val packageName: String,
145-
val version: Int,
146145
val options: FileOptions,
147146
val context: ProtocolContext,
148147
val sourceCodeInfo: DescriptorProtos.SourceCodeInfo

protokt-codegen/src/main/resources/dsl.stg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ dslValue(p) ::= <%
7171
emptyMap()
7272
<elseif (p.repeated)>
7373
emptyList()
74-
<elseif (isMessage.(p.fieldType) || p.wrapped)>
74+
<elseif (isMessage.(p.fieldType) || p.wrapped || p.nullable)>
7575
null
7676
<else>
7777
<p.defaultValue>

protokt-codegen/src/main/resources/renderers.stg

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ deserializeType(p) ::= <%
111111
%>
112112

113113
deserializeValue(p) ::= <%
114-
<if (p.repeated || isMessage.(p.fieldType) || p.wrapped)>
114+
<if (p.repeated || isMessage.(p.fieldType) || p.wrapped || p.nullable)>
115115
null
116116
<else>
117117
<p.defaultValue>
@@ -159,19 +159,29 @@ defaultValue(field, type, name) ::= <%
159159
emptyList()
160160
<elseif (isGeneratedType.(type))>
161161
<name><kotlinDefaultValues.(type)>
162+
<elseif (field.nullable)>
163+
null
162164
<else>
163165
<kotlinDefaultValues.(type)>
164166
<endif>
165167
%>
166168

167169
nonDefaultValue(field, name) ::= <%
168-
<if (field.repeated)>(<field.fieldName>.isNotEmpty())<\ >
169-
<elseif (isBytes.(field.type) || isString.(field.type))>(<name>.isNotEmpty())<\ >
170-
<elseif (isMessage.(field.type))>(<field.fieldName> != null)<\ >
171-
<elseif (isEnum.(field.type))>(<name>.value != 0)<\ >
170+
<if ((isMessage.(field.type) && !field.repeated) || field.optional)>
171+
(<field.fieldName> != null)<\ >
172+
<elseif (field.repeated)>
173+
(<field.fieldName>.isNotEmpty())<\ >
174+
<elseif (isBytes.(field.type) || isString.(field.type))>
175+
(<proto2Check()><name>.isNotEmpty())<\ >
176+
<elseif (isMessage.(field.type))>
177+
(<field.fieldName> != null)<\ >
178+
<elseif (isEnum.(field.type))>
179+
(<name>.value != 0)<\ >
172180
<elseif (field.type.scalar)>
173-
<if (isBool.(field.type))>(<name>)<\ >
174-
<else>(<name> != <kotlinDefaultValues.(field.type)>)<\ >
181+
<if (isBool.(field.type))>
182+
(<name>)<\ >
183+
<else>
184+
(<name> != <kotlinDefaultValues.(field.type)>)<\ >
175185
<endif>
176186
<endif>
177187
%>

0 commit comments

Comments
 (0)