Skip to content

Commit 59165ab

Browse files
committed
KRPC-142 gRPC Optional Types Support (#295)
* Fix import types in root package cases * KRPC-142 Support Optional Types
1 parent e8b50b6 commit 59165ab

File tree

6 files changed

+190
-44
lines changed

6 files changed

+190
-44
lines changed

protobuf-plugin/src/main/kotlin/kotlinx/rpc/protobuf/CodeGenerator.kt

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,31 @@ open class CodeGenerator(
7171
CodeGenerator(parameters, "$indent$ONE_INDENT", builder, logger).block()
7272
}
7373

74-
internal fun scope(prefix: String, suffix: String = "", block: (CodeGenerator.() -> Unit)? = null) {
74+
internal fun scope(
75+
prefix: String,
76+
suffix: String = "",
77+
nlAfterClosed: Boolean = true,
78+
block: (CodeGenerator.() -> Unit)? = null,
79+
) {
7580
addLine(prefix)
76-
scopeWithSuffix(suffix, block)
81+
scopeWithSuffix(suffix, nlAfterClosed, block)
82+
}
83+
84+
internal fun ifBranch(
85+
prefix: String = "",
86+
condition: String,
87+
ifBlock: (CodeGenerator.() -> Unit),
88+
elseBlock: (CodeGenerator.() -> Unit)? = null,
89+
) {
90+
scope("${prefix}if ($condition)", nlAfterClosed = false, suffix = if (elseBlock != null) " else" else "", block = ifBlock)
91+
scopeWithSuffix(block = elseBlock)
7792
}
7893

79-
private fun scopeWithSuffix(suffix: String = "", block: (CodeGenerator.() -> Unit)? = null) {
94+
private fun scopeWithSuffix(
95+
suffix: String = "",
96+
nlAfterClosed: Boolean = true,
97+
block: (CodeGenerator.() -> Unit)? = null,
98+
) {
8099
if (block == null) {
81100
newLine()
82101
lastIsDeclaration = true
@@ -95,8 +114,11 @@ open class CodeGenerator(
95114
newLine()
96115
append(nested.build().trimEnd())
97116
addLine("}$suffix")
98-
newLine()
99-
lastIsDeclaration = true
117+
if (nlAfterClosed) {
118+
newLine()
119+
}
120+
121+
lastIsDeclaration = nlAfterClosed
100122
}
101123

102124
fun code(code: String) {

protobuf-plugin/src/main/kotlin/kotlinx/rpc/protobuf/ModelToKotlinGenerator.kt

Lines changed: 113 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -30,35 +30,46 @@ class ModelToKotlinGenerator(
3030
)
3131
}
3232

33+
private var currentPackage: FqName = FqName.Package.Root
34+
3335
private fun FileDeclaration.generatePublicKotlinFile(): FileGenerator {
36+
currentPackage = packageName
37+
3438
return file(codeGenerationParameters, logger = logger) {
3539
filename = this@generatePublicKotlinFile.name
36-
packageName = this@generatePublicKotlinFile.packageName.fullName()
37-
packagePath = this@generatePublicKotlinFile.packageName.fullName()
40+
packageName = this@generatePublicKotlinFile.packageName.safeFullName()
41+
packagePath = this@generatePublicKotlinFile.packageName.safeFullName()
3842

3943
dependencies.forEach { dependency ->
40-
importPackage(dependency.packageName.fullName())
44+
importPackage(dependency.packageName.safeFullName())
4145
}
4246

47+
fileOptIns = listOf("ExperimentalRpcApi::class", "InternalRpcApi::class")
48+
4349
generatePublicDeclaredEntities(this@generatePublicKotlinFile)
4450

51+
import("kotlinx.rpc.internal.utils.*")
52+
4553
additionalPublicImports.forEach {
4654
import(it)
4755
}
4856
}
4957
}
5058

5159
private fun FileDeclaration.generateInternalKotlinFile(): FileGenerator {
60+
currentPackage = packageName
61+
5262
return file(codeGenerationParameters, logger = logger) {
5363
filename = this@generateInternalKotlinFile.name
54-
packageName = this@generateInternalKotlinFile.packageName.fullName()
64+
packageName = this@generateInternalKotlinFile.packageName.safeFullName()
5565
packagePath =
56-
this@generateInternalKotlinFile.packageName.fullName().packageNameSuffixed(RPC_INTERNAL_PACKAGE_SUFFIX)
66+
this@generateInternalKotlinFile.packageName.safeFullName()
67+
.packageNameSuffixed(RPC_INTERNAL_PACKAGE_SUFFIX)
5768

5869
fileOptIns = listOf("ExperimentalRpcApi::class", "InternalRpcApi::class")
5970

6071
dependencies.forEach { dependency ->
61-
importPackage(dependency.packageName.fullName())
72+
importPackage(dependency.packageName.safeFullName())
6273
}
6374

6475
generateInternalDeclaredEntities(this@generateInternalKotlinFile)
@@ -87,7 +98,7 @@ class ModelToKotlinGenerator(
8798
}
8899

89100
private fun MessageDeclaration.fields() = actualFields.map {
90-
it.transformToFieldDeclaration() to it.type
101+
it.transformToFieldDeclaration() to it
91102
}
92103

93104
@Suppress("detekt.CyclomaticComplexMethod")
@@ -125,14 +136,22 @@ class ModelToKotlinGenerator(
125136
clazz(
126137
name = "${declaration.name.simpleName}Builder",
127138
declarationType = DeclarationType.Class,
128-
superTypes = listOf(declaration.name.fullName()),
139+
superTypes = listOf(declaration.name.safeFullName()),
129140
) {
130-
declaration.fields().forEach { (fieldDeclaration, type) ->
131-
val value = if (type is FieldType.Reference) {
132-
additionalInternalImports.add("kotlin.properties.Delegates")
133-
"by Delegates.notNull()"
134-
} else {
135-
"= ${type.defaultValue}"
141+
declaration.fields().forEach { (fieldDeclaration, field) ->
142+
val value = when {
143+
field.type is FieldType.Reference && field.nullable -> {
144+
"= null"
145+
}
146+
147+
field.type is FieldType.Reference -> {
148+
additionalInternalImports.add("kotlin.properties.Delegates")
149+
"by Delegates.notNull()"
150+
}
151+
152+
else -> {
153+
"= ${field.type.defaultValue}"
154+
}
136155
}
137156

138157
code("override var $fieldDeclaration $value")
@@ -144,35 +163,55 @@ class ModelToKotlinGenerator(
144163
name = "invoke",
145164
modifiers = "operator",
146165
args = "body: ${declaration.name.simpleName}Builder.() -> Unit",
147-
contextReceiver = "${declaration.name.fullName()}.Companion",
148-
returnType = declaration.name.fullName(),
166+
contextReceiver = "${declaration.name.safeFullName()}.Companion",
167+
returnType = declaration.name.safeFullName(),
149168
) {
150169
code("return ${declaration.name.simpleName}Builder().apply(body)")
151170
}
152171

153-
val platformType = "${declaration.outerClassName.fullName()}.${declaration.name.simpleName}"
172+
val platformType = "${declaration.outerClassName.safeFullName()}.${declaration.name.simpleName}"
154173

155174
function(
156175
name = "toPlatform",
157-
contextReceiver = declaration.name.fullName(),
176+
contextReceiver = declaration.name.safeFullName(),
158177
returnType = platformType,
159178
) {
160179
scope("return $platformType.newBuilder().apply", ".build()") {
161180
declaration.actualFields.forEach { field ->
162-
val call = "this@toPlatform.${field.name}${field.toPlatformCast()}"
163-
code("set${field.name.replaceFirstChar { ch -> ch.uppercase() }}($call)")
181+
val uppercaseName = field.name.replaceFirstChar { ch -> ch.uppercase() }
182+
val setFieldCall = "set$uppercaseName"
183+
184+
if (field.nullable) {
185+
code("this@toPlatform.${field.name}?.let { $setFieldCall(it${field.toPlatformCast()}) }")
186+
} else {
187+
code("$setFieldCall(this@toPlatform.${field.name}${field.toPlatformCast()})")
188+
}
164189
}
165190
}
166191
}
167192

168193
function(
169194
name = "toKotlin",
170195
contextReceiver = platformType,
171-
returnType = declaration.name.fullName(),
196+
returnType = declaration.name.safeFullName(),
172197
) {
173198
scope("return ${declaration.name.simpleName}") {
174199
declaration.actualFields.forEach { field ->
175-
code("${field.name} = this@toKotlin.${field.name}${field.toKotlinCast()}")
200+
val getter = "this@toKotlin.${field.name}${field.toKotlinCast()}"
201+
if (field.nullable) {
202+
ifBranch(
203+
prefix = "${field.name} = ",
204+
condition = "has${field.name.replaceFirstChar { ch -> ch.uppercase() }}()",
205+
ifBlock = {
206+
code(getter)
207+
},
208+
elseBlock = {
209+
code("null")
210+
}
211+
)
212+
} else {
213+
code("${field.name} = $getter")
214+
}
176215
}
177216
}
178217
}
@@ -185,7 +224,11 @@ class ModelToKotlinGenerator(
185224
FieldType.IntegralType.UINT32 -> ".toInt()"
186225
FieldType.IntegralType.UINT64 -> ".toLong()"
187226
FieldType.IntegralType.BYTES -> ".let { bytes -> com.google.protobuf.ByteString.copyFrom(bytes) }"
188-
is FieldType.Reference -> ".toPlatform()"
227+
is FieldType.Reference -> ".toPlatform()".also {
228+
val fq by type.value
229+
importRootDeclarationIfNeeded(fq, "toPlatform", true)
230+
}
231+
189232
else -> ""
190233
}
191234
}
@@ -197,7 +240,11 @@ class ModelToKotlinGenerator(
197240
FieldType.IntegralType.UINT32 -> ".toUInt()"
198241
FieldType.IntegralType.UINT64 -> ".toULong()"
199242
FieldType.IntegralType.BYTES -> ".toByteArray()"
200-
is FieldType.Reference -> ".toKotlin()"
243+
is FieldType.Reference -> ".toKotlin()".also {
244+
val fq by type.value
245+
importRootDeclarationIfNeeded(fq, "toKotlin", true)
246+
}
247+
201248
else -> ""
202249
}
203250
}
@@ -211,7 +258,7 @@ class ModelToKotlinGenerator(
211258
// KRPC-156 Reference Types
212259
is FieldType.Reference -> {
213260
val value by type.value
214-
value.fullName()
261+
value.safeFullName()
215262
}
216263

217264
is FieldType.IntegralType -> {
@@ -230,7 +277,11 @@ class ModelToKotlinGenerator(
230277
else -> {
231278
error("Unsupported type: $type")
232279
}
233-
}
280+
}.withNullability(nullable)
281+
}
282+
283+
private fun String.withNullability(nullable: Boolean): String {
284+
return "$this${if (nullable) "?" else ""}"
234285
}
235286

236287
@Suppress("unused")
@@ -278,11 +329,11 @@ class ModelToKotlinGenerator(
278329

279330
@Suppress("unused")
280331
private fun CodeGenerator.generateInternalEnum(declaration: EnumDeclaration) {
281-
val platformType = "${declaration.outerClassName.fullName()}.${declaration.name.simpleName}"
332+
val platformType = "${declaration.outerClassName.safeFullName()}.${declaration.name.simpleName}"
282333

283334
function(
284335
name = "toPlatform",
285-
contextReceiver = declaration.name.fullName(),
336+
contextReceiver = declaration.name.safeFullName(),
286337
returnType = platformType,
287338
) {
288339
scope("return when (this)") {
@@ -299,7 +350,7 @@ class ModelToKotlinGenerator(
299350
function(
300351
name = "toKotlin",
301352
contextReceiver = platformType,
302-
returnType = declaration.name.fullName(),
353+
returnType = declaration.name.safeFullName(),
303354
) {
304355
scope("return when (this)") {
305356
declaration.aliases.forEach { field ->
@@ -324,8 +375,8 @@ class ModelToKotlinGenerator(
324375
function(
325376
name = method.name,
326377
modifiers = "suspend",
327-
args = "message: ${inputType.name.fullName()}",
328-
returnType = outputType.name.fullName(),
378+
args = "message: ${inputType.name.safeFullName()}",
379+
returnType = outputType.name.safeFullName(),
329380
)
330381
}
331382
}
@@ -337,7 +388,7 @@ class ModelToKotlinGenerator(
337388
modifiers = "private",
338389
name = "${service.name.simpleName}Delegate",
339390
declarationType = DeclarationType.Object,
340-
superTypes = listOf("kotlinx.rpc.grpc.descriptor.GrpcDelegate<${service.name.fullName()}>"),
391+
superTypes = listOf("kotlinx.rpc.grpc.descriptor.GrpcDelegate<${service.name.safeFullName()}>"),
341392
) {
342393
function(
343394
name = "clientProvider",
@@ -351,7 +402,7 @@ class ModelToKotlinGenerator(
351402
function(
352403
name = "definitionFor",
353404
modifiers = "override",
354-
args = "impl: ${service.name.fullName()}",
405+
args = "impl: ${service.name.safeFullName()}",
355406
returnType = "kotlinx.rpc.grpc.ServerServiceDefinition",
356407
) {
357408
scope("return ${service.name.simpleName}ServerDelegate(impl).bindService()")
@@ -364,7 +415,7 @@ class ModelToKotlinGenerator(
364415
name = "${service.name.simpleName}ServerDelegate",
365416
declarationType = DeclarationType.Class,
366417
superTypes = listOf("${service.name.simpleName}GrpcKt.${service.name.simpleName}CoroutineImplBase()"),
367-
constructorArgs = listOf("private val impl: ${service.name.fullName()}"),
418+
constructorArgs = listOf("private val impl: ${service.name.safeFullName()}"),
368419
) {
369420
service.methods.forEach { method ->
370421
val grpcName = method.name.replaceFirstChar { it.lowercase() }
@@ -379,6 +430,9 @@ class ModelToKotlinGenerator(
379430
returnType = outputType.toPlatformMessageType(),
380431
) {
381432
code("return impl.${method.name}(request.toKotlin()).toPlatform()")
433+
434+
importRootDeclarationIfNeeded(inputType.name, "toPlatform", true)
435+
importRootDeclarationIfNeeded(outputType.name, "toKotlin", true)
382436
}
383437
}
384438
}
@@ -415,9 +469,13 @@ class ModelToKotlinGenerator(
415469
scope("return when (call.callableName)") {
416470
service.methods.forEach { method ->
417471
val inputType by method.inputType
472+
val outputType by method.outputType
418473
val grpcName = method.name.replaceFirstChar { it.lowercase() }
419-
val result = "stub.$grpcName((message as ${inputType.name.fullName()}).toPlatform())"
474+
val result = "stub.$grpcName((message as ${inputType.name.safeFullName()}).toPlatform())"
420475
code("\"${method.name}\" -> $result.toKotlin() as R")
476+
477+
importRootDeclarationIfNeeded(inputType.name, "toPlatform", true)
478+
importRootDeclarationIfNeeded(outputType.name, "toKotlin", true)
421479
}
422480

423481
code("else -> error(\"Illegal call: \${call.callableName}\")")
@@ -437,7 +495,26 @@ class ModelToKotlinGenerator(
437495
}
438496

439497
private fun MessageDeclaration.toPlatformMessageType(): String {
440-
return "${outerClassName.fullName()}.${name.simpleName}"
498+
return "${outerClassName.safeFullName()}.${name.simpleName}"
499+
}
500+
501+
private fun FqName.safeFullName(): String {
502+
importRootDeclarationIfNeeded(this)
503+
504+
return fullName()
505+
}
506+
507+
private fun importRootDeclarationIfNeeded(
508+
declaration: FqName,
509+
nameToImport: String = declaration.simpleName,
510+
internalOnly: Boolean = false,
511+
) {
512+
if (declaration.parent == FqName.Package.Root && currentPackage != FqName.Package.Root && nameToImport.isNotBlank()) {
513+
additionalInternalImports.add(nameToImport)
514+
if (!internalOnly) {
515+
additionalPublicImports.add(nameToImport)
516+
}
517+
}
441518
}
442519
}
443520

0 commit comments

Comments
 (0)