Skip to content

Commit b703bbc

Browse files
committed
gRPC Initial Implementation (#262)
1 parent 127c521 commit b703bbc

File tree

80 files changed

+3021
-25
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+3021
-25
lines changed

build.gradle.kts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
*/
44

55
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
6-
import util.configureApiValidation
7-
import util.configureNpm
8-
import util.configureProjectReport
96
import util.libs
7+
import util.configureProjectReport
8+
import util.configureNpm
9+
import util.configureApiValidation
1010

1111
plugins {
1212
alias(libs.plugins.serialization) apply false
1313
alias(libs.plugins.kotlinx.rpc) apply false
1414
alias(libs.plugins.conventions.kover)
15+
alias(libs.plugins.protobuf) apply false
1516
alias(libs.plugins.conventions.gradle.doctor)
1617
alias(libs.plugins.atomicfu)
1718
id("build-util")

compiler-plugin/compiler-plugin-backend/src/main/core/kotlinx/rpc/codegen/extension/RpcIrContext.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ internal class RpcIrContext(
105105
getRpcIrClassSymbol("RpcServiceDescriptor", "descriptor")
106106
}
107107

108+
val grpcServiceDescriptor by lazy {
109+
getIrClassSymbol("kotlinx.rpc.grpc.descriptor", "GrpcServiceDescriptor")
110+
}
111+
112+
val grpcDelegate by lazy {
113+
getIrClassSymbol("kotlinx.rpc.grpc.descriptor", "GrpcDelegate")
114+
}
115+
108116
val rpcType by lazy {
109117
getRpcIrClassSymbol("RpcType", "descriptor")
110118
}
@@ -274,6 +282,10 @@ internal class RpcIrContext(
274282
rpcServiceDescriptor.namedProperty("fqName")
275283
}
276284

285+
val grpcServiceDescriptorDelegate by lazy {
286+
grpcServiceDescriptor.namedProperty("delegate")
287+
}
288+
277289
private fun IrClassSymbol.namedProperty(name: String): IrPropertySymbol {
278290
return owner.properties.single { it.name.asString() == name }.symbol
279291
}
@@ -288,7 +300,7 @@ internal class RpcIrContext(
288300
return getIrClassSymbol("kotlinx.rpc$suffix", name)
289301
}
290302

291-
private fun getIrClassSymbol(packageName: String, name: String): IrClassSymbol {
303+
fun getIrClassSymbol(packageName: String, name: String): IrClassSymbol {
292304
return versionSpecificApi.referenceClass(pluginContext, packageName, name)
293305
?: error("Unable to find symbol. Package: $packageName, name: $name")
294306
}

compiler-plugin/compiler-plugin-backend/src/main/core/kotlinx/rpc/codegen/extension/RpcIrServiceProcessor.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ internal class RpcIrServiceProcessor(
1515
private val logger: MessageCollector,
1616
) {
1717
fun visitClass(declaration: IrClass, data: RpcIrContext) {
18-
if (declaration.hasAnnotation(RpcClassId.rpcAnnotation) && declaration.isInterface) {
18+
if ((declaration.hasAnnotation(RpcClassId.rpcAnnotation)
19+
|| declaration.hasAnnotation(RpcClassId.grpcAnnotation)) && declaration.isInterface
20+
) {
1921
processService(declaration, data)
2022
}
2123
}

compiler-plugin/compiler-plugin-backend/src/main/core/kotlinx/rpc/codegen/extension/RpcStubGenerator.kt

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ private object Descriptor {
4343
const val CREATE_INSTANCE = "createInstance"
4444
}
4545

46+
private object GrpcDescriptor {
47+
const val DELEGATE = "delegate"
48+
}
49+
4650
@Suppress("detekt.LargeClass", "detekt.TooManyFunctions")
4751
internal class RpcStubGenerator(
4852
private val declaration: ServiceDeclaration,
@@ -122,7 +126,10 @@ internal class RpcStubGenerator(
122126

123127
clientProperty()
124128

125-
coroutineContextProperty()
129+
// not for gRPC
130+
if (!declaration.isGrpc) {
131+
coroutineContextProperty()
132+
}
126133

127134
declaration.fields.forEach {
128135
rpcFlowField(it)
@@ -581,7 +588,15 @@ internal class RpcStubGenerator(
581588
return@irBlockBody
582589
}
583590

584-
+irReturn(
591+
val call = if (declaration.isGrpc) {
592+
irRpcMethodClientCall(
593+
method = method,
594+
functionThisReceiver = functionThisReceiver,
595+
isMethodObject = isMethodObject,
596+
methodClass = methodClass,
597+
arguments = arguments,
598+
)
599+
} else {
585600
irCall(
586601
callee = ctx.functions.scopedClientCall,
587602
type = method.function.returnType,
@@ -637,7 +652,9 @@ internal class RpcStubGenerator(
637652
}
638653
}
639654
}
640-
)
655+
}
656+
657+
+irReturn(call)
641658
}
642659
}
643660
}
@@ -939,7 +956,10 @@ internal class RpcStubGenerator(
939956
stubCompanionObjectThisReceiver = thisReceiver
940957
?: error("Stub companion object expected to have thisReceiver: ${name.asString()}")
941958

942-
superTypes = listOf(ctx.rpcServiceDescriptor.typeWith(declaration.serviceType))
959+
superTypes = listOfNotNull(
960+
ctx.rpcServiceDescriptor.typeWith(declaration.serviceType),
961+
if (declaration.isGrpc) ctx.grpcServiceDescriptor.typeWith(declaration.serviceType) else null,
962+
)
943963

944964
generateCompanionObjectConstructor()
945965

@@ -972,6 +992,10 @@ internal class RpcStubGenerator(
972992
generateCreateInstanceFunction()
973993

974994
generateGetFieldsFunction()
995+
996+
if (declaration.isGrpc) {
997+
generateGrpcDelegateProperty()
998+
}
975999
}
9761000

9771001
/**
@@ -1645,6 +1669,43 @@ internal class RpcStubGenerator(
16451669
}
16461670
}
16471671

1672+
/**
1673+
* override val delegate: GrpcDelegate = MyServiceDelegate
1674+
*/
1675+
private fun IrClass.generateGrpcDelegateProperty() {
1676+
addProperty {
1677+
name = Name.identifier(GrpcDescriptor.DELEGATE)
1678+
visibility = DescriptorVisibilities.PUBLIC
1679+
}.apply {
1680+
overriddenSymbols = listOf(ctx.properties.grpcServiceDescriptorDelegate)
1681+
1682+
addBackingFieldUtil {
1683+
visibility = DescriptorVisibilities.PRIVATE
1684+
type = ctx.grpcDelegate.defaultType
1685+
vsApi { isFinalVS = true }
1686+
}.apply {
1687+
initializer = factory.createExpressionBody(
1688+
IrGetObjectValueImpl(
1689+
startOffset = UNDEFINED_OFFSET,
1690+
endOffset = UNDEFINED_OFFSET,
1691+
type = ctx.grpcDelegate.defaultType,
1692+
symbol = ctx.getIrClassSymbol(
1693+
declaration.service.packageFqName?.asString()
1694+
?: error("Expected package name fro service ${declaration.service.name}"),
1695+
"${declaration.service.name.asString()}Delegate",
1696+
),
1697+
)
1698+
)
1699+
}
1700+
1701+
addDefaultGetter(this@generateGrpcDelegateProperty, ctx.irBuiltIns) {
1702+
visibility = DescriptorVisibilities.PUBLIC
1703+
overriddenSymbols = listOf(ctx.properties.grpcServiceDescriptorDelegate.owner.getterOrFail.symbol)
1704+
}
1705+
}
1706+
}
1707+
1708+
16481709
// Associated object annotation works on JS, WASM, and Native platforms.
16491710
// See https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/find-associated-object.html
16501711
private fun addAssociatedObjectAnnotationIfPossible() {

compiler-plugin/compiler-plugin-backend/src/main/core/kotlinx/rpc/codegen/extension/ServiceDeclaration.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
/*
2-
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package kotlinx.rpc.codegen.extension
66

7+
import kotlinx.rpc.codegen.common.RpcClassId
78
import org.jetbrains.kotlin.ir.declarations.IrClass
89
import org.jetbrains.kotlin.ir.declarations.IrProperty
910
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
1011
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
1112
import org.jetbrains.kotlin.ir.types.IrType
1213
import org.jetbrains.kotlin.ir.util.defaultType
14+
import org.jetbrains.kotlin.ir.util.hasAnnotation
1315
import org.jetbrains.kotlin.ir.util.kotlinFqName
1416

1517
class ServiceDeclaration(
@@ -18,6 +20,7 @@ class ServiceDeclaration(
1820
val methods: List<Method>,
1921
val fields: List<FlowField>,
2022
) {
23+
val isGrpc = service.hasAnnotation(RpcClassId.grpcAnnotation)
2124
val fqName = service.kotlinFqName.asString()
2225

2326
val serviceType = service.defaultType

compiler-plugin/compiler-plugin-common/src/main/core/kotlinx/rpc/codegen/common/Names.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package kotlinx.rpc.codegen.common
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.name.Name
1212
object RpcClassId {
1313
val remoteServiceInterface = ClassId(FqName("kotlinx.rpc"), Name.identifier("RemoteService"))
1414
val rpcAnnotation = ClassId(FqName("kotlinx.rpc.annotations"), Name.identifier("Rpc"))
15+
val grpcAnnotation = ClassId(FqName("kotlinx.rpc.grpc.annotations"), Name.identifier("Grpc"))
1516
val checkedTypeAnnotation = ClassId(FqName("kotlinx.rpc.annotations"), Name.identifier("CheckedTypeAnnotation"))
1617

1718
val serializableAnnotation = ClassId(FqName("kotlinx.serialization"), Name.identifier("Serializable"))

compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirGenerationKeys.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package kotlinx.rpc.codegen
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.name.Name
1313
import org.jetbrains.kotlinx.serialization.compiler.fir.SerializationPluginKey
1414

1515
internal class RpcGeneratedStubKey(
16+
val isGrpc: Boolean,
1617
private val serviceName: Name,
1718
val functions: List<FirFunctionSymbol<*>>,
1819
) : GeneratedDeclarationKey() {
@@ -25,6 +26,7 @@ internal val FirBasedSymbol<*>.generatedRpcServiceStubKey: RpcGeneratedStubKey?
2526
(origin as? FirDeclarationOrigin.Plugin)?.key as? RpcGeneratedStubKey
2627

2728
internal class RpcGeneratedRpcMethodClassKey(
29+
val isGrpc: Boolean,
2830
val rpcMethod: FirFunctionSymbol<*>,
2931
) : GeneratedDeclarationKey() {
3032
val isObject = rpcMethod.valueParameterSymbols.isEmpty()

compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcAdditionalCheckers.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
33
*/
44

55
package kotlinx.rpc.codegen
@@ -25,6 +25,7 @@ class FirRpcAdditionalCheckers(
2525
) : FirAdditionalCheckersExtension(session) {
2626
override fun FirDeclarationPredicateRegistrar.registerPredicates() {
2727
register(FirRpcPredicates.rpc)
28+
register(FirRpcPredicates.grpc)
2829
register(FirRpcPredicates.checkedAnnotationMeta)
2930
}
3031

compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcPredicates.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ object FirRpcPredicates {
1616
metaAnnotated(RpcClassId.rpcAnnotation.asSingleFqName(), includeItself = true)
1717
}
1818

19+
internal val grpc = DeclarationPredicate.create {
20+
annotated(RpcClassId.grpcAnnotation.asSingleFqName()) // @Grpc
21+
}
22+
1923
internal val checkedAnnotationMeta = DeclarationPredicate.create {
2024
metaAnnotated(RpcClassId.checkedTypeAnnotation.asSingleFqName(), includeItself = false)
2125
}

compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcServiceGenerator.kt

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class FirRpcServiceGenerator(
7373

7474
override fun FirDeclarationPredicateRegistrar.registerPredicates() {
7575
register(FirRpcPredicates.rpc)
76+
register(FirRpcPredicates.grpc)
7677
}
7778

7879
/**
@@ -110,7 +111,7 @@ class FirRpcServiceGenerator(
110111
val rpcMethodClassKey = classSymbol.generatedRpcMethodClassKey
111112

112113
return when {
113-
rpcMethodClassKey != null -> {
114+
rpcMethodClassKey != null && !rpcMethodClassKey.isGrpc -> {
114115
when {
115116
!rpcMethodClassKey.isObject -> setOf(
116117
SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT,
@@ -128,7 +129,10 @@ class FirRpcServiceGenerator(
128129
SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT
129130
}
130131

131-
classSymbol.isInterface && session.predicateBasedProvider.matches(FirRpcPredicates.rpc, classSymbol) -> {
132+
classSymbol.isInterface && (
133+
session.predicateBasedProvider.matches(FirRpcPredicates.rpc, classSymbol) ||
134+
session.predicateBasedProvider.matches(FirRpcPredicates.grpc, classSymbol)
135+
) -> {
132136
setOf(RpcNames.SERVICE_STUB_NAME)
133137
}
134138

@@ -157,7 +161,7 @@ class FirRpcServiceGenerator(
157161
generateRpcMethodClass(owner, name, rpcServiceStubKey)
158162
}
159163

160-
owner.generatedRpcMethodClassKey != null -> {
164+
owner.generatedRpcMethodClassKey != null && owner.generatedRpcMethodClassKey?.isGrpc == false -> {
161165
generateNestedClassLikeDeclarationWithSerialization(owner, name)
162166
}
163167

@@ -189,7 +193,7 @@ class FirRpcServiceGenerator(
189193
val methodName = name.rpcMethodName
190194
val rpcMethod = rpcServiceStubKey.functions.singleOrNull { it.name == methodName }
191195
?: return null
192-
val rpcMethodClassKey = RpcGeneratedRpcMethodClassKey(rpcMethod)
196+
val rpcMethodClassKey = RpcGeneratedRpcMethodClassKey(rpcServiceStubKey.isGrpc, rpcMethod)
193197
val classKind = if (rpcMethodClassKey.isObject) ClassKind.OBJECT else ClassKind.CLASS
194198

195199
val rpcMethodClass = createNestedClass(
@@ -202,13 +206,15 @@ class FirRpcServiceGenerator(
202206
modality = Modality.FINAL
203207
}
204208

205-
rpcMethodClass.addAnnotation(RpcClassId.serializableAnnotation, session)
209+
if (!session.predicateBasedProvider.matches(FirRpcPredicates.grpc, owner)) {
210+
rpcMethodClass.addAnnotation(RpcClassId.serializableAnnotation, session)
211+
}
206212

207213
/**
208214
* Required to pass isSerializableObjectAndNeedsFactory check
209215
* from [SerializationFirSupertypesExtension].
210216
*/
211-
if (!isJvmOrMetadata && rpcMethodClassKey.isObject) {
217+
if (!isJvmOrMetadata && rpcMethodClassKey.isObject && !rpcMethodClassKey.isGrpc) {
212218
rpcMethodClass.replaceSuperTypeRefs(createSerializationFactorySupertype())
213219
}
214220

@@ -260,7 +266,13 @@ class FirRpcServiceGenerator(
260266
private fun generateRpcServiceStubClass(owner: FirClassSymbol<*>): FirRegularClassSymbol? {
261267
val functions = vsApi { owner.declaredFunctionsVS(session) }
262268

263-
return createNestedClass(owner, RpcNames.SERVICE_STUB_NAME, RpcGeneratedStubKey(owner.name, functions)) {
269+
val key = RpcGeneratedStubKey(
270+
isGrpc = session.predicateBasedProvider.matches(FirRpcPredicates.grpc, owner),
271+
serviceName = owner.name,
272+
functions = functions,
273+
)
274+
275+
return createNestedClass(owner, RpcNames.SERVICE_STUB_NAME, key) {
264276
visibility = Visibilities.Public
265277
modality = Modality.FINAL
266278
}.symbol
@@ -294,7 +306,7 @@ class FirRpcServiceGenerator(
294306
context: MemberGenerationContext,
295307
rpcMethodClassKey: RpcGeneratedRpcMethodClassKey,
296308
): Set<Name> {
297-
return if (rpcMethodClassKey.isObject) {
309+
return if (rpcMethodClassKey.isObject && !rpcMethodClassKey.isGrpc) {
298310
// add .serializer() method for a serializable object
299311
serializationExtension.getCallableNamesForClass(classSymbol, context)
300312
} else {

0 commit comments

Comments
 (0)