Skip to content

Commit 4aeb4d0

Browse files
committed
Compiler plugin tests (#157)
1 parent 25f1441 commit 4aeb4d0

File tree

63 files changed

+4018
-683
lines changed

Some content is hidden

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

63 files changed

+4018
-683
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ build
1818
!.idea/codeStyles/*
1919
!.idea/icon.svg
2020
!.idea/detekt.xml
21+
!.idea/kotlinTestDataPluginTestDataPaths.xml
2122

2223
.DS_Store
2324

.idea/kotlinTestDataPluginTestDataPaths.xml

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build.gradle.kts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ apiValidation {
3030

3131
ignoredProjects.addAll(
3232
listOf(
33-
"codegen-tests-jvm",
34-
"codegen-tests-mpp",
33+
"compiler-plugin-tests",
3534
"krpc-test",
3635
"utils",
3736
)

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

Lines changed: 0 additions & 20 deletions
This file was deleted.

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,19 @@ package kotlinx.rpc.codegen.extension
77
import kotlinx.rpc.codegen.VersionSpecificApi
88
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
99
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
10+
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
1011
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
12+
import org.jetbrains.kotlin.config.CompilerConfiguration
1113
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
1214

13-
internal class RPCIrExtension(
14-
private val logger: MessageCollector,
15-
private val versionSpecificApi: VersionSpecificApi,
16-
) : IrGenerationExtension {
15+
class RPCIrExtension(configuration: CompilerConfiguration) : IrGenerationExtension {
16+
private val logger = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
17+
1718
override fun generate(
1819
moduleFragment: IrModuleFragment,
1920
pluginContext: IrPluginContext,
2021
) {
21-
val context = RPCIrContext(pluginContext, versionSpecificApi)
22+
val context = RPCIrContext(pluginContext, VersionSpecificApi.INSTANCE)
2223

2324
val processor = RPCIrServiceProcessor(logger)
2425
moduleFragment.transform(processor, context)

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package kotlinx.rpc.codegen.extension
66

77
import kotlinx.rpc.codegen.VersionSpecificApi
8+
import kotlinx.rpc.codegen.VersionSpecificApiImpl.copyToVS
89
import kotlinx.rpc.codegen.common.rpcMethodClassName
910
import kotlinx.rpc.codegen.common.rpcMethodClassNameKsp
1011
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
@@ -1274,7 +1275,7 @@ internal class RPCStubGenerator(
12741275
it.name == OperatorNameConventions.EQUALS
12751276
}.symbol
12761277

1277-
dispatchReceiverParameter = anyClass.thisReceiver
1278+
dispatchReceiverParameter = anyClass.copyThisReceiver(this@apply)
12781279

12791280
addValueParameter {
12801281
name = Name.identifier("other")
@@ -1295,7 +1296,7 @@ internal class RPCStubGenerator(
12951296
it.name == OperatorNameConventions.HASH_CODE
12961297
}.symbol
12971298

1298-
dispatchReceiverParameter = anyClass.thisReceiver
1299+
dispatchReceiverParameter = anyClass.copyThisReceiver(this@apply)
12991300
}
13001301

13011302
addFunction {
@@ -1311,10 +1312,13 @@ internal class RPCStubGenerator(
13111312
it.name == OperatorNameConventions.TO_STRING
13121313
}.symbol
13131314

1314-
dispatchReceiverParameter = anyClass.thisReceiver
1315+
dispatchReceiverParameter = anyClass.copyThisReceiver(this@apply)
13151316
}
13161317
}
13171318

1319+
private fun IrClass.copyThisReceiver(function: IrFunction) =
1320+
thisReceiver?.copyToVS(function, origin = IrDeclarationOrigin.DEFINED)
1321+
13181322
private fun stringConst(value: String) = IrConstImpl.string(
13191323
startOffset = UNDEFINED_OFFSET,
13201324
endOffset = UNDEFINED_OFFSET,

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package kotlinx.rpc.codegen
66

7+
import kotlinx.rpc.codegen.extension.RPCIrExtension
78
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
89
import org.jetbrains.kotlin.compiler.plugin.CliOption
910
import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
@@ -21,16 +22,17 @@ class RPCCommandLineProcessor : CommandLineProcessor {
2122

2223
@OptIn(ExperimentalCompilerApi::class)
2324
class RPCCompilerPlugin : CompilerPluginRegistrar() {
24-
init {
25-
VersionSpecificApi.INSTANCE = VersionSpecificApiImpl
26-
}
27-
2825
override val supportsK2: Boolean = true
2926

3027
override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {
31-
val irExtension = RPCIrPlugin.provideExtension(configuration)
32-
33-
IrGenerationExtension.registerExtension(irExtension)
34-
FirExtensionRegistrarAdapter.registerExtension(FirRPCExtensionRegistrar(configuration))
28+
registerRpcExtensions(configuration)
3529
}
3630
}
31+
32+
@OptIn(ExperimentalCompilerApi::class)
33+
fun CompilerPluginRegistrar.ExtensionStorage.registerRpcExtensions(configuration: CompilerConfiguration) {
34+
VersionSpecificApi.INSTANCE = VersionSpecificApiImpl
35+
36+
IrGenerationExtension.registerExtension(RPCIrExtension(configuration))
37+
FirExtensionRegistrarAdapter.registerExtension(FirRPCExtensionRegistrar(configuration))
38+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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
6+
7+
import kotlin.reflect.KProperty
8+
9+
/**
10+
* Thrown when an uninitialized field of an RPC interface is accessed.
11+
*
12+
* Use [awaitFieldInitialization] to await for the field initialization
13+
*/
14+
public class UninitializedRPCFieldException(serviceName: String, property: KProperty<*>): Exception() {
15+
override val message: String = "${property.name} field of RPC service \"$serviceName\" in not initialized"
16+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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
6+
7+
import kotlinx.rpc.internal.RPCDeferredField
8+
import kotlinx.rpc.internal.RPCServiceFieldsProvider
9+
import kotlinx.rpc.internal.findRPCStubProvider
10+
import kotlinx.rpc.internal.safeCast
11+
import kotlin.reflect.KClass
12+
13+
/**
14+
* Waits for the initialization of an RPC field in the generated client:
15+
*
16+
* ```kotlin
17+
* interface MyService : RPC {
18+
* val stateFlow: StateFlow<Int>
19+
* }
20+
*
21+
* val service = rpcClient.withService<MyService>()
22+
* val currentValue = service.awaitFieldInitialization { stateFlow }.value
23+
* ```
24+
*
25+
* @param T service type
26+
* @param R field type
27+
* @param getter function that returns the field of the context service to wait for.
28+
* @return service filed after it was initialized.
29+
*/
30+
public suspend fun <T : RPC, R> T.awaitFieldInitialization(getter: T.() -> R): R {
31+
val field = getter()
32+
33+
if (field is RPCDeferredField<*>) {
34+
@Suppress("UNCHECKED_CAST")
35+
return (field as RPCDeferredField<R>).await()
36+
}
37+
38+
error("Please choose required field for a valid RPC client generated by RPCClient.withService method")
39+
}
40+
41+
/**
42+
* Waits for the initialization of all RPC fields in the generated client:
43+
*
44+
* ```kotlin
45+
* interface MyService : RPC {
46+
* val stateFlow1: StateFlow<Int>
47+
* val stateFlow2: StateFlow<Int>
48+
* }
49+
*
50+
* val service = rpcClient.withService<MyService>()
51+
* val currentValue = service.awaitFieldInitialization()
52+
* // fields `stateFlow1` and `stateFlow2` are initialized
53+
* ```
54+
*
55+
* @param T service type
56+
* @return specified service, after all of it's field were initialized.
57+
*/
58+
public suspend inline fun <reified T : RPC> T.awaitFieldInitialization(): T {
59+
return awaitFieldInitialization(T::class)
60+
}
61+
62+
/**
63+
* Waits for the initialization of all RPC fields in the generated client:
64+
*
65+
* ```kotlin
66+
* interface MyService : RPC {
67+
* val stateFlow1: StateFlow<Int>
68+
* val stateFlow2: StateFlow<Int>
69+
* }
70+
*
71+
* val service = rpcClient.withService<MyService>()
72+
* val currentValue = service.awaitFieldInitialization(MyService::class)
73+
* // fields `stateFlow1` and `stateFlow2` are initialized
74+
* ```
75+
*
76+
* @param T service type
77+
* @param kClass [KClass] of the [T] type.
78+
* @return specified service, after all of it's field were initialized.
79+
*/
80+
public suspend fun <T : RPC> T.awaitFieldInitialization(kClass: KClass<T>): T {
81+
findRPCStubProvider<RPCServiceFieldsProvider<T>>(kClass, RPCServiceFieldsProvider::class.safeCast())
82+
.rpcFields(this)
83+
.forEach { field ->
84+
field.await()
85+
}
86+
87+
return this
88+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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
6+
7+
import kotlinx.atomicfu.atomic
8+
import kotlinx.rpc.internal.RPCStubServiceProvider
9+
import kotlinx.rpc.internal.findRPCStubProvider
10+
import kotlinx.rpc.internal.kClass
11+
import kotlinx.rpc.internal.safeCast
12+
import kotlin.reflect.KClass
13+
import kotlin.reflect.KType
14+
15+
/**
16+
* Creates instance of the generated service [T], that is able to communicate with server using RPCClient.
17+
*
18+
* [awaitFieldInitialization] method can be used on that instance.
19+
*
20+
* @param T the exact type of the service to be created.
21+
* @return instance of the generated service.
22+
*/
23+
public inline fun <reified T : RPC> RPCClient.withService(): T {
24+
return withService(T::class)
25+
}
26+
27+
/**
28+
* Creates instance of the generated service [T], that is able to communicate with server using RPCClient.
29+
*
30+
* [awaitFieldInitialization] method can be used on that instance.
31+
*
32+
* @param T the exact type of the service to be created.
33+
* @param serviceKType [KType] of the service to be created.
34+
* @return instance of the generated service.
35+
*/
36+
public fun <T : RPC> RPCClient.withService(serviceKType: KType): T {
37+
return withService(serviceKType.kClass())
38+
}
39+
40+
/**
41+
* Counter for locally added services.
42+
* Used to differentiate uniques local services, regardless of their type.
43+
*/
44+
private val SERVICE_ID = atomic(0L)
45+
46+
/**
47+
* Creates instance of the generated service [T], that is able to communicate with server using RPCClient.
48+
*
49+
* [awaitFieldInitialization] method can be used on that instance.
50+
*
51+
* @param T the exact type of the service to be created.
52+
* @param serviceKClass [KClass] of the service to be created.
53+
* @return instance of the generated service.
54+
*/
55+
public fun <T : RPC> RPCClient.withService(serviceKClass: KClass<T>): T {
56+
val provider = findRPCStubProvider<RPCStubServiceProvider<T>>(
57+
kClass = serviceKClass,
58+
resultKClass = RPCStubServiceProvider::class.safeCast(),
59+
)
60+
61+
val id = SERVICE_ID.incrementAndGet()
62+
63+
return provider.withClient(id, this)
64+
}

0 commit comments

Comments
 (0)