Skip to content

Commit 85695e5

Browse files
committed
Support for @rpc annotation
1 parent 962c724 commit 85695e5

File tree

13 files changed

+255
-50
lines changed

13 files changed

+255
-50
lines changed

compiler-plugin/compiler-plugin-cli/src/main/latest/kotlinx/rpc/codegen/RPCCompilerPlugin.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,5 @@ fun CompilerPluginRegistrar.ExtensionStorage.registerRpcExtensions(configuration
3434
VersionSpecificApi.INSTANCE = VersionSpecificApiImpl
3535

3636
IrGenerationExtension.registerExtension(RPCIrExtension(configuration))
37-
FirExtensionRegistrarAdapter.registerExtension(FirRPCExtensionRegistrar(configuration))
37+
FirExtensionRegistrarAdapter.registerExtension(FirRpcExtensionRegistrar(configuration))
3838
}

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
@@ -8,8 +8,9 @@ import org.jetbrains.kotlin.name.ClassId
88
import org.jetbrains.kotlin.name.FqName
99
import org.jetbrains.kotlin.name.Name
1010

11-
object ClassDeclarations {
11+
object RpcClassId {
1212
val rpcInterface = ClassId(FqName("kotlinx.rpc"), Name.identifier("RPC"))
13+
val rpcAnnotation = ClassId(FqName("kotlinx.rpc"), Name.identifier("Rpc"))
1314

1415
val serializableAnnotation = ClassId(FqName("kotlinx.serialization"), Name.identifier("Serializable"))
1516
val contextualAnnotation = ClassId(FqName("kotlinx.serialization"), Name.identifier("Contextual"))

compiler-plugin/compiler-plugin-k2/src/main/latest/kotlinx/rpc/codegen/FirRPCExtensionRegistrar.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
88
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
99
import org.jetbrains.kotlin.config.CompilerConfiguration
1010
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar
11+
import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension.Factory as CFactory
1112
import org.jetbrains.kotlin.fir.extensions.FirDeclarationGenerationExtension.Factory as GFactory
13+
import org.jetbrains.kotlin.fir.extensions.FirSupertypeGenerationExtension.Factory as SFactory
1214

13-
class FirRPCExtensionRegistrar(private val configuration: CompilerConfiguration) : FirExtensionRegistrar() {
15+
class FirRpcExtensionRegistrar(private val configuration: CompilerConfiguration) : FirExtensionRegistrar() {
1416
override fun ExtensionRegistrarContext.configurePlugin() {
1517
val logger = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
1618

17-
+GFactory { FirRPCServiceGenerator(it, logger) }
19+
+CFactory { FirRpcCheckers(it) }
20+
+GFactory { FirRpcServiceGenerator(it, logger) }
21+
+SFactory { FirRpcSupertypeGenerator(it, logger) }
1822
}
1923
}

compiler-plugin/compiler-plugin-k2/src/main/latest/kotlinx/rpc/codegen/FirRPCServiceGenerator.kt

Lines changed: 21 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
package kotlinx.rpc.codegen
66

7-
import kotlinx.rpc.codegen.common.ClassDeclarations
7+
import kotlinx.rpc.codegen.common.RpcClassId
88
import kotlinx.rpc.codegen.common.RpcNames
99
import kotlinx.rpc.codegen.common.rpcMethodClassName
1010
import kotlinx.rpc.codegen.common.rpcMethodName
@@ -19,9 +19,7 @@ import org.jetbrains.kotlin.fir.FirSession
1919
import org.jetbrains.kotlin.fir.declarations.FirFunction
2020
import org.jetbrains.kotlin.fir.declarations.utils.isInterface
2121
import org.jetbrains.kotlin.fir.declarations.utils.visibility
22-
import org.jetbrains.kotlin.fir.extensions.FirDeclarationGenerationExtension
23-
import org.jetbrains.kotlin.fir.extensions.MemberGenerationContext
24-
import org.jetbrains.kotlin.fir.extensions.NestedClassGenerationContext
22+
import org.jetbrains.kotlin.fir.extensions.*
2523
import org.jetbrains.kotlin.fir.moduleData
2624
import org.jetbrains.kotlin.fir.plugin.*
2725
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
@@ -44,22 +42,7 @@ import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackage
4442
*
4543
* ## General idea
4644
*
47-
* [getNestedClassifiersNames] should return a set of [Name]s to generate for.
48-
* For these names [generateNestedClassLikeDeclaration] can generate some nested classes,
49-
* which is what we need.
50-
*
51-
* But the catch is that we cannot say for sure, if we need to generate a class
52-
* while in [getNestedClassifiersNames], but if we do not return anything
53-
* [generateNestedClassLikeDeclaration] will not be called.
54-
* We need to generate a class if only the current declaration is an RPC interface
55-
* (inherits kotlinx.rpc.RPC). There is no resolved supertypes in [getNestedClassifiersNames],
56-
* But, if the potentially generated class is not referenced anywhere,
57-
* then [generateNestedClassLikeDeclaration] will already have supertypes resolved,
58-
* so we can use this info to check the actual supertypes for RPC interface.
59-
*
60-
* So we always return a class name that may be generated.
61-
* And then, in [generateNestedClassLikeDeclaration] we do the actual check with the resolved supertypes
62-
* and generate a class if needed, otherwise returning null.
45+
* Detect `@Rpc` annotation - generate stub classes.
6346
*
6447
* ## Usage of kotlinx.serialization plugin
6548
*
@@ -68,38 +51,38 @@ import org.jetbrains.kotlinx.serialization.compiler.resolve.SerializationPackage
6851
* We generate classes that are marked `@Serializable`.
6952
* In that case, the serialization plugin will not be able to pick up those classes and process them accordingly.
7053
*
71-
* That's why we have an instance of this plugin, which we call only on our generated classes - [serializationExtension]
54+
* That is why we have an instance of this plugin, which we call only on our generated classes - [serializationExtension]
7255
*
7356
* Not all the methods that we would like to call are public, so we access them via reflection:
7457
* - [generateCompanionDeclaration]
7558
* - [generateSerializerImplClass]
7659
*
7760
* This is basically copying the behavior of the actual plugin but isolated only for our generated classes.
7861
*/
79-
class FirRPCServiceGenerator(
62+
class FirRpcServiceGenerator(
8063
session: FirSession,
8164
@Suppress("unused")
8265
private val logger: MessageCollector,
8366
) : FirDeclarationGenerationExtension(session) {
8467
private val serializationExtension = SerializationFirResolveExtension(session)
8568
private val isJvmOrMetadata = !session.moduleData.platform.run { isJs() || isWasm() || isNative() }
8669

70+
override fun FirDeclarationPredicateRegistrar.registerPredicates() {
71+
register(FirRpcPredicates.rpc)
72+
}
73+
8774
/**
8875
* Generates nested classifiers.
8976
*
9077
* They can be of three kinds:
9178
* - Nested Service Stub class.
9279
* In that case [classSymbol] will not have any RPC-generated [FirClassSymbol.origin].
93-
* Rge only check we do - is we check that the declaration is an interface,
94-
* and return [RpcNames.SERVICE_STUB_NAME].
95-
* We cannot be sure if the declaration is actually an RPC service,
96-
* because superTypes are not resolved during that stage.
97-
* We postpone this check until [generateNestedClassLikeDeclaration].
80+
* The only check we do - presence of the `@Rpc` annotation and return [RpcNames.SERVICE_STUB_NAME].
9881
*
9982
* - Companion object of the service stub and method classes.
10083
* If we generate this companion object, we will have [FirClassSymbol.origin]
10184
* of [classSymbol] be set to [RPCGeneratedStubKey],
102-
* because we are inside the previously generated service stub class.
85+
* because we're inside the previously generated service stub class.
10386
* The same goes for method classes too.
10487
* So we return [SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT]
10588
* and a list of method class names.
@@ -141,7 +124,7 @@ class FirRPCServiceGenerator(
141124
SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT
142125
}
143126

144-
classSymbol.isInterface -> {
127+
classSymbol.isInterface && session.predicateBasedProvider.matches(FirRpcPredicates.rpc, classSymbol) -> {
145128
setOf(RpcNames.SERVICE_STUB_NAME)
146129
}
147130

@@ -158,7 +141,7 @@ class FirRPCServiceGenerator(
158141
override fun generateNestedClassLikeDeclaration(
159142
owner: FirClassSymbol<*>,
160143
name: Name,
161-
context: NestedClassGenerationContext
144+
context: NestedClassGenerationContext,
162145
): FirClassLikeSymbol<*>? {
163146
val rpcServiceStubKey = owner.generatedRpcServiceStubKey
164147
return when {
@@ -214,7 +197,7 @@ class FirRPCServiceGenerator(
214197
modality = Modality.FINAL
215198
}
216199

217-
rpcMethodClass.addAnnotation(ClassDeclarations.serializableAnnotation, session)
200+
rpcMethodClass.addAnnotation(RpcClassId.serializableAnnotation, session)
218201

219202
/**
220203
* Required to pass isSerializableObjectAndNeedsFactory check
@@ -264,18 +247,10 @@ class FirRPCServiceGenerator(
264247
}
265248

266249
/**
267-
* Checks whether the [owner] class is actually an RPC service
268-
* (the supertypes are resolved at this stage,
269-
* as the [RpcNames.SERVICE_STUB_NAME] is not references anywhere)
270-
*
271-
* If the [owner] is an RPC service - generates its service stub.
250+
* Generates [owner]'s service stub.
272251
* Scrapes the functions from the [owner] to generate method classes.
273252
*/
274253
private fun generateRpcServiceStubClass(owner: FirClassSymbol<*>): FirRegularClassSymbol? {
275-
owner.resolvedSuperTypes.find {
276-
it.classId == ClassDeclarations.rpcInterface
277-
} ?: return null
278-
279254
@OptIn(SymbolInternals::class)
280255
val functions = owner.fir.declarations
281256
.filterIsInstance<FirFunction>()
@@ -306,7 +281,7 @@ class FirRPCServiceGenerator(
306281
}
307282

308283
/**
309-
* If the method does not have any parameters, it is an object,
284+
* If the method doesn't have any parameters, it is an object,
310285
* and its only callable names are constructor, and the ones provided by the [serializationExtension].
311286
* Otherwise, the callable names are the names of the method parameters and the constructor.
312287
*/
@@ -322,7 +297,7 @@ class FirRPCServiceGenerator(
322297
rpcMethodClassKey.rpcMethod.valueParameterSymbols.map { it.name }.toSet()
323298
} + SpecialNames.INIT
324299

325-
// ^ init is necessary either way, as serialization does not add it for a serializable object
300+
// ^ init is necessary either way, as serialization doesn't add it for a serializable object
326301
}
327302

328303
override fun generateConstructors(context: MemberGenerationContext): List<FirConstructorSymbol> {
@@ -361,7 +336,7 @@ class FirRPCServiceGenerator(
361336

362337
override fun generateProperties(
363338
callableId: CallableId,
364-
context: MemberGenerationContext?
339+
context: MemberGenerationContext?,
365340
): List<FirPropertySymbol> {
366341
context ?: return emptyList()
367342

@@ -399,14 +374,14 @@ class FirRPCServiceGenerator(
399374
returnType = valueParam.resolvedReturnType,
400375
).apply {
401376
if (valueParam.resolvedReturnType.requiresContextual()) {
402-
addAnnotation(ClassDeclarations.contextualAnnotation, session)
377+
addAnnotation(RpcClassId.contextualAnnotation, session)
403378
}
404379
}.symbol.let(::listOf)
405380
}
406381

407382
private fun ConeKotlinType.requiresContextual(): Boolean {
408383
return when (classId) {
409-
ClassDeclarations.flow, ClassDeclarations.sharedFlow, ClassDeclarations.stateFlow -> true
384+
RpcClassId.flow, RpcClassId.sharedFlow, RpcClassId.stateFlow -> true
410385
else -> false
411386
}
412387
}
@@ -416,7 +391,7 @@ class FirRPCServiceGenerator(
416391
*/
417392
override fun generateFunctions(
418393
callableId: CallableId,
419-
context: MemberGenerationContext?
394+
context: MemberGenerationContext?,
420395
): List<FirNamedFunctionSymbol> {
421396
val owner = context?.owner ?: return emptyList()
422397

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.rpc.codegen
6+
7+
import kotlinx.rpc.codegen.checkers.FirRpcDeclarationCheckers
8+
import org.jetbrains.kotlin.fir.FirSession
9+
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers
10+
import org.jetbrains.kotlin.fir.analysis.extensions.FirAdditionalCheckersExtension
11+
import org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar
12+
13+
class FirRpcCheckers(session: FirSession) : FirAdditionalCheckersExtension(session) {
14+
override fun FirDeclarationPredicateRegistrar.registerPredicates() {
15+
register(FirRpcPredicates.rpc)
16+
}
17+
18+
override val declarationCheckers: DeclarationCheckers = FirRpcDeclarationCheckers
19+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.rpc.codegen
6+
7+
import kotlinx.rpc.codegen.common.RpcClassId
8+
import org.jetbrains.kotlin.fir.extensions.predicate.DeclarationPredicate
9+
10+
object FirRpcPredicates {
11+
internal val rpc = DeclarationPredicate.create {
12+
annotated(RpcClassId.rpcAnnotation.asSingleFqName()) // @Rpc
13+
}
14+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.rpc.codegen
6+
7+
import kotlinx.rpc.codegen.common.RpcClassId
8+
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
9+
import org.jetbrains.kotlin.fir.FirSession
10+
import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration
11+
import org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar
12+
import org.jetbrains.kotlin.fir.extensions.FirSupertypeGenerationExtension
13+
import org.jetbrains.kotlin.fir.extensions.predicateBasedProvider
14+
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
15+
import org.jetbrains.kotlin.fir.types.classId
16+
import org.jetbrains.kotlin.fir.types.constructClassLikeType
17+
import org.jetbrains.kotlin.fir.types.toFirResolvedTypeRef
18+
19+
class FirRpcSupertypeGenerator(
20+
session: FirSession,
21+
@Suppress("unused") private val logger: MessageCollector,
22+
) : FirSupertypeGenerationExtension(session) {
23+
override fun FirDeclarationPredicateRegistrar.registerPredicates() {
24+
register(FirRpcPredicates.rpc)
25+
}
26+
27+
override fun needTransformSupertypes(declaration: FirClassLikeDeclaration): Boolean {
28+
return session.predicateBasedProvider.matches(FirRpcPredicates.rpc, declaration)
29+
}
30+
31+
override fun computeAdditionalSupertypes(
32+
classLikeDeclaration: FirClassLikeDeclaration,
33+
resolvedSupertypes: List<FirResolvedTypeRef>,
34+
typeResolver: TypeResolveService,
35+
): List<FirResolvedTypeRef> {
36+
if (resolvedSupertypes.any { it.type.classId == RpcClassId.rpcInterface }) {
37+
return emptyList()
38+
}
39+
40+
return listOf(
41+
RpcClassId.rpcInterface
42+
.constructClassLikeType(emptyArray(), isNullable = false)
43+
.toFirResolvedTypeRef()
44+
)
45+
}
46+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.rpc.codegen
6+
7+
import kotlinx.rpc.codegen.common.RpcClassId
8+
import org.jetbrains.kotlin.KtSourceElement
9+
import org.jetbrains.kotlin.fir.FirSession
10+
import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId
11+
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
12+
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
13+
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
14+
import org.jetbrains.kotlin.fir.types.classId
15+
16+
fun FirClassSymbol<*>.isRpc(): Boolean = resolvedSuperTypes.any {
17+
it.classId == RpcClassId.rpcInterface
18+
}
19+
20+
fun FirBasedSymbol<*>.rpcAnnotationSource(session: FirSession): KtSourceElement? {
21+
return rpcAnnotation(session)?.source
22+
}
23+
24+
fun FirBasedSymbol<*>.rpcAnnotation(session: FirSession): FirAnnotation? {
25+
return resolvedCompilerAnnotationsWithClassIds.rpcAnnotation(session)
26+
}
27+
28+
fun List<FirAnnotation>.rpcAnnotation(session: FirSession): FirAnnotation? {
29+
return getAnnotationByClassId(RpcClassId.rpcAnnotation, session)
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.rpc.codegen.checkers
6+
7+
import kotlinx.rpc.codegen.FirRpcPredicates
8+
import kotlinx.rpc.codegen.checkers.diagnostics.FirRpcDiagnostics
9+
import kotlinx.rpc.codegen.isRpc
10+
import kotlinx.rpc.codegen.rpcAnnotationSource
11+
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
12+
import org.jetbrains.kotlin.diagnostics.reportOn
13+
import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind
14+
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
15+
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirRegularClassChecker
16+
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
17+
import org.jetbrains.kotlin.fir.declarations.utils.isInterface
18+
import org.jetbrains.kotlin.fir.extensions.predicateBasedProvider
19+
20+
object FirRpcAnnotationChecker : FirRegularClassChecker(MppCheckerKind.Common) {
21+
override fun check(
22+
declaration: FirRegularClass,
23+
context: CheckerContext,
24+
reporter: DiagnosticReporter,
25+
) {
26+
val rpcAnnotated = context.session.predicateBasedProvider.matches(FirRpcPredicates.rpc, declaration)
27+
28+
if (!declaration.isInterface && rpcAnnotated) {
29+
reporter.reportOn(
30+
source = declaration.symbol.rpcAnnotationSource(context.session),
31+
factory = FirRpcDiagnostics.WRONG_RPC_ANNOTATION_TARGET,
32+
context = context,
33+
)
34+
}
35+
36+
if (declaration.symbol.isRpc() && !rpcAnnotated) {
37+
reporter.reportOn(
38+
source = declaration.symbol.rpcAnnotationSource(context.session),
39+
factory = FirRpcDiagnostics.MISSING_RPC_ANNOTATION,
40+
context = context,
41+
)
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)