Skip to content

Commit ff097c5

Browse files
committed
gRPC Initial Implementation (#262)
1 parent 63719f0 commit ff097c5

File tree

80 files changed

+3024
-28
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

+3024
-28
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: 14 additions & 2 deletions
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.extension
@@ -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
}
@@ -262,6 +270,10 @@ internal class RpcIrContext(
262270
rpcServiceDescriptor.namedProperty("fqName")
263271
}
264272

273+
val grpcServiceDescriptorDelegate by lazy {
274+
grpcServiceDescriptor.namedProperty("delegate")
275+
}
276+
265277
private fun IrClassSymbol.namedProperty(name: String): IrPropertySymbol {
266278
return owner.properties.single { it.name.asString() == name }.symbol
267279
}
@@ -276,7 +288,7 @@ internal class RpcIrContext(
276288
return getIrClassSymbol("kotlinx.rpc$suffix", name)
277289
}
278290

279-
private fun getIrClassSymbol(packageName: String, name: String): IrClassSymbol {
291+
fun getIrClassSymbol(packageName: String, name: String): IrClassSymbol {
280292
return versionSpecificApi.referenceClass(pluginContext, packageName, name)
281293
?: error("Unable to find symbol. Package: $packageName, name: $name")
282294
}

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
@@ -17,7 +17,9 @@ internal class RpcIrServiceProcessor(
1717
private val logger: MessageCollector,
1818
) : IrElementTransformer<RpcIrContext> {
1919
override fun visitClass(declaration: IrClass, data: RpcIrContext): IrStatement {
20-
if (declaration.hasAnnotation(RpcClassId.rpcAnnotation) && declaration.isInterface) {
20+
if ((declaration.hasAnnotation(RpcClassId.rpcAnnotation)
21+
|| declaration.hasAnnotation(RpcClassId.grpcAnnotation)) && declaration.isInterface
22+
) {
2123
processService(declaration, data)
2224
}
2325

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

Lines changed: 66 additions & 5 deletions
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.extension
@@ -44,6 +44,10 @@ private object Descriptor {
4444
const val CREATE_INSTANCE = "createInstance"
4545
}
4646

47+
private object GrpcDescriptor {
48+
const val DELEGATE = "delegate"
49+
}
50+
4751
@Suppress("detekt.LargeClass", "detekt.TooManyFunctions")
4852
internal class RpcStubGenerator(
4953
private val declaration: ServiceDeclaration,
@@ -123,7 +127,10 @@ internal class RpcStubGenerator(
123127

124128
clientProperty()
125129

126-
coroutineContextProperty()
130+
// not for gRPC
131+
if (!declaration.isGrpc) {
132+
coroutineContextProperty()
133+
}
127134

128135
declaration.fields.forEach {
129136
rpcFlowField(it)
@@ -550,7 +557,15 @@ internal class RpcStubGenerator(
550557
overriddenSymbols = listOf(method.function.symbol)
551558

552559
body = irBuilder(symbol).irBlockBody {
553-
+irReturn(
560+
val call = if (declaration.isGrpc) {
561+
irRpcMethodClientCall(
562+
method = method,
563+
functionThisReceiver = functionThisReceiver,
564+
isMethodObject = isMethodObject,
565+
methodClass = methodClass,
566+
arguments = arguments,
567+
)
568+
} else {
554569
irCall(
555570
callee = ctx.functions.scopedClientCall,
556571
type = method.function.returnType,
@@ -600,7 +615,9 @@ internal class RpcStubGenerator(
600615

601616
putValueArgument(1, lambda)
602617
}
603-
)
618+
}
619+
620+
+irReturn(call)
604621
}
605622
}
606623
}
@@ -868,7 +885,10 @@ internal class RpcStubGenerator(
868885
stubCompanionObjectThisReceiver = thisReceiver
869886
?: error("Stub companion object expected to have thisReceiver: ${name.asString()}")
870887

871-
superTypes = listOf(ctx.rpcServiceDescriptor.typeWith(declaration.serviceType))
888+
superTypes = listOfNotNull(
889+
ctx.rpcServiceDescriptor.typeWith(declaration.serviceType),
890+
if (declaration.isGrpc) ctx.grpcServiceDescriptor.typeWith(declaration.serviceType) else null,
891+
)
872892

873893
generateCompanionObjectConstructor()
874894

@@ -901,6 +921,10 @@ internal class RpcStubGenerator(
901921
generateCreateInstanceFunction()
902922

903923
generateGetFieldsFunction()
924+
925+
if (declaration.isGrpc) {
926+
generateGrpcDelegateProperty()
927+
}
904928
}
905929

906930
/**
@@ -1488,6 +1512,43 @@ internal class RpcStubGenerator(
14881512
}
14891513
}
14901514

1515+
/**
1516+
* override val delegate: GrpcDelegate = MyServiceDelegate
1517+
*/
1518+
private fun IrClass.generateGrpcDelegateProperty() {
1519+
addProperty {
1520+
name = Name.identifier(GrpcDescriptor.DELEGATE)
1521+
visibility = DescriptorVisibilities.PUBLIC
1522+
}.apply {
1523+
overriddenSymbols = listOf(ctx.properties.grpcServiceDescriptorDelegate)
1524+
1525+
addBackingFieldUtil {
1526+
visibility = DescriptorVisibilities.PRIVATE
1527+
type = ctx.grpcDelegate.defaultType
1528+
vsApi { isFinalVS = true }
1529+
}.apply {
1530+
initializer = factory.createExpressionBody(
1531+
IrGetObjectValueImpl(
1532+
startOffset = UNDEFINED_OFFSET,
1533+
endOffset = UNDEFINED_OFFSET,
1534+
type = ctx.grpcDelegate.defaultType,
1535+
symbol = ctx.getIrClassSymbol(
1536+
declaration.service.packageFqName?.asString()
1537+
?: error("Expected package name fro service ${declaration.service.name}"),
1538+
"${declaration.service.name.asString()}Delegate",
1539+
),
1540+
)
1541+
)
1542+
}
1543+
1544+
addDefaultGetter(this@generateGrpcDelegateProperty, ctx.irBuiltIns) {
1545+
visibility = DescriptorVisibilities.PUBLIC
1546+
overriddenSymbols = listOf(ctx.properties.grpcServiceDescriptorDelegate.owner.getterOrFail.symbol)
1547+
}
1548+
}
1549+
}
1550+
1551+
14911552
// Associated object annotation works on JS, WASM, and Native platforms.
14921553
// See https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/find-associated-object.html
14931554
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
@@ -76,6 +76,7 @@ class FirRpcServiceGenerator(
7676

7777
override fun FirDeclarationPredicateRegistrar.registerPredicates() {
7878
register(FirRpcPredicates.rpc)
79+
register(FirRpcPredicates.grpc)
7980
}
8081

8182
/**
@@ -113,7 +114,7 @@ class FirRpcServiceGenerator(
113114
val rpcMethodClassKey = classSymbol.generatedRpcMethodClassKey
114115

115116
return when {
116-
rpcMethodClassKey != null -> {
117+
rpcMethodClassKey != null && !rpcMethodClassKey.isGrpc -> {
117118
when {
118119
!rpcMethodClassKey.isObject -> setOf(
119120
SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT,
@@ -131,7 +132,10 @@ class FirRpcServiceGenerator(
131132
SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT
132133
}
133134

134-
classSymbol.isInterface && session.predicateBasedProvider.matches(FirRpcPredicates.rpc, classSymbol) -> {
135+
classSymbol.isInterface && (
136+
session.predicateBasedProvider.matches(FirRpcPredicates.rpc, classSymbol) ||
137+
session.predicateBasedProvider.matches(FirRpcPredicates.grpc, classSymbol)
138+
) -> {
135139
setOf(RpcNames.SERVICE_STUB_NAME)
136140
}
137141

@@ -160,7 +164,7 @@ class FirRpcServiceGenerator(
160164
generateRpcMethodClass(owner, name, rpcServiceStubKey)
161165
}
162166

163-
owner.generatedRpcMethodClassKey != null -> {
167+
owner.generatedRpcMethodClassKey != null && owner.generatedRpcMethodClassKey?.isGrpc == false -> {
164168
generateNestedClassLikeDeclarationWithSerialization(owner, name)
165169
}
166170

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

198202
val rpcMethodClass = createNestedClass(
@@ -205,13 +209,15 @@ class FirRpcServiceGenerator(
205209
modality = Modality.FINAL
206210
}
207211

208-
rpcMethodClass.addAnnotation(RpcClassId.serializableAnnotation, session)
212+
if (!session.predicateBasedProvider.matches(FirRpcPredicates.grpc, owner)) {
213+
rpcMethodClass.addAnnotation(RpcClassId.serializableAnnotation, session)
214+
}
209215

210216
/**
211217
* Required to pass isSerializableObjectAndNeedsFactory check
212218
* from [SerializationFirSupertypesExtension].
213219
*/
214-
if (!isJvmOrMetadata && rpcMethodClassKey.isObject) {
220+
if (!isJvmOrMetadata && rpcMethodClassKey.isObject && !rpcMethodClassKey.isGrpc) {
215221
rpcMethodClass.replaceSuperTypeRefs(createSerializationFactorySupertype())
216222
}
217223

@@ -266,7 +272,13 @@ class FirRpcServiceGenerator(
266272
.filterIsInstance<FirFunction>()
267273
.map { it.symbol }
268274

269-
return createNestedClass(owner, RpcNames.SERVICE_STUB_NAME, RpcGeneratedStubKey(owner.name, functions)) {
275+
val key = RpcGeneratedStubKey(
276+
isGrpc = session.predicateBasedProvider.matches(FirRpcPredicates.grpc, owner),
277+
serviceName = owner.name,
278+
functions = functions,
279+
)
280+
281+
return createNestedClass(owner, RpcNames.SERVICE_STUB_NAME, key) {
270282
visibility = Visibilities.Public
271283
modality = Modality.FINAL
272284
}.symbol
@@ -300,7 +312,7 @@ class FirRpcServiceGenerator(
300312
context: MemberGenerationContext,
301313
rpcMethodClassKey: RpcGeneratedRpcMethodClassKey,
302314
): Set<Name> {
303-
return if (rpcMethodClassKey.isObject) {
315+
return if (rpcMethodClassKey.isObject && !rpcMethodClassKey.isGrpc) {
304316
// add .serializer() method for a serializable object
305317
serializationExtension.getCallableNamesForClass(classSymbol, context)
306318
} else {

0 commit comments

Comments
 (0)