Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gRPC Initial Implementation #262

Merged
merged 6 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
/*
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import util.configureApiValidation
import util.configureNpm
import util.configureProjectReport
import util.libs
import util.configureProjectReport
import util.configureNpm
import util.configureApiValidation

plugins {
alias(libs.plugins.serialization) apply false
alias(libs.plugins.kotlinx.rpc) apply false
alias(libs.plugins.conventions.kover)
alias(libs.plugins.protobuf) apply false
alias(libs.plugins.conventions.gradle.doctor)
alias(libs.plugins.atomicfu)
id("build-util")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.rpc.codegen.extension
Expand Down Expand Up @@ -105,6 +105,14 @@ internal class RpcIrContext(
getRpcIrClassSymbol("RpcServiceDescriptor", "descriptor")
}

val grpcServiceDescriptor by lazy {
getIrClassSymbol("kotlinx.rpc.grpc.descriptor", "GrpcServiceDescriptor")
}

val grpcDelegate by lazy {
getIrClassSymbol("kotlinx.rpc.grpc.descriptor", "GrpcDelegate")
}

val rpcType by lazy {
getRpcIrClassSymbol("RpcType", "descriptor")
}
Expand Down Expand Up @@ -262,6 +270,10 @@ internal class RpcIrContext(
rpcServiceDescriptor.namedProperty("fqName")
}

val grpcServiceDescriptorDelegate by lazy {
grpcServiceDescriptor.namedProperty("delegate")
}

private fun IrClassSymbol.namedProperty(name: String): IrPropertySymbol {
return owner.properties.single { it.name.asString() == name }.symbol
}
Expand All @@ -276,7 +288,7 @@ internal class RpcIrContext(
return getIrClassSymbol("kotlinx.rpc$suffix", name)
}

private fun getIrClassSymbol(packageName: String, name: String): IrClassSymbol {
fun getIrClassSymbol(packageName: String, name: String): IrClassSymbol {
return versionSpecificApi.referenceClass(pluginContext, packageName, name)
?: error("Unable to find symbol. Package: $packageName, name: $name")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.rpc.codegen.extension
Expand All @@ -9,14 +9,17 @@ import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer

internal class RpcIrServiceProcessor(
@Suppress("unused")
private val logger: MessageCollector,
) : IrElementTransformer<RpcIrContext> {
override fun visitClass(declaration: IrClass, data: RpcIrContext): IrStatement {
if (declaration.hasAnnotation(RpcClassId.rpcAnnotation)) {
if ((declaration.hasAnnotation(RpcClassId.rpcAnnotation)
|| declaration.hasAnnotation(RpcClassId.grpcAnnotation)) && declaration.isInterface
) {
processService(declaration, data)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.rpc.codegen.extension
Expand Down Expand Up @@ -44,6 +44,10 @@ private object Descriptor {
const val CREATE_INSTANCE = "createInstance"
}

private object GrpcDescriptor {
const val DELEGATE = "delegate"
}

@Suppress("detekt.LargeClass", "detekt.TooManyFunctions")
internal class RpcStubGenerator(
private val declaration: ServiceDeclaration,
Expand Down Expand Up @@ -123,7 +127,10 @@ internal class RpcStubGenerator(

clientProperty()

coroutineContextProperty()
// not for gRPC
if (!declaration.isGrpc) {
coroutineContextProperty()
}

declaration.fields.forEach {
rpcFlowField(it)
Expand Down Expand Up @@ -550,7 +557,15 @@ internal class RpcStubGenerator(
overriddenSymbols = listOf(method.function.symbol)

body = irBuilder(symbol).irBlockBody {
+irReturn(
val call = if (declaration.isGrpc) {
irRpcMethodClientCall(
method = method,
functionThisReceiver = functionThisReceiver,
isMethodObject = isMethodObject,
methodClass = methodClass,
arguments = arguments,
)
} else {
irCall(
callee = ctx.functions.scopedClientCall,
type = method.function.returnType,
Expand Down Expand Up @@ -600,7 +615,9 @@ internal class RpcStubGenerator(

putValueArgument(1, lambda)
}
)
}

+irReturn(call)
}
}
}
Expand Down Expand Up @@ -868,7 +885,10 @@ internal class RpcStubGenerator(
stubCompanionObjectThisReceiver = thisReceiver
?: error("Stub companion object expected to have thisReceiver: ${name.asString()}")

superTypes = listOf(ctx.rpcServiceDescriptor.typeWith(declaration.serviceType))
superTypes = listOfNotNull(
ctx.rpcServiceDescriptor.typeWith(declaration.serviceType),
if (declaration.isGrpc) ctx.grpcServiceDescriptor.typeWith(declaration.serviceType) else null,
)

generateCompanionObjectConstructor()

Expand Down Expand Up @@ -901,6 +921,10 @@ internal class RpcStubGenerator(
generateCreateInstanceFunction()

generateGetFieldsFunction()

if (declaration.isGrpc) {
generateGrpcDelegateProperty()
}
}

/**
Expand Down Expand Up @@ -1488,6 +1512,43 @@ internal class RpcStubGenerator(
}
}

/**
* override val delegate: GrpcDelegate = MyServiceDelegate
*/
private fun IrClass.generateGrpcDelegateProperty() {
addProperty {
name = Name.identifier(GrpcDescriptor.DELEGATE)
visibility = DescriptorVisibilities.PUBLIC
}.apply {
overriddenSymbols = listOf(ctx.properties.grpcServiceDescriptorDelegate)

addBackingFieldUtil {
visibility = DescriptorVisibilities.PRIVATE
type = ctx.grpcDelegate.defaultType
vsApi { isFinalVS = true }
}.apply {
initializer = factory.createExpressionBody(
IrGetObjectValueImpl(
startOffset = UNDEFINED_OFFSET,
endOffset = UNDEFINED_OFFSET,
type = ctx.grpcDelegate.defaultType,
symbol = ctx.getIrClassSymbol(
declaration.service.packageFqName?.asString()
?: error("Expected package name fro service ${declaration.service.name}"),
"${declaration.service.name.asString()}Delegate",
),
)
)
}

addDefaultGetter(this@generateGrpcDelegateProperty, ctx.irBuiltIns) {
visibility = DescriptorVisibilities.PUBLIC
overriddenSymbols = listOf(ctx.properties.grpcServiceDescriptorDelegate.owner.getterOrFail.symbol)
}
}
}


// Associated object annotation works on JS, WASM, and Native platforms.
// See https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/find-associated-object.html
private fun addAssociatedObjectAnnotationIfPossible() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
/*
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.rpc.codegen.extension

import kotlinx.rpc.codegen.common.RpcClassId
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.util.kotlinFqName

class ServiceDeclaration(
Expand All @@ -18,6 +20,7 @@ class ServiceDeclaration(
val methods: List<Method>,
val fields: List<FlowField>,
) {
val isGrpc = service.hasAnnotation(RpcClassId.grpcAnnotation)
val fqName = service.kotlinFqName.asString()

val serviceType = service.defaultType
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

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

val serializableAnnotation = ClassId(FqName("kotlinx.serialization"), Name.identifier("Serializable"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

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

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

internal class RpcGeneratedRpcMethodClassKey(
val isGrpc: Boolean,
val rpcMethod: FirFunctionSymbol<*>,
) : GeneratedDeclarationKey() {
val isObject = rpcMethod.valueParameterSymbols.isEmpty()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.rpc.codegen
Expand All @@ -25,6 +25,7 @@ class FirRpcAdditionalCheckers(
) : FirAdditionalCheckersExtension(session) {
override fun FirDeclarationPredicateRegistrar.registerPredicates() {
register(FirRpcPredicates.rpc)
register(FirRpcPredicates.grpc)
register(FirRpcPredicates.checkedAnnotationMeta)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ object FirRpcPredicates {
metaAnnotated(RpcClassId.rpcAnnotation.asSingleFqName(), includeItself = true)
}

internal val grpc = DeclarationPredicate.create {
annotated(RpcClassId.grpcAnnotation.asSingleFqName()) // @Grpc
}

internal val checkedAnnotationMeta = DeclarationPredicate.create {
metaAnnotated(RpcClassId.checkedTypeAnnotation.asSingleFqName(), includeItself = false)
}
Expand Down
Loading