Skip to content

Commit d811cb0

Browse files
committed
protoc-plugin: Add Map types
Signed-off-by: Johannes Zottele <[email protected]>
1 parent ec3a536 commit d811cb0

File tree

4 files changed

+87
-83
lines changed

4 files changed

+87
-83
lines changed

protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,8 @@ package kotlinx.rpc.protobuf
77
import org.slf4j.Logger
88
import org.slf4j.helpers.NOPLogger
99

10-
data class CodeGenerationParameters(
11-
val messageMode: RpcProtobufPlugin.MessageMode,
12-
)
1310

1411
open class CodeGenerator(
15-
val parameters: CodeGenerationParameters,
1612
private val indent: String,
1713
private val builder: StringBuilder = StringBuilder(),
1814
private val logger: Logger = NOPLogger.NOP_LOGGER,
@@ -68,7 +64,7 @@ open class CodeGenerator(
6864
}
6965

7066
private fun withNextIndent(block: CodeGenerator.() -> Unit) {
71-
CodeGenerator(parameters, "$indent$ONE_INDENT", builder, logger).block()
67+
CodeGenerator("$indent$ONE_INDENT", builder, logger).block()
7268
}
7369

7470
internal fun scope(
@@ -132,7 +128,7 @@ open class CodeGenerator(
132128
return
133129
}
134130

135-
val nested = CodeGenerator(parameters, "$indent$ONE_INDENT", logger = logger).apply(block)
131+
val nested = CodeGenerator("$indent$ONE_INDENT", logger = logger).apply(block)
136132

137133
if (nested.isEmpty) {
138134
newLine()
@@ -306,13 +302,12 @@ open class CodeGenerator(
306302
}
307303

308304
class FileGenerator(
309-
codeGenerationParameters: CodeGenerationParameters,
310305
var filename: String? = null,
311306
var packageName: String? = null,
312307
var packagePath: String? = packageName,
313308
var fileOptIns: List<String> = emptyList(),
314309
logger: Logger = NOPLogger.NOP_LOGGER,
315-
) : CodeGenerator(codeGenerationParameters, "", logger = logger) {
310+
) : CodeGenerator("", logger = logger) {
316311
private val imports = mutableListOf<String>()
317312

318313
fun importPackage(name: String) {
@@ -354,10 +349,9 @@ class FileGenerator(
354349
}
355350

356351
fun file(
357-
codeGenerationParameters: CodeGenerationParameters,
358352
name: String? = null,
359353
packageName: String? = null,
360354
logger: Logger = NOPLogger.NOP_LOGGER,
361355
block: FileGenerator.() -> Unit,
362-
): FileGenerator = FileGenerator(codeGenerationParameters, name, packageName, packageName, emptyList(), logger)
356+
): FileGenerator = FileGenerator(name, packageName, packageName, emptyList(), logger)
363357
.apply(block)

protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinCommonGenerator.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ private const val RPC_INTERNAL_PACKAGE_SUFFIX = "_rpc_internal"
1515
class ModelToKotlinCommonGenerator(
1616
private val model: Model,
1717
private val logger: Logger,
18-
private val codeGenerationParameters: CodeGenerationParameters,
1918
) {
2019
fun generateKotlinFiles(): List<FileGenerator> {
2120
return model.files.flatMap { it.generateKotlinFiles() }
@@ -36,7 +35,7 @@ class ModelToKotlinCommonGenerator(
3635
private fun FileDeclaration.generatePublicKotlinFile(): FileGenerator {
3736
currentPackage = packageName
3837

39-
return file(codeGenerationParameters, logger = logger) {
38+
return file(logger = logger) {
4039
filename = this@generatePublicKotlinFile.name
4140
packageName = this@generatePublicKotlinFile.packageName.safeFullName()
4241
packagePath = this@generatePublicKotlinFile.packageName.safeFullName()
@@ -61,7 +60,7 @@ class ModelToKotlinCommonGenerator(
6160
private fun FileDeclaration.generateInternalKotlinFile(): FileGenerator {
6261
currentPackage = packageName
6362

64-
return file(codeGenerationParameters, logger = logger) {
63+
return file(logger = logger) {
6564
filename = this@generateInternalKotlinFile.name
6665
packageName = this@generateInternalKotlinFile.packageName.safeFullName()
6766
packagePath =

protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/RpcProtobufPlugin.kt

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,9 @@ import java.io.File
2020
class RpcProtobufPlugin {
2121
companion object {
2222
private const val DEBUG_OUTPUT_OPTION = "debugOutput"
23-
private const val MESSAGE_MODE_OPTION = "messageMode"
24-
}
25-
26-
enum class MessageMode {
27-
Interface, Class;
28-
29-
companion object {
30-
fun of(value: String?): MessageMode {
31-
return when (value) {
32-
"interface" -> Interface
33-
"class" -> Class
34-
null -> error("Message mode is not specified, use --messageMode=interface or --messageMode=class")
35-
else -> error("Unknown message mode: $value")
36-
}
37-
}
38-
}
3923
}
4024

4125
private var debugOutput: String? = null
42-
private lateinit var messageGenerationMode: MessageMode
43-
private var targetCommon: Boolean = false
4426
private val logger: Logger by lazy {
4527
val debugOutput = debugOutput ?: return@lazy NOPLogger.NOP_LOGGER
4628

@@ -71,7 +53,6 @@ class RpcProtobufPlugin {
7153
}
7254

7355
debugOutput = parameters[DEBUG_OUTPUT_OPTION]
74-
messageGenerationMode = MessageMode.of(parameters[MESSAGE_MODE_OPTION])
7556

7657
val files = input.generateKotlinCommonFiles()
7758
.map { file ->
@@ -101,9 +82,8 @@ class RpcProtobufPlugin {
10182
}
10283

10384
private fun CodeGeneratorRequest.generateKotlinCommonFiles(): List<FileGenerator> {
104-
val model = this.toCommonModel()
105-
val fileGenerator =
106-
ModelToKotlinCommonGenerator(model, logger, CodeGenerationParameters(messageGenerationMode))
85+
val model = this.toModel()
86+
val fileGenerator = ModelToKotlinCommonGenerator(model, logger)
10787
return fileGenerator.generateKotlinFiles()
10888
}
10989
}

protoc-gen/src/main/kotlin/kotlinx/rpc/protobuf/codeRequestToModel.kt

Lines changed: 79 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,33 @@ import kotlinx.rpc.protobuf.model.*
1111

1212
private val modelCache = mutableMapOf<Descriptors.GenericDescriptor, Any>()
1313

14-
fun CodeGeneratorRequest.toCommonModel(): Model {
14+
/**
15+
* Converts a [CodeGeneratorRequest] into the protoc plugin [Model] of the protobuf.
16+
*
17+
* @return a [Model] instance containing a list of [FileDeclaration]s that represent
18+
* the converted protobuf files.
19+
*/
20+
fun CodeGeneratorRequest.toModel(): Model {
1521
val protoFileMap = protoFileList.associateBy { it.name }
1622
val fileDescriptors = mutableMapOf<String, Descriptors.FileDescriptor>()
1723

1824
val files = protoFileList.map { protoFile -> protoFile.toDescriptor(protoFileMap, fileDescriptors) }
1925

2026
return Model(
21-
files = files.map { it.toCommonModel() }
27+
files = files.map { it.toModel() }
2228
)
2329
}
2430

2531

32+
/**
33+
* Converts a [DescriptorProtos.FileDescriptorProto] instance to a [Descriptors.FileDescriptor],
34+
* resolving its dependencies.
35+
*
36+
* @param protoFileMap a map of file names to `FileDescriptorProto` objects which are available for resolution.
37+
* @param cache a mutable map that stores already resolved `FileDescriptor` objects by file name to prevent
38+
* redundant computations.
39+
* @return the resolved `FileDescriptor` instance corresponding to this `FileDescriptorProto`.
40+
*/
2641
private fun DescriptorProtos.FileDescriptorProto.toDescriptor(
2742
protoFileMap: Map<String, DescriptorProtos.FileDescriptorProto>,
2843
cache: MutableMap<String, Descriptors.FileDescriptor>
@@ -39,6 +54,38 @@ private fun DescriptorProtos.FileDescriptorProto.toDescriptor(
3954
return fileDescriptor
4055
}
4156

57+
/**
58+
* Returns the fully qualified name [FqName] of this descriptor, resolving it to the most specific
59+
* declaration or package name based on its type.
60+
*
61+
* Depending on the type of the descriptor, the fully qualified name is computed recursively,
62+
* using the containing type or file, and appropriately converting names.
63+
*
64+
* @return The fully qualified name represented as an instance of FqName, specific to the descriptor's context.
65+
*/
66+
private fun Descriptors.GenericDescriptor.fqName(): FqName {
67+
val nameCapital = name.simpleProtoNameToKotlin(firstLetterUpper = true)
68+
val nameLower = name.simpleProtoNameToKotlin()
69+
return when (this) {
70+
is Descriptors.FileDescriptor -> FqName.Package.fromString(`package`)
71+
is Descriptors.Descriptor -> FqName.Declaration(nameCapital, containingType?.fqName() ?: file.fqName())
72+
is Descriptors.FieldDescriptor -> {
73+
val usedName = if (realContainingOneof != null) nameCapital else nameLower
74+
FqName.Declaration(usedName, containingType?.fqName() ?: file.fqName())
75+
}
76+
77+
is Descriptors.EnumValueDescriptor -> FqName.Declaration(name, type.fqName())
78+
is Descriptors.OneofDescriptor -> FqName.Declaration(nameCapital, containingType?.fqName() ?: file.fqName())
79+
is Descriptors.ServiceDescriptor -> FqName.Declaration(nameCapital, file?.fqName() ?: file.fqName())
80+
is Descriptors.MethodDescriptor -> FqName.Declaration(nameLower, service?.fqName() ?: file.fqName())
81+
else -> error("Unknown generic descriptor: $this")
82+
}
83+
}
84+
85+
/**
86+
* Caches the `descriptor.toModel()` result in the [modelCache] to ensure that only a single object
87+
* per descriptor exists.
88+
*/
4289
private inline fun <D, reified T> D.cached(block: (D) -> T): T
4390
where D : Descriptors.GenericDescriptor, T : Any {
4491
if (modelCache.containsKey(this)) {
@@ -49,35 +96,35 @@ private inline fun <D, reified T> D.cached(block: (D) -> T): T
4996
return declaration
5097
}
5198

52-
private fun Descriptors.FileDescriptor.toCommonModel(): FileDeclaration = cached {
99+
private fun Descriptors.FileDescriptor.toModel(): FileDeclaration = cached {
53100
return FileDeclaration(
54101
name = kotlinFileName(),
55102
packageName = FqName.Package.fromString(`package`),
56-
dependencies = dependencies.map { it.toCommonModel() },
57-
messageDeclarations = messageTypes.map { it.toCommonModel() },
58-
enumDeclarations = enumTypes.map { it.toCommonModel() },
59-
serviceDeclarations = services.map { it.toCommonModel() },
103+
dependencies = dependencies.map { it.toModel() },
104+
messageDeclarations = messageTypes.map { it.toModel() },
105+
enumDeclarations = enumTypes.map { it.toModel() },
106+
serviceDeclarations = services.map { it.toModel() },
60107
doc = null,
61108
dec = this,
62109
)
63110
}
64111

65-
private fun Descriptors.Descriptor.toCommonModel(): MessageDeclaration = cached {
66-
val regularFields = fields.filter { field -> field.realContainingOneof == null }.map { it.toCommonModel() }
112+
private fun Descriptors.Descriptor.toModel(): MessageDeclaration = cached {
113+
val regularFields = fields.filter { field -> field.realContainingOneof == null }.map { it.toModel() }
67114

68115
return MessageDeclaration(
69116
name = fqName(),
70117
actualFields = regularFields,
71118
// get all oneof declarations that are not created from an optional in proto3 https://github.com/googleapis/api-linter/issues/1323
72-
oneOfDeclarations = oneofs.filter { it.fields[0].realContainingOneof != null }.map { it.toCommonModel() },
73-
enumDeclarations = enumTypes.map { it.toCommonModel() },
74-
nestedDeclarations = nestedTypes.map { it.toCommonModel() },
119+
oneOfDeclarations = oneofs.filter { it.fields[0].realContainingOneof != null }.map { it.toModel() },
120+
enumDeclarations = enumTypes.map { it.toModel() },
121+
nestedDeclarations = nestedTypes.map { it.toModel() },
75122
doc = null,
76123
dec = this,
77124
)
78125
}
79126

80-
private fun Descriptors.FieldDescriptor.toCommonModel(): FieldDeclaration = cached {
127+
private fun Descriptors.FieldDescriptor.toModel(): FieldDeclaration = cached {
81128
toProto().hasProto3Optional()
82129
return FieldDeclaration(
83130
name = fqName().simpleName,
@@ -88,15 +135,15 @@ private fun Descriptors.FieldDescriptor.toCommonModel(): FieldDeclaration = cach
88135
}
89136

90137

91-
private fun Descriptors.OneofDescriptor.toCommonModel(): OneOfDeclaration = cached {
138+
private fun Descriptors.OneofDescriptor.toModel(): OneOfDeclaration = cached {
92139
return OneOfDeclaration(
93140
name = fqName(),
94-
variants = fields.map { it.toCommonModel() },
141+
variants = fields.map { it.toModel() },
95142
dec = this,
96143
)
97144
}
98145

99-
private fun Descriptors.EnumDescriptor.toCommonModel(): EnumDeclaration = cached {
146+
private fun Descriptors.EnumDescriptor.toModel(): EnumDeclaration = cached {
100147
val entriesMap = mutableMapOf<Int, EnumDeclaration.Entry>()
101148
val aliases = mutableListOf<EnumDeclaration.Alias>()
102149

@@ -105,7 +152,7 @@ private fun Descriptors.EnumDescriptor.toCommonModel(): EnumDeclaration = cached
105152
val original = entriesMap.getValue(value.number)
106153
aliases.add(value.toAliasModel(original))
107154
} else {
108-
entriesMap[value.number] = value.toCommonModel()
155+
entriesMap[value.number] = value.toModel()
109156
}
110157
}
111158

@@ -122,7 +169,7 @@ private fun Descriptors.EnumDescriptor.toCommonModel(): EnumDeclaration = cached
122169
)
123170
}
124171

125-
private fun Descriptors.EnumValueDescriptor.toCommonModel(): EnumDeclaration.Entry = cached {
172+
private fun Descriptors.EnumValueDescriptor.toModel(): EnumDeclaration.Entry = cached {
126173
return EnumDeclaration.Entry(
127174
name = fqName(),
128175
doc = null,
@@ -140,19 +187,19 @@ private fun Descriptors.EnumValueDescriptor.toAliasModel(original: EnumDeclarati
140187
)
141188
}
142189

143-
private fun Descriptors.ServiceDescriptor.toCommonModel(): ServiceDeclaration = cached {
190+
private fun Descriptors.ServiceDescriptor.toModel(): ServiceDeclaration = cached {
144191
return ServiceDeclaration(
145192
name = fqName(),
146-
methods = methods.map { it.toCommonModel() },
193+
methods = methods.map { it.toModel() },
147194
dec = this,
148195
)
149196
}
150197

151-
private fun Descriptors.MethodDescriptor.toCommonModel(): MethodDeclaration = cached {
198+
private fun Descriptors.MethodDescriptor.toModel(): MethodDeclaration = cached {
152199
return MethodDeclaration(
153200
name = name,
154-
inputType = inputType.toCommonModel(),
155-
outputType = outputType.toCommonModel(),
201+
inputType = inputType.toModel(),
202+
outputType = outputType.toModel(),
156203
dec = this,
157204
)
158205
}
@@ -176,41 +223,25 @@ private fun Descriptors.FieldDescriptor.modelType(): FieldType {
176223
Descriptors.FieldDescriptor.Type.SFIXED64 -> FieldType.IntegralType.SFIXED64
177224
Descriptors.FieldDescriptor.Type.SINT32 -> FieldType.IntegralType.SINT32
178225
Descriptors.FieldDescriptor.Type.SINT64 -> FieldType.IntegralType.SINT64
179-
Descriptors.FieldDescriptor.Type.ENUM -> FieldType.Reference(lazy { enumType!!.toCommonModel().name })
180-
Descriptors.FieldDescriptor.Type.MESSAGE -> FieldType.Reference(lazy { messageType!!.toCommonModel().name })
226+
Descriptors.FieldDescriptor.Type.ENUM -> FieldType.Reference(lazy { enumType!!.toModel().name })
227+
Descriptors.FieldDescriptor.Type.MESSAGE -> FieldType.Reference(lazy { messageType!!.toModel().name })
181228
Descriptors.FieldDescriptor.Type.GROUP -> error("GROUP type is unsupported")
182229
}
183230

231+
if (isMapField) {
232+
val keyType = messageType.findFieldByName("key").modelType()
233+
val valType = messageType.findFieldByName("value").modelType()
234+
val mapEntry = FieldType.Map.Entry(keyType, valType)
235+
return FieldType.Map(lazy { mapEntry })
236+
}
237+
184238
if (isRepeated) {
185239
return FieldType.List(baseType)
186240
}
187241

188-
// TODO: Handle map type
189-
190242
return baseType
191243
}
192244

193-
//// GenericDescriptor Extensions ////
194-
195-
private fun Descriptors.GenericDescriptor.fqName(): FqName {
196-
val nameCapital = name.simpleProtoNameToKotlin(firstLetterUpper = true)
197-
val nameLower = name.simpleProtoNameToKotlin()
198-
return when (this) {
199-
is Descriptors.FileDescriptor -> FqName.Package.fromString(`package`)
200-
is Descriptors.Descriptor -> FqName.Declaration(nameCapital, containingType?.fqName() ?: file.fqName())
201-
is Descriptors.FieldDescriptor -> {
202-
val usedName = if (realContainingOneof != null) nameCapital else nameLower
203-
FqName.Declaration(usedName, containingType?.fqName() ?: file.fqName())
204-
}
205-
206-
is Descriptors.EnumValueDescriptor -> FqName.Declaration(name, type.fqName())
207-
is Descriptors.OneofDescriptor -> FqName.Declaration(nameCapital, containingType?.fqName() ?: file.fqName())
208-
is Descriptors.ServiceDescriptor -> FqName.Declaration(nameCapital, file?.fqName() ?: file.fqName())
209-
is Descriptors.MethodDescriptor -> FqName.Declaration(nameLower, service?.fqName() ?: file.fqName())
210-
else -> error("Unknown generic descriptor: $this")
211-
}
212-
}
213-
214245
//// Utility Extensions ////
215246

216247
private fun Descriptors.FileDescriptor.kotlinFileName(): String {

0 commit comments

Comments
 (0)