From da5f36304c4390bf5daa911a7de637da196d43b4 Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Thu, 5 Dec 2024 12:23:27 +0100 Subject: [PATCH] Strict mode (#243) --- .../kotlinx/rpc/codegen/RpcCompilerPlugin.kt | 24 +- .../core/kotlinx/rpc/codegen/common/Names.kt | 11 + .../compiler-plugin-k2/build.gradle.kts | 44 ++ ...heckers.kt => FirRpcAdditionalCheckers.kt} | 17 +- .../rpc/codegen/FirRpcExtensionRegistrar.kt | 3 +- .../rpc/codegen/FirRpcServiceGenerator.kt | 5 +- .../core/kotlinx/rpc/codegen/StrictMode.kt | 142 ++++ ...clarationCheckers.kt => FirRpcCheckers.kt} | 5 +- .../FirRpcServiceDeclarationChecker.kt | 78 ++ .../checkers/FirRpcStrictModeClassChecker.kt | 198 +++++ .../diagnostics/DiagnosticFactories.kt | 46 ++ .../checkers/diagnostics/FirRpcDiagnostics.kt | 24 + .../RpcDiagnosticRendererFactory.kt | 90 +++ .../src/main/kotlin/kotlinx/rpc/Extensions.kt | 94 +++ .../rpc/KotlinCompilerPluginBuilder.kt | 3 +- .../kotlin/kotlinx/rpc/RpcGradlePlugin.kt | 3 + .../main/kotlin/kotlinx/rpc/RpcPluginConst.kt | 2 +- .../kotlin/kotlinx/rpc/compilerPlugins.kt | 25 + tests/compiler-plugin-tests/build.gradle.kts | 15 +- .../test/runners/DiagnosticTestGenerated.java | 12 + .../ExtensionRegistrarConfigurator.kt | 25 +- .../test/services/RpcClasspathProviders.kt | 2 + .../src/testData/box/fields.kt | 6 +- .../testData/diagnostics/rpcService.fir.txt | 128 +++ .../src/testData/diagnostics/rpcService.kt | 44 ++ .../testData/diagnostics/strictMode.fir.txt | 742 ++++++++++++++++++ .../src/testData/diagnostics/strictMode.kt | 86 ++ versions-root/libs.versions.toml | 2 + 28 files changed, 1853 insertions(+), 23 deletions(-) rename compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/{FirRpcCheckers.kt => FirRpcAdditionalCheckers.kt} (75%) create mode 100644 compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/StrictMode.kt rename compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/{FirRpcDeclarationCheckers.kt => FirRpcCheckers.kt} (88%) create mode 100644 compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirRpcServiceDeclarationChecker.kt create mode 100644 compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirRpcStrictModeClassChecker.kt create mode 100644 compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/diagnostics/DiagnosticFactories.kt create mode 100644 gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt create mode 100644 tests/compiler-plugin-tests/src/testData/diagnostics/rpcService.fir.txt create mode 100644 tests/compiler-plugin-tests/src/testData/diagnostics/rpcService.kt create mode 100644 tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.fir.txt create mode 100644 tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.kt diff --git a/compiler-plugin/compiler-plugin-cli/src/main/latest/kotlinx/rpc/codegen/RpcCompilerPlugin.kt b/compiler-plugin/compiler-plugin-cli/src/main/latest/kotlinx/rpc/codegen/RpcCompilerPlugin.kt index 3fae78fd..3d2fb50e 100644 --- a/compiler-plugin/compiler-plugin-cli/src/main/latest/kotlinx/rpc/codegen/RpcCompilerPlugin.kt +++ b/compiler-plugin/compiler-plugin-cli/src/main/latest/kotlinx/rpc/codegen/RpcCompilerPlugin.kt @@ -6,7 +6,7 @@ package kotlinx.rpc.codegen import kotlinx.rpc.codegen.extension.RpcIrExtension import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension -import org.jetbrains.kotlin.compiler.plugin.CliOption +import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi @@ -15,9 +15,25 @@ import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter @OptIn(ExperimentalCompilerApi::class) class RpcCommandLineProcessor : CommandLineProcessor { - override val pluginId = "kotlinx.rpc.compiler-plugin" - - override val pluginOptions = emptyList() + override val pluginId = "kotlinx-rpc" + + override val pluginOptions = listOf( + StrictModeCliOptions.STATE_FLOW, + StrictModeCliOptions.SHARED_FLOW, + StrictModeCliOptions.NESTED_FLOW, + StrictModeCliOptions.STREAM_SCOPED_FUNCTIONS, + StrictModeCliOptions.SUSPENDING_SERVER_STREAMING, + StrictModeCliOptions.NOT_TOP_LEVEL_SERVER_FLOW, + StrictModeCliOptions.FIELDS, + ) + + override fun processOption( + option: AbstractCliOption, + value: String, + configuration: CompilerConfiguration, + ) { + option.processAsStrictModeOption(value, configuration) + } } @OptIn(ExperimentalCompilerApi::class) diff --git a/compiler-plugin/compiler-plugin-common/src/main/core/kotlinx/rpc/codegen/common/Names.kt b/compiler-plugin/compiler-plugin-common/src/main/core/kotlinx/rpc/codegen/common/Names.kt index e19d0fff..c5da2bca 100644 --- a/compiler-plugin/compiler-plugin-common/src/main/core/kotlinx/rpc/codegen/common/Names.kt +++ b/compiler-plugin/compiler-plugin-common/src/main/core/kotlinx/rpc/codegen/common/Names.kt @@ -4,6 +4,7 @@ package kotlinx.rpc.codegen.common +import org.jetbrains.kotlin.name.CallableId import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name @@ -21,6 +22,16 @@ object RpcClassId { val stateFlow = ClassId(FqName("kotlinx.coroutines.flow"), Name.identifier("StateFlow")) } +object RpcCallableId { + val streamScoped = CallableId(FqName("kotlinx.rpc.krpc"), Name.identifier("streamScoped")) + val withStreamScope = CallableId(FqName("kotlinx.rpc.krpc"), Name.identifier("withStreamScope")) + val StreamScope = CallableId(FqName("kotlinx.rpc.krpc"), Name.identifier("StreamScope")) + val invokeOnStreamScopeCompletion = CallableId( + FqName("kotlinx.rpc.krpc"), + Name.identifier("invokeOnStreamScopeCompletion"), + ) +} + object RpcNames { val SERVICE_STUB_NAME: Name = Name.identifier("\$rpcServiceStub") diff --git a/compiler-plugin/compiler-plugin-k2/build.gradle.kts b/compiler-plugin/compiler-plugin-k2/build.gradle.kts index b634ae00..c17c72a9 100644 --- a/compiler-plugin/compiler-plugin-k2/build.gradle.kts +++ b/compiler-plugin/compiler-plugin-k2/build.gradle.kts @@ -2,6 +2,7 @@ * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ +import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode import util.enableContextReceivers import util.otherwise @@ -10,6 +11,49 @@ import util.whenForIde plugins { alias(libs.plugins.conventions.jvm) alias(libs.plugins.compiler.specific.module) + alias(libs.plugins.shadow.jar) +} + +/* +This is a hack to solve the next problem: + +There is PsiElement class that is used in compiler and plugin. + +If we use kotlin-compiler-embeddable.jar to compile the module, +PsiElement is org.jetbrains.kotlin.com.jetbrains.intellij.PsiElement. +And it works ok in the user projects! + +But. + +If we run tests, which use kotlin-compiler.jar, we run into ClassNotFoundException. +Because the class it has in th classpath is com.jetbrains.intellij.PsiElement + +- Alright, we can use kotlin-compiler.jar to compile the plugin. +- No, because the same error now will occur in the user projects, + but it is com.jetbrains.intellij.PsiElement that is not found. + +- Ok, we can use kotlin-compiler-embeddable.jar to compile tests. +- Then we ran into java.lang.VerifyError: Bad type on operand stack, which I have no idea how to fix. + +This solution replaces org.jetbrains.kotlin.com.jetbrains.intellij.PsiElement usages in plugin +with com.jetbrains.intellij.PsiElement only for the tests, fixing both use cases. +It is basically a reverse engineering of what Kotlin does for the embedded jar. + */ +val shadowJar = tasks.named("shadowJar") { + configurations = listOf(project.configurations.compileClasspath.get()) + relocate("org.jetbrains.kotlin.com.intellij.psi", "com.intellij.psi") + + exclude("javaslang/**") + exclude("kotlin/**") + exclude("messages/**") + exclude("misc/**") + exclude("org/**") + + archiveFileName.set("plugin-k2-for-tests.jar") +} + +tasks.jar { + finalizedBy(shadowJar) } kotlin { diff --git a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcCheckers.kt b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcAdditionalCheckers.kt similarity index 75% rename from compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcCheckers.kt rename to compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcAdditionalCheckers.kt index 8d4ef578..aab618a1 100644 --- a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcCheckers.kt +++ b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcAdditionalCheckers.kt @@ -7,6 +7,7 @@ package kotlinx.rpc.codegen import kotlinx.rpc.codegen.checkers.FirCheckedAnnotationHelper import kotlinx.rpc.codegen.checkers.FirRpcDeclarationCheckers import kotlinx.rpc.codegen.checkers.FirRpcExpressionCheckers +import kotlinx.rpc.codegen.checkers.diagnostics.FirRpcStrictModeDiagnostics import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.analysis.checkers.declaration.DeclarationCheckers import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers @@ -16,19 +17,29 @@ import org.jetbrains.kotlin.fir.caches.firCachesFactory import org.jetbrains.kotlin.fir.extensions.FirDeclarationPredicateRegistrar import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol -class FirRpcCheckers(session: FirSession, serializationIsPresent: Boolean) : FirAdditionalCheckersExtension(session) { +class FirRpcAdditionalCheckers( + session: FirSession, + serializationIsPresent: Boolean, + modes: StrictModeAggregator, +) : FirAdditionalCheckersExtension(session) { override fun FirDeclarationPredicateRegistrar.registerPredicates() { register(FirRpcPredicates.rpc) register(FirRpcPredicates.checkedAnnotationMeta) } - private val ctx = FirCheckersContext(session, serializationIsPresent) + private val ctx = FirCheckersContext(session, serializationIsPresent, modes) override val declarationCheckers: DeclarationCheckers = FirRpcDeclarationCheckers(ctx) override val expressionCheckers: ExpressionCheckers = FirRpcExpressionCheckers(ctx) } -class FirCheckersContext(private val session: FirSession, val serializationIsPresent: Boolean) { +class FirCheckersContext( + private val session: FirSession, + val serializationIsPresent: Boolean, + modes: StrictModeAggregator, +) { + val strictModeDiagnostics = FirRpcStrictModeDiagnostics(modes) + val typeParametersCache = session.firCachesFactory.createCache { typeParameter: FirTypeParameterSymbol -> FirCheckedAnnotationHelper.checkedAnnotations(session, typeParameter) } diff --git a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcExtensionRegistrar.kt b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcExtensionRegistrar.kt index 2a8728f9..a29d06e3 100644 --- a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcExtensionRegistrar.kt +++ b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcExtensionRegistrar.kt @@ -16,6 +16,7 @@ import org.jetbrains.kotlin.fir.extensions.FirSupertypeGenerationExtension.Facto class FirRpcExtensionRegistrar(private val configuration: CompilerConfiguration) : FirExtensionRegistrar() { override fun ExtensionRegistrarContext.configurePlugin() { val logger = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE) + val modes = configuration.strictModeAggregator() val serializationIsPresent = try { Class.forName("org.jetbrains.kotlinx.serialization.compiler.fir.SerializationFirResolveExtension") @@ -29,7 +30,7 @@ class FirRpcExtensionRegistrar(private val configuration: CompilerConfiguration) false } - +CFactory { FirRpcCheckers(it, serializationIsPresent) } + +CFactory { FirRpcAdditionalCheckers(it, serializationIsPresent, modes) } +SFactory { FirRpcSupertypeGenerator(it, logger) } } diff --git a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcServiceGenerator.kt b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcServiceGenerator.kt index 1482183d..3dcdd998 100644 --- a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcServiceGenerator.kt +++ b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/FirRpcServiceGenerator.kt @@ -187,9 +187,10 @@ class FirRpcServiceGenerator( owner: FirClassSymbol<*>, name: Name, rpcServiceStubKey: RpcGeneratedStubKey, - ): FirClassLikeSymbol<*> { + ): FirClassLikeSymbol<*>? { val methodName = name.rpcMethodName - val rpcMethod = rpcServiceStubKey.functions.single { it.name == methodName } + val rpcMethod = rpcServiceStubKey.functions.singleOrNull { it.name == methodName } + ?: return null val rpcMethodClassKey = RpcGeneratedRpcMethodClassKey(rpcMethod) val classKind = if (rpcMethodClassKey.isObject) ClassKind.OBJECT else ClassKind.CLASS diff --git a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/StrictMode.kt b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/StrictMode.kt new file mode 100644 index 00000000..b401f3ad --- /dev/null +++ b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/StrictMode.kt @@ -0,0 +1,142 @@ +/* + * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.codegen + +import org.jetbrains.kotlin.compiler.plugin.AbstractCliOption +import org.jetbrains.kotlin.compiler.plugin.CliOption +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.config.CompilerConfigurationKey +import kotlin.text.lowercase + +enum class StrictMode { + NONE, WARNING, ERROR; + + companion object { + fun fromCli(value: String): StrictMode? { + return when (value.lowercase()) { + "none" -> NONE + "warning" -> WARNING + "error" -> ERROR + else -> null + } + } + } +} + +data class StrictModeAggregator( + val stateFlow: StrictMode, + val sharedFlow: StrictMode, + val nestedFlow: StrictMode, + val streamScopedFunctions: StrictMode, + val suspendingServerStreaming: StrictMode, + val notTopLevelServerFlow: StrictMode, + val fields: StrictMode, +) + +object StrictModeConfigurationKeys { + val STATE_FLOW = CompilerConfigurationKey.create("state flow rpc mode") + val SHARED_FLOW = CompilerConfigurationKey.create("shared flow rpc mode") + val NESTED_FLOW = CompilerConfigurationKey.create("nested flow rpc mode") + val STREAM_SCOPED_FUNCTIONS = CompilerConfigurationKey.create("stream scoped rpc mode") + val SUSPENDING_SERVER_STREAMING = CompilerConfigurationKey.create( + "suspending server streaming rpc mode" + ) + val NOT_TOP_LEVEL_SERVER_FLOW = CompilerConfigurationKey.create("not top level server flow rpc mode") + val FIELDS = CompilerConfigurationKey.create("fields rpc mode") +} + +fun CompilerConfiguration.strictModeAggregator(): StrictModeAggregator { + return StrictModeAggregator( + stateFlow = get(StrictModeConfigurationKeys.STATE_FLOW, StrictMode.WARNING), + sharedFlow = get(StrictModeConfigurationKeys.SHARED_FLOW, StrictMode.WARNING), + nestedFlow = get(StrictModeConfigurationKeys.NESTED_FLOW, StrictMode.WARNING), + streamScopedFunctions = get(StrictModeConfigurationKeys.STREAM_SCOPED_FUNCTIONS, StrictMode.NONE), + suspendingServerStreaming = get(StrictModeConfigurationKeys.SUSPENDING_SERVER_STREAMING, StrictMode.NONE), + notTopLevelServerFlow = get(StrictModeConfigurationKeys.NOT_TOP_LEVEL_SERVER_FLOW, StrictMode.WARNING), + fields = get(StrictModeConfigurationKeys.FIELDS, StrictMode.WARNING), + ) +} + +object StrictModeCliOptions { + val STATE_FLOW = CliOption( + optionName = "strict-stateFlow", + valueDescription = VALUE_DESCRIPTION, + description = description("StateFlow"), + required = false, + allowMultipleOccurrences = false, + ) + + val SHARED_FLOW = CliOption( + optionName = "strict-sharedFlow", + valueDescription = VALUE_DESCRIPTION, + description = description("SharedFlow"), + required = false, + allowMultipleOccurrences = false, + ) + + val NESTED_FLOW = CliOption( + optionName = "strict-nested-flow", + valueDescription = VALUE_DESCRIPTION, + description = description("Nested flows"), + required = false, + allowMultipleOccurrences = false, + ) + + val STREAM_SCOPED_FUNCTIONS = CliOption( + optionName = "strict-stream-scope", + valueDescription = VALUE_DESCRIPTION, + description = description("Stream Scopes"), + required = false, + allowMultipleOccurrences = false, + ) + + val SUSPENDING_SERVER_STREAMING = CliOption( + optionName = "strict-suspending-server-streaming", + valueDescription = VALUE_DESCRIPTION, + description = description("suspending server streaming methods"), + required = false, + allowMultipleOccurrences = false, + ) + + val NOT_TOP_LEVEL_SERVER_FLOW = CliOption( + optionName = "strict-not-top-level-server-flow", + valueDescription = VALUE_DESCRIPTION, + description = description("not top-level server streaming declarations"), + required = false, + allowMultipleOccurrences = false, + ) + + val FIELDS = CliOption( + optionName = "strict-fields", + valueDescription = VALUE_DESCRIPTION, + description = description("fields"), + required = false, + allowMultipleOccurrences = false, + ) + + const val VALUE_DESCRIPTION = "none, warning or error" + + fun description(entity: String): String { + return "Diagnostic level for $entity in @Rpc services." + } + + val configurationMapper = mapOf( + STATE_FLOW to StrictModeConfigurationKeys.STATE_FLOW, + SHARED_FLOW to StrictModeConfigurationKeys.SHARED_FLOW, + NESTED_FLOW to StrictModeConfigurationKeys.NESTED_FLOW, + STREAM_SCOPED_FUNCTIONS to StrictModeConfigurationKeys.STREAM_SCOPED_FUNCTIONS, + SUSPENDING_SERVER_STREAMING to StrictModeConfigurationKeys.SUSPENDING_SERVER_STREAMING, + NOT_TOP_LEVEL_SERVER_FLOW to StrictModeConfigurationKeys.NOT_TOP_LEVEL_SERVER_FLOW, + FIELDS to StrictModeConfigurationKeys.FIELDS, + ) +} + +fun AbstractCliOption.processAsStrictModeOption(value: String, configuration: CompilerConfiguration): Boolean { + val key = StrictModeCliOptions.configurationMapper[this] ?: return false + val mode = StrictMode.fromCli(value) ?: return false + + configuration.put(key, mode) + return true +} diff --git a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirRpcDeclarationCheckers.kt b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirRpcCheckers.kt similarity index 88% rename from compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirRpcDeclarationCheckers.kt rename to compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirRpcCheckers.kt index affcbaba..1b3fa83f 100644 --- a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirRpcDeclarationCheckers.kt +++ b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirRpcCheckers.kt @@ -14,8 +14,10 @@ import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExpressionCheckers import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirFunctionCallChecker class FirRpcDeclarationCheckers(ctx: FirCheckersContext) : DeclarationCheckers() { - override val regularClassCheckers: Set = setOf( + override val regularClassCheckers: Set = setOfNotNull( FirRpcAnnotationChecker(ctx), + if (ctx.serializationIsPresent) FirRpcStrictModeClassChecker(ctx) else null, + FirRpcServiceDeclarationChecker(ctx), ) override val classCheckers: Set = setOf( @@ -34,5 +36,6 @@ class FirRpcDeclarationCheckers(ctx: FirCheckersContext) : DeclarationCheckers() class FirRpcExpressionCheckers(ctx: FirCheckersContext) : ExpressionCheckers() { override val functionCallCheckers: Set = setOf( FirCheckedAnnotationFunctionCallChecker(ctx), + FirRpcStrictModeExpressionChecker(ctx), ) } diff --git a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirRpcServiceDeclarationChecker.kt b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirRpcServiceDeclarationChecker.kt new file mode 100644 index 00000000..098021b1 --- /dev/null +++ b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirRpcServiceDeclarationChecker.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.codegen.checkers + +import kotlinx.rpc.codegen.FirCheckersContext +import kotlinx.rpc.codegen.FirRpcPredicates +import kotlinx.rpc.codegen.checkers.diagnostics.FirRpcDiagnostics +import kotlinx.rpc.codegen.common.RpcClassId +import kotlinx.rpc.codegen.vsApi +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.diagnostics.reportOn +import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker +import org.jetbrains.kotlin.fir.declarations.FirClass +import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction +import org.jetbrains.kotlin.fir.declarations.utils.isSuspend +import org.jetbrains.kotlin.fir.extensions.predicateBasedProvider +import org.jetbrains.kotlin.fir.types.coneType + +class FirRpcServiceDeclarationChecker( + @Suppress("unused") + private val ctx: FirCheckersContext, +) : FirClassChecker(MppCheckerKind.Common) { + override fun check( + declaration: FirClass, + context: CheckerContext, + reporter: DiagnosticReporter, + ) { + if (!context.session.predicateBasedProvider.matches(FirRpcPredicates.rpc, declaration)) { + return + } + + if (declaration.typeParameters.isNotEmpty()) { + reporter.reportOn( + source = declaration.source, + factory = FirRpcDiagnostics.TYPE_PARAMETERS_IN_RPC_INTERFACE, + context = context, + ) + } + + declaration.declarations.filterIsInstance().onEach { function -> + if (function.typeParameters.isNotEmpty()) { + reporter.reportOn( + source = function.source, + factory = FirRpcDiagnostics.TYPE_PARAMETERS_IN_RPC_FUNCTION, + context = context, + ) + } + + val returnType = vsApi { function.returnTypeRef.coneType.toClassSymbolVS(context.session) } + ?: return@onEach + + if (returnType.classId != RpcClassId.flow && !function.isSuspend) { + reporter.reportOn( + source = function.source, + factory = FirRpcDiagnostics.NON_SUSPENDING_REQUEST_WITHOUT_STREAMING_RETURN_TYPE, + context = context, + ) + } + } + .groupBy { it.name } + .filter { (_, list) -> list.size > 1 } + .forEach { name, functions -> + functions.forEach { function -> + reporter.reportOn( + source = function.source, + factory = FirRpcDiagnostics.AD_HOC_POLYMORPHISM_IN_RPC_SERVICE, + a = functions.size, + b = name, + context = context, + ) + } + } + } +} diff --git a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirRpcStrictModeClassChecker.kt b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirRpcStrictModeClassChecker.kt new file mode 100644 index 00000000..4c76ee50 --- /dev/null +++ b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/FirRpcStrictModeClassChecker.kt @@ -0,0 +1,198 @@ +/* + * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.codegen.checkers + +import kotlinx.rpc.codegen.FirCheckersContext +import kotlinx.rpc.codegen.FirRpcPredicates +import kotlinx.rpc.codegen.checkers.diagnostics.FirRpcStrictModeDiagnostics +import kotlinx.rpc.codegen.common.RpcCallableId +import kotlinx.rpc.codegen.common.RpcClassId +import kotlinx.rpc.codegen.vsApi +import org.jetbrains.kotlin.KtSourceElement +import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory0 +import org.jetbrains.kotlin.diagnostics.reportOn +import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind +import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirClassChecker +import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirFunctionCallChecker +import org.jetbrains.kotlin.fir.analysis.checkers.extractArgumentsTypeRefAndSource +import org.jetbrains.kotlin.fir.analysis.checkers.toClassLikeSymbol +import org.jetbrains.kotlin.fir.declarations.FirClass +import org.jetbrains.kotlin.fir.declarations.FirProperty +import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction +import org.jetbrains.kotlin.fir.declarations.utils.isSuspend +import org.jetbrains.kotlin.fir.expressions.FirFunctionCall +import org.jetbrains.kotlin.fir.extensions.predicateBasedProvider +import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol +import org.jetbrains.kotlin.fir.scopes.impl.toConeType +import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol +import org.jetbrains.kotlin.fir.types.FirTypeRef +import org.jetbrains.kotlin.fir.types.coneType +import org.jetbrains.kotlin.utils.memoryOptimizedMap +import org.jetbrains.kotlin.utils.memoryOptimizedPlus +import org.jetbrains.kotlinx.serialization.compiler.fir.services.FirSerializablePropertiesProvider +import org.jetbrains.kotlinx.serialization.compiler.fir.services.serializablePropertiesProvider + +class FirRpcStrictModeExpressionChecker( + private val ctx: FirCheckersContext, +) : FirFunctionCallChecker(MppCheckerKind.Common) { + private val streamScopeFunctions = setOf( + RpcCallableId.StreamScope, + RpcCallableId.streamScoped, + RpcCallableId.withStreamScope, + RpcCallableId.invokeOnStreamScopeCompletion, + ) + + override fun check( + expression: FirFunctionCall, + context: CheckerContext, + reporter: DiagnosticReporter, + ) { + expression.calleeReference.toResolvedCallableSymbol()?.let { symbol -> + if (symbol.callableId in streamScopeFunctions) { + ctx.strictModeDiagnostics.STREAM_SCOPE_FUNCTION_IN_RPC?.let { + reporter.reportOn(expression.calleeReference.source, it, context) + } + } + } + } +} + +class FirRpcStrictModeClassChecker(private val ctx: FirCheckersContext) : FirClassChecker(MppCheckerKind.Common) { + override fun check( + declaration: FirClass, + context: CheckerContext, + reporter: DiagnosticReporter, + ) { + if (!context.session.predicateBasedProvider.matches(FirRpcPredicates.rpc, declaration)) { + return + } + + val serializablePropertiesProvider = context.session.serializablePropertiesProvider + declaration.declarations.forEach { declaration -> + when (declaration) { + is FirProperty -> { + ctx.strictModeDiagnostics.FIELD_IN_RPC_SERVICE?.let { + reporter.reportOn(declaration.source, it, context) + } + } + + is FirSimpleFunction -> { + checkFunction(declaration, context, reporter, serializablePropertiesProvider) + } + + else -> {} + } + } + } + + private fun checkFunction( + function: FirSimpleFunction, + context: CheckerContext, + reporter: DiagnosticReporter, + serializablePropertiesProvider: FirSerializablePropertiesProvider, + ) { + fun reportOn(element: KtSourceElement?, checker: FirRpcStrictModeDiagnostics.() -> KtDiagnosticFactory0?) { + reporter.reportOn(element, ctx.strictModeDiagnostics.checker() ?: return, context) + } + + val returnClassSymbol = vsApi { + function.returnTypeRef.coneType.toClassSymbolVS(context.session) + } + + val types = function.valueParameters.memoryOptimizedMap { parameter -> + parameter.source to vsApi { + parameter.returnTypeRef + } + } memoryOptimizedPlus (function.returnTypeRef.source to function.returnTypeRef) + + types.forEach { (source, symbol) -> + checkSerializableTypes>( + context = context, + typeRef = symbol, + serializablePropertiesProvider = serializablePropertiesProvider, + ) { symbol, parents -> + when (symbol.classId) { + RpcClassId.stateFlow -> { + reportOn(source) { STATE_FLOW_IN_RPC_SERVICE } + } + + RpcClassId.sharedFlow -> { + reportOn(source) { SHARED_FLOW_IN_RPC_SERVICE } + } + + RpcClassId.flow -> { + if (parents.any { it.classId == RpcClassId.flow }) { + reportOn(source) { NESTED_STREAMING_IN_RPC_SERVICE } + } else if (parents.isNotEmpty() && parents[0] == returnClassSymbol) { + reportOn(source) { NON_TOP_LEVEL_SERVER_STREAMING_IN_RPC_SERVICE } + } + } + } + + symbol + } + } + + if (returnClassSymbol?.classId == RpcClassId.flow && function.isSuspend) { + reportOn(function.source) { SUSPENDING_SERVER_STREAMING_IN_RPC_SERVICE } + } + } + + private fun checkSerializableTypes( + context: CheckerContext, + typeRef: FirTypeRef, + serializablePropertiesProvider: FirSerializablePropertiesProvider, + parentContext: List = emptyList(), + checker: (FirClassLikeSymbol<*>, List) -> ContextElement?, + ) { + val symbol = typeRef.toClassLikeSymbol(context.session) ?: return + val newElement = checker(symbol, parentContext) + val nextContext = if (newElement != null) { + parentContext memoryOptimizedPlus newElement + } else { + parentContext + } + + if (symbol !is FirClassSymbol<*>) { + return + } + + val extracted = extractArgumentsTypeRefAndSource(typeRef) + .orEmpty() + .withIndex() + .associate { (i, refSource) -> + symbol.typeParameterSymbols[i].toConeType() to refSource.typeRef + } + + val flowProps: List = if (symbol.classId == RpcClassId.flow) { + listOf(extracted.values.toList()[0]!!) + } else { + emptyList() + } + + serializablePropertiesProvider.getSerializablePropertiesForClass(symbol) + .serializableProperties + .mapNotNull { property -> + val resolvedTypeRef = property.propertySymbol.resolvedReturnTypeRef + if (resolvedTypeRef.toClassLikeSymbol(context.session) != null) { + resolvedTypeRef + } else { + extracted[property.propertySymbol.resolvedReturnType] + } + }.memoryOptimizedPlus(flowProps) + .forEach { symbol -> + checkSerializableTypes( + context = context, + typeRef = symbol, + serializablePropertiesProvider = serializablePropertiesProvider, + parentContext = nextContext, + checker = checker, + ) + } + } +} diff --git a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/diagnostics/DiagnosticFactories.kt b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/diagnostics/DiagnosticFactories.kt new file mode 100644 index 00000000..06fe0db5 --- /dev/null +++ b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/diagnostics/DiagnosticFactories.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:Suppress("unused") + +package kotlinx.rpc.codegen.checkers.diagnostics + +import kotlinx.rpc.codegen.StrictMode +import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory0 +import org.jetbrains.kotlin.diagnostics.Severity +import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies +import org.jetbrains.kotlin.utils.DummyDelegate +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KClass +import kotlin.reflect.KProperty + +inline fun modded0(mode: StrictMode): DiagnosticFactory0DelegateProviderOnNull { + return DiagnosticFactory0DelegateProviderOnNull(mode, T::class) +} + +class DiagnosticFactory0DelegateProviderOnNull( + private val mode: StrictMode, + private val psiType: KClass<*>, +) { + operator fun provideDelegate( + @Suppress("unused") + thisRef: Any?, + prop: KProperty<*>, + ): ReadOnlyProperty { + val severity = when (mode) { + StrictMode.ERROR -> Severity.ERROR + StrictMode.WARNING -> Severity.WARNING + StrictMode.NONE -> null + } ?: return DummyDelegate(null) + + return DummyDelegate( + KtDiagnosticFactory0( + name = prop.name, + severity = severity, + defaultPositioningStrategy = SourceElementPositioningStrategies.DEFAULT, + psiType = psiType, + ), + ) + } +} diff --git a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/diagnostics/FirRpcDiagnostics.kt b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/diagnostics/FirRpcDiagnostics.kt index d9a9008c..dc9701e8 100644 --- a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/diagnostics/FirRpcDiagnostics.kt +++ b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/diagnostics/FirRpcDiagnostics.kt @@ -4,11 +4,16 @@ package kotlinx.rpc.codegen.checkers.diagnostics +import kotlinx.rpc.codegen.StrictModeAggregator +import org.jetbrains.kotlin.com.intellij.psi.PsiElement +import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies import org.jetbrains.kotlin.diagnostics.error0 import org.jetbrains.kotlin.diagnostics.error1 +import org.jetbrains.kotlin.diagnostics.error2 import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory import org.jetbrains.kotlin.diagnostics.warning0 import org.jetbrains.kotlin.fir.types.ConeKotlinType +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtAnnotationEntry object FirRpcDiagnostics { @@ -16,8 +21,27 @@ object FirRpcDiagnostics { val MISSING_SERIALIZATION_MODULE by warning0() val WRONG_RPC_ANNOTATION_TARGET by error0() val CHECKED_ANNOTATION_VIOLATION by error1() + val NON_SUSPENDING_REQUEST_WITHOUT_STREAMING_RETURN_TYPE by error0() + val AD_HOC_POLYMORPHISM_IN_RPC_SERVICE by error2() + val TYPE_PARAMETERS_IN_RPC_FUNCTION by error0(SourceElementPositioningStrategies.TYPE_PARAMETERS_LIST) + val TYPE_PARAMETERS_IN_RPC_INTERFACE by error0(SourceElementPositioningStrategies.TYPE_PARAMETERS_LIST) init { RootDiagnosticRendererFactory.registerFactory(RpcDiagnosticRendererFactory) } } + +@Suppress("PropertyName", "detekt.VariableNaming") +class FirRpcStrictModeDiagnostics(val modes: StrictModeAggregator) { + val STATE_FLOW_IN_RPC_SERVICE by modded0(modes.stateFlow) + val SHARED_FLOW_IN_RPC_SERVICE by modded0(modes.sharedFlow) + val NESTED_STREAMING_IN_RPC_SERVICE by modded0(modes.nestedFlow) + val STREAM_SCOPE_FUNCTION_IN_RPC by modded0(modes.streamScopedFunctions) + val SUSPENDING_SERVER_STREAMING_IN_RPC_SERVICE by modded0(modes.suspendingServerStreaming) + val NON_TOP_LEVEL_SERVER_STREAMING_IN_RPC_SERVICE by modded0(modes.notTopLevelServerFlow) + val FIELD_IN_RPC_SERVICE by modded0(modes.fields) + + init { + RootDiagnosticRendererFactory.registerFactory(RpcStrictModeDiagnosticRendererFactory(this)) + } +} diff --git a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/diagnostics/RpcDiagnosticRendererFactory.kt b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/diagnostics/RpcDiagnosticRendererFactory.kt index a6559b93..4c642500 100644 --- a/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/diagnostics/RpcDiagnosticRendererFactory.kt +++ b/compiler-plugin/compiler-plugin-k2/src/main/core/kotlinx/rpc/codegen/checkers/diagnostics/RpcDiagnosticRendererFactory.kt @@ -4,8 +4,11 @@ package kotlinx.rpc.codegen.checkers.diagnostics +import kotlinx.rpc.codegen.StrictMode +import kotlinx.rpc.codegen.StrictModeAggregator import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory +import org.jetbrains.kotlin.diagnostics.rendering.Renderer import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers object RpcDiagnosticRendererFactory : BaseDiagnosticRendererFactory() { @@ -35,5 +38,92 @@ object RpcDiagnosticRendererFactory : BaseDiagnosticRendererFactory() { "must be annotated with {0} or an annotation annotated with {0}.", rendererA = FirDiagnosticRenderers.RENDER_TYPE, ) + + put( + factory = FirRpcDiagnostics.NON_SUSPENDING_REQUEST_WITHOUT_STREAMING_RETURN_TYPE, + message = "Non suspending request function is not allowed for functions that doesn't return Flow.", + ) + + put( + factory = FirRpcDiagnostics.AD_HOC_POLYMORPHISM_IN_RPC_SERVICE, + message = "Ad-hoc polymorphism is not allowed in @Rpc services. Found {0} '{1}' functions.", + rendererA = Renderer { it.toString() }, + rendererB = Renderer { it.asString() }, + ) + + put( + factory = FirRpcDiagnostics.TYPE_PARAMETERS_IN_RPC_FUNCTION, + message = "Type parameters are not allowed in Rpc functions.", + ) + + put( + factory = FirRpcDiagnostics.TYPE_PARAMETERS_IN_RPC_INTERFACE, + message = "Type parameters are not allowed in @Rpc interfaces.", + ) + } +} + +class RpcStrictModeDiagnosticRendererFactory( + private val diagnostics: FirRpcStrictModeDiagnostics, +) : BaseDiagnosticRendererFactory() { + override val MAP = KtDiagnosticFactoryToRendererMap("Rpc").apply { + diagnostics.STATE_FLOW_IN_RPC_SERVICE?.let { + put( + factory = it, + message = message("StateFlow") { stateFlow }, + ) + } + + diagnostics.SHARED_FLOW_IN_RPC_SERVICE?.let { + put( + factory = it, + message = message("SharedFlow") { sharedFlow }, + ) + } + + diagnostics.NESTED_STREAMING_IN_RPC_SERVICE?.let { + put( + factory = it, + message = message("Nested streaming") { nestedFlow }, + ) + } + + diagnostics.STREAM_SCOPE_FUNCTION_IN_RPC?.let { + put( + factory = it, + message = message("Stream scope usage") { streamScopedFunctions }, + ) + } + + diagnostics.SUSPENDING_SERVER_STREAMING_IN_RPC_SERVICE?.let { + put( + factory = it, + message = message("Suspend function declaration with server streaming") { suspendingServerStreaming }, + ) + } + + diagnostics.NON_TOP_LEVEL_SERVER_STREAMING_IN_RPC_SERVICE?.let { + put( + factory = it, + message = message("Not top-level server-side streaming") { sharedFlow }, + ) + } + + diagnostics.FIELD_IN_RPC_SERVICE?.let { + put( + factory = it, + message = message("Field declaration") { fields }, + ) + } + } + + private fun message(entityName: String, selector: StrictModeAggregator.() -> StrictMode): String { + val actionWord = when (diagnostics.modes.selector()) { + StrictMode.NONE -> "" + StrictMode.WARNING -> "deprecated" + StrictMode.ERROR -> "prohibited" + } + + return "$entityName is $actionWord in @Rpc services in strict mode." } } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt new file mode 100644 index 00000000..d2c535e4 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/Extensions.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:Suppress("unused") + +package kotlinx.rpc + +import org.gradle.api.Action +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.kotlin.dsl.newInstance +import javax.inject.Inject + +open class RpcExtension @Inject constructor(objects: ObjectFactory) { + /** + * Strict mode settings. + * Allows configuring the reporting state of deprecated features. + */ + val strict: RpcStrictModeExtension = objects.newInstance() + + /** + * Strict mode settings. + * Allows configuring the reporting state of deprecated features. + */ + fun strict(configure: Action) { + configure.execute(strict) + } +} + +open class RpcStrictModeExtension @Inject constructor(objects: ObjectFactory) { + /** + * `StateFlow`s in RPC services are deprecated, + * due to their error-prone nature. + * + * Consider using plain flows and converting them to state on the application side. + */ + val stateFlow: Property = objects.strictModeProperty() + + /** + * `SharedFlow`s in RPC services are deprecated, + * due to their error-prone nature. + * + * Consider using plain flows and converting them to state on the application side. + */ + val sharedFlow: Property = objects.strictModeProperty() + + /** + * Nested flows in RPC services are deprecated, + * due to their error-prone nature. + * + * Consider using plain flows and converting them to state on the application side. + */ + val nestedFlow: Property = objects.strictModeProperty() + + /** + * WIP: https://youtrack.jetbrains.com/issue/KRPC-133 + * Will be enabled later, when an alternative is ready. + */ + private val streamScopedFunctions: Property = objects.strictModeProperty(RpcStrictMode.NONE) + + /** + * WIP: https://youtrack.jetbrains.com/issue/KRPC-133 + * Will be enabled later, when an alternative is ready. + */ + private val suspendingServerStreaming: Property = objects.strictModeProperty(RpcStrictMode.NONE) + + /** + * Not top-level flows in the return value are deprecated in RPC for streaming. + * + * Consider returning a Flow and requesting other data in a different method. + */ + val notTopLevelServerFlow: Property = objects.strictModeProperty() + + /** + * Fields in RPC services are deprecated, + * due to its error-prone nature. + * + * Consider using regular streaming. + */ + val fields: Property = objects.strictModeProperty() + + private fun ObjectFactory.strictModeProperty( + default: RpcStrictMode = RpcStrictMode.WARNING, + ): Property { + return property(RpcStrictMode::class.java).convention(default) + } +} + +enum class RpcStrictMode { + NONE, WARNING, ERROR; + + fun toCompilerArg(): String = name.lowercase() +} diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/KotlinCompilerPluginBuilder.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/KotlinCompilerPluginBuilder.kt index 831dcfcb..b723c82a 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/KotlinCompilerPluginBuilder.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/KotlinCompilerPluginBuilder.kt @@ -20,6 +20,7 @@ class KotlinCompilerPluginBuilder { var applyToCompilation: (kotlinCompilation: KotlinCompilation<*>) -> Provider> = { it.target.project.provider { emptyList() } } + var pluginId: String = PLUGIN_ID var groupId = GROUP_ID var artifactId: String? = null @@ -43,7 +44,7 @@ class KotlinCompilerPluginBuilder { } override fun getCompilerPluginId(): String { - return pluginId + pluginSuffix + return pluginId } override fun getPluginArtifact(): SubpluginArtifact { diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcGradlePlugin.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcGradlePlugin.kt index 67efffba..96ccff1a 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcGradlePlugin.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcGradlePlugin.kt @@ -6,10 +6,13 @@ package kotlinx.rpc import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.kotlin.dsl.create @Suppress("unused") class RpcGradlePlugin : Plugin { override fun apply(target: Project) { + target.extensions.create("rpc") + applyCompilerPlugin(target) } diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcPluginConst.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcPluginConst.kt index f5ae75d0..387d021b 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcPluginConst.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/RpcPluginConst.kt @@ -11,7 +11,7 @@ import org.jetbrains.kotlin.gradle.utils.loadPropertyFromResources object RpcPluginConst { const val GROUP_ID = "org.jetbrains.kotlinx" - const val PLUGIN_ID = "org.jetbrains.kotlinx.rpc.plugin" + const val PLUGIN_ID = "kotlinx-rpc" const val COMPILER_PLUGIN_ARTIFACT_ID = "kotlinx-rpc-compiler-plugin" const val INTERNAL_DEVELOPMENT_PROPERTY = "kotlinx.rpc.plugin.internalDevelopment" diff --git a/gradle-plugin/src/main/kotlin/kotlinx/rpc/compilerPlugins.kt b/gradle-plugin/src/main/kotlin/kotlinx/rpc/compilerPlugins.kt index 17c88d66..496fbbd7 100644 --- a/gradle-plugin/src/main/kotlin/kotlinx/rpc/compilerPlugins.kt +++ b/gradle-plugin/src/main/kotlin/kotlinx/rpc/compilerPlugins.kt @@ -6,7 +6,9 @@ package kotlinx.rpc +import org.gradle.kotlin.dsl.findByType import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin +import org.jetbrains.kotlin.gradle.plugin.SubpluginOption class CompilerPluginK2 : KotlinCompilerPluginSupportPlugin by compilerPlugin({ pluginSuffix = "-k2" @@ -22,4 +24,27 @@ class CompilerPluginBackend : KotlinCompilerPluginSupportPlugin by compilerPlugi class CompilerPluginCli : KotlinCompilerPluginSupportPlugin by compilerPlugin({ pluginSuffix = "-cli" + + applyToCompilation = { + val extension = it.target.project.extensions.findByType() + ?: RpcExtension(it.target.project.objects) + + val strict = extension.strict + + it.target.project.provider { + listOf( + SubpluginOption("strict-stateFlow", strict.stateFlow.get().toCompilerArg()), + SubpluginOption("strict-sharedFlow", strict.sharedFlow.get().toCompilerArg()), + SubpluginOption("strict-nested-flow", strict.nestedFlow.get().toCompilerArg()), + // WIP: https://youtrack.jetbrains.com/issue/KRPC-133 +// SubpluginOption("strict-stream-scope", strict.streamScopedFunctions.get().toCompilerArg()), +// SubpluginOption( +// "strict-suspending-server-streaming", +// strict.suspendingServerStreaming.get().toCompilerArg() +// ), + SubpluginOption("strict-not-top-level-server-flow", strict.notTopLevelServerFlow.get().toCompilerArg()), + SubpluginOption("strict-fields", strict.fields.get().toCompilerArg()), + ) + } + } }) diff --git a/tests/compiler-plugin-tests/build.gradle.kts b/tests/compiler-plugin-tests/build.gradle.kts index a6e19406..9ec8d0d3 100644 --- a/tests/compiler-plugin-tests/build.gradle.kts +++ b/tests/compiler-plugin-tests/build.gradle.kts @@ -27,6 +27,8 @@ kotlin { val testDataClasspath: Configuration by configurations.creating val testRuntimeClasspath: Configuration by project.configurations.getting +val globalRootDir: String by extra + /** * I should probably explain this. * @@ -36,7 +38,7 @@ val testRuntimeClasspath: Configuration by project.configurations.getting * * `NioFiles` is problematic. * It was packed with kotlin-compiler jar, but Proguard which excluded `deleteRecursively` method from it. - * It this method is called. + * And this method is called. * So tests fail with: * ``` * java.lang.NoSuchMethodError: com.intellij.openapi.util.io.NioFiles.deleteRecursively(Ljava/nio/file/Path;)V @@ -62,7 +64,6 @@ sourceSets.test.configure { } dependencies { - @Suppress("UnstableApiUsage") testPriorityRuntimeClasspath(libs.intellij.util) { isTransitive = false } implementation(projects.core) @@ -79,7 +80,10 @@ dependencies { testImplementation(libs.serialization.plugin) } - testImplementation(libs.compiler.plugin.cli) + testImplementation(libs.compiler.plugin.cli) { + exclude(group = "org.jetbrains.kotlinx", module = "compiler-plugin-k2") + } + testImplementation(files("$globalRootDir/compiler-plugin/compiler-plugin-k2/build/libs/plugin-k2-for-tests.jar")) testImplementation(libs.kotlin.reflect) testImplementation(libs.kotlin.compiler) @@ -99,14 +103,13 @@ dependencies { testDataClasspath(libs.serialization.core) } -val globalRootDir: String by extra - val updateTestData = (project.findProperty("kotlin.test.update.test.data") as? String) ?: "false" tasks.test { dependsOn(tasks.getByName("jar")) dependsOn(project(":core").tasks.getByName("jvmJar")) dependsOn(project(":utils").tasks.getByName("jvmJar")) + dependsOn(project(":krpc:krpc-core").tasks.getByName("jvmJar")) useJUnitPlatform() @@ -154,7 +157,7 @@ fun Test.setJarPathAsProperty( val path = testRuntimeClasspath .files - .first { includedRegex.matches(it.name) } + .firstOrNull { includedRegex.matches(it.name) } ?: run { logger.warn("Can't find $jarName in testRuntimeClasspath configuration") return diff --git a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java index c5473600..995639c6 100644 --- a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java +++ b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/DiagnosticTestGenerated.java @@ -35,4 +35,16 @@ public void testCheckedAnnotation() { public void testRpcChecked() { runTest("src/testData/diagnostics/rpcChecked.kt"); } + + @Test + @TestMetadata("rpcService.kt") + public void testRpcService() { + runTest("src/testData/diagnostics/rpcService.kt"); + } + + @Test + @TestMetadata("strictMode.kt") + public void testStrictMode() { + runTest("src/testData/diagnostics/strictMode.kt"); + } } diff --git a/tests/compiler-plugin-tests/src/test/kotlin/kotlinx/rpc/codegen/test/services/ExtensionRegistrarConfigurator.kt b/tests/compiler-plugin-tests/src/test/kotlin/kotlinx/rpc/codegen/test/services/ExtensionRegistrarConfigurator.kt index 0b7a3850..22a1814a 100644 --- a/tests/compiler-plugin-tests/src/test/kotlin/kotlinx/rpc/codegen/test/services/ExtensionRegistrarConfigurator.kt +++ b/tests/compiler-plugin-tests/src/test/kotlin/kotlinx/rpc/codegen/test/services/ExtensionRegistrarConfigurator.kt @@ -4,22 +4,45 @@ package kotlinx.rpc.codegen.test.services +import kotlinx.rpc.codegen.StrictMode +import kotlinx.rpc.codegen.StrictModeConfigurationKeys import kotlinx.rpc.codegen.registerRpcExtensions import org.jetbrains.kotlin.compiler.plugin.CompilerPluginRegistrar import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.test.directives.model.DirectiveApplicability +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.services.EnvironmentConfigurator import org.jetbrains.kotlin.test.services.TestServices import org.jetbrains.kotlinx.serialization.compiler.extensions.SerializationComponentRegistrar class ExtensionRegistrarConfigurator(testServices: TestServices) : EnvironmentConfigurator(testServices) { + override val directiveContainers: List = listOf(RpcDirectives) + override fun CompilerPluginRegistrar.ExtensionStorage.registerCompilerExtensions( module: TestModule, - configuration: CompilerConfiguration + configuration: CompilerConfiguration, ) { + val strictMode = module.directives[RpcDirectives.RPC_STRICT_MODE] + if (strictMode.isNotEmpty()) { + val mode = StrictMode.fromCli(strictMode.single()) ?: StrictMode.WARNING + configuration.put(StrictModeConfigurationKeys.STATE_FLOW, mode) + configuration.put(StrictModeConfigurationKeys.SHARED_FLOW, mode) + configuration.put(StrictModeConfigurationKeys.NESTED_FLOW, mode) + configuration.put(StrictModeConfigurationKeys.STREAM_SCOPED_FUNCTIONS, mode) + configuration.put(StrictModeConfigurationKeys.SUSPENDING_SERVER_STREAMING, mode) + configuration.put(StrictModeConfigurationKeys.NOT_TOP_LEVEL_SERVER_FLOW, mode) + configuration.put(StrictModeConfigurationKeys.FIELDS, mode) + } + registerRpcExtensions(configuration) // libs SerializationComponentRegistrar.registerExtensions(this) } } + +object RpcDirectives : SimpleDirectivesContainer() { + val RPC_STRICT_MODE by stringDirective("none, warning or error", DirectiveApplicability.Module) +} diff --git a/tests/compiler-plugin-tests/src/test/kotlin/kotlinx/rpc/codegen/test/services/RpcClasspathProviders.kt b/tests/compiler-plugin-tests/src/test/kotlin/kotlinx/rpc/codegen/test/services/RpcClasspathProviders.kt index 9856baae..9cc14f0b 100644 --- a/tests/compiler-plugin-tests/src/test/kotlin/kotlinx/rpc/codegen/test/services/RpcClasspathProviders.kt +++ b/tests/compiler-plugin-tests/src/test/kotlin/kotlinx/rpc/codegen/test/services/RpcClasspathProviders.kt @@ -28,6 +28,7 @@ private class RuntimeDependency( private object RpcClasspathProvider { private val TEST_RUNTIME = RuntimeDependency("build/libs/", "compiler-plugin-test") + private val KRPC_CORE_JVM = RuntimeDependency("$globalRootDir/krpc/krpc-core/build/libs/", "krpc-core-jvm") private val CORE_JVM = RuntimeDependency("$globalRootDir/core/build/libs/", "core-jvm") private val UTILS_JVM = RuntimeDependency("$globalRootDir/utils/build/libs/", "utils-jvm") @@ -41,6 +42,7 @@ private object RpcClasspathProvider { val additionalDependencies = listOf( TEST_RUNTIME, CORE_JVM, + KRPC_CORE_JVM, UTILS_JVM, ).map { it.getFile(testServices) } diff --git a/tests/compiler-plugin-tests/src/testData/box/fields.kt b/tests/compiler-plugin-tests/src/testData/box/fields.kt index 0b08d675..59fd99ec 100644 --- a/tests/compiler-plugin-tests/src/testData/box/fields.kt +++ b/tests/compiler-plugin-tests/src/testData/box/fields.kt @@ -11,11 +11,11 @@ import kotlinx.rpc.codegen.test.TestRpcClient @Rpc interface BoxService : RemoteService { - val plainFlow: Flow + val plainFlow: Flow - val sharedFlow: SharedFlow + val sharedFlow: SharedFlow - val stateFlow: StateFlow + val stateFlow: StateFlow } fun box(): String = runBlocking { diff --git a/tests/compiler-plugin-tests/src/testData/diagnostics/rpcService.fir.txt b/tests/compiler-plugin-tests/src/testData/diagnostics/rpcService.fir.txt new file mode 100644 index 00000000..36f04f50 --- /dev/null +++ b/tests/compiler-plugin-tests/src/testData/diagnostics/rpcService.fir.txt @@ -0,0 +1,128 @@ +FILE: rpcService.kt + @FILE:R|kotlin/OptIn|(markerClass = vararg((Q|kotlinx/rpc/internal/utils/ExperimentalRpcApi|))) + @R|kotlinx/rpc/annotations/Rpc|() public abstract interface MyService : R|kotlin/Any|, R|kotlinx/rpc/RemoteService| { + public abstract fun hello(): R|kotlin/Unit| + + public abstract suspend fun generic(a: R|T|): R|kotlin/Unit| + + public abstract suspend fun |> generic2(a: R|T|, b: R|kotlin/Int|, c: R|T4|, t2: R|T2|): R|T3| + + public abstract suspend fun sameName(): R|kotlin/Unit| + + public abstract suspend fun sameName(a: R|kotlin/Int|): R|kotlin/Unit| + + public abstract suspend fun sameName(a: R|kotlin/Int|, b: R|kotlin/Int|): R|kotlin/Unit| + + public abstract suspend fun sameName(a: R|kotlin/Int|, b: R|kotlin/Int|, c: R|kotlin/Int|): R|kotlin/Unit| + + public abstract suspend fun sameName2(): R|kotlin/Unit| + + public abstract suspend fun sameName2(a: R|kotlin/Int|): R|kotlin/Unit| + + public final class $rpcServiceStub : R|kotlin/Any| { + @R|kotlinx/serialization/Serializable|() public final object hello$rpcMethod : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.hello$rpcMethod| { + super() + } + + } + + @R|kotlinx/serialization/Serializable|() public final class generic$rpcMethod : R|kotlin/Any| { + public final val a: R|T| + public get(): R|T| + + public constructor(a: R|T|): R|MyService.$rpcServiceStub.generic$rpcMethod| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.generic$rpcMethod.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MyService.$rpcServiceStub.generic$rpcMethod|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MyService.$rpcServiceStub.generic$rpcMethod| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MyService.$rpcServiceStub.generic$rpcMethod.$serializer| { + super() + } + + } + + } + + @R|kotlinx/serialization/Serializable|() public final class generic2$rpcMethod : R|kotlin/Any| { + public final val a: R|T| + public get(): R|T| + + public final val b: R|kotlin/Int| + public get(): R|kotlin/Int| + + public final val c: R|T4| + public get(): R|T4| + + public final val t2: R|T2| + public get(): R|T2| + + public constructor(a: R|T|, b: R|kotlin/Int|, c: R|T4|, t2: R|T2|): R|MyService.$rpcServiceStub.generic2$rpcMethod| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.generic2$rpcMethod.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MyService.$rpcServiceStub.generic2$rpcMethod|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MyService.$rpcServiceStub.generic2$rpcMethod| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MyService.$rpcServiceStub.generic2$rpcMethod.$serializer| { + super() + } + + } + + } + + public final companion object Companion : R|kotlin/Any| { + } + + } + + } + @R|kotlinx/rpc/annotations/Rpc|() public abstract interface MyServiceT : R|kotlin/Any|, R|kotlinx/rpc/RemoteService| { + public final class $rpcServiceStub : R|kotlin/Any| { + public final companion object Companion : R|kotlin/Any| { + } + + } + + } + @R|kotlinx/rpc/annotations/Rpc|() public abstract interface MyServiceT2 : R|kotlin/Any|, R|kotlinx/rpc/RemoteService| { + public final class $rpcServiceStub : R|kotlin/Any| { + public final companion object Companion : R|kotlin/Any| { + } + + } + + } diff --git a/tests/compiler-plugin-tests/src/testData/diagnostics/rpcService.kt b/tests/compiler-plugin-tests/src/testData/diagnostics/rpcService.kt new file mode 100644 index 00000000..deca0222 --- /dev/null +++ b/tests/compiler-plugin-tests/src/testData/diagnostics/rpcService.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:OptIn(ExperimentalRpcApi::class) + +import kotlin.coroutines.* +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import kotlinx.serialization.Serializable +import kotlinx.serialization.Contextual +import kotlinx.rpc.annotations.Rpc +import kotlinx.rpc.krpc.streamScoped +import kotlinx.rpc.krpc.withStreamScope +import kotlinx.rpc.krpc.StreamScope +import kotlinx.rpc.krpc.invokeOnStreamScopeCompletion +import kotlinx.rpc.internal.utils.ExperimentalRpcApi + +@Rpc +interface MyService { + fun hello() + + suspend fun generic(a: T) + + suspend fun > generic2(a: T, b: Int, c: T4, t2: T2): T3 + + suspend fun sameName() + + suspend fun sameName(a: Int) + + suspend fun sameName(a: Int, b: Int) + + suspend fun sameName(a: Int, b: Int, c: Int) + + suspend fun sameName2() + + suspend fun sameName2(a: Int) +} + +@Rpc +interface MyServiceT + +@Rpc +interface MyServiceT2 diff --git a/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.fir.txt b/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.fir.txt new file mode 100644 index 00000000..00e5f72b --- /dev/null +++ b/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.fir.txt @@ -0,0 +1,742 @@ +Module: none +FILE: a.kt + @R|kotlinx/rpc/annotations/Rpc|() public abstract interface MyServiceWithStateFlow : R|kotlin/Any|, R|kotlinx/rpc/RemoteService| { + public abstract suspend fun hello(flow: R|kotlinx/coroutines/flow/StateFlow|): R|kotlin/Unit| + + public final class $rpcServiceStub : R|kotlin/Any| { + @R|kotlinx/serialization/Serializable|() public final class hello$rpcMethod : R|kotlin/Any| { + @R|kotlinx/serialization/Contextual|() public final val flow: R|kotlinx/coroutines/flow/StateFlow| + public get(): R|kotlinx/coroutines/flow/StateFlow| + + public constructor(flow: R|kotlinx/coroutines/flow/StateFlow|): R|MyServiceWithStateFlow.$rpcServiceStub.hello$rpcMethod| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyServiceWithStateFlow.$rpcServiceStub.hello$rpcMethod.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MyServiceWithStateFlow.$rpcServiceStub.hello$rpcMethod|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MyServiceWithStateFlow.$rpcServiceStub.hello$rpcMethod| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MyServiceWithStateFlow.$rpcServiceStub.hello$rpcMethod.$serializer| { + super() + } + + } + + } + + public final companion object Companion : R|kotlin/Any| { + } + + } + + } +Module: main +FILE: b.kt + @FILE:R|kotlin/OptIn|(markerClass = vararg((Q|kotlinx/rpc/internal/utils/ExperimentalRpcApi|))) + @R|kotlinx/serialization/Serializable|() public final data class InnerFlow : R|kotlin/Any| { + public constructor(flow: R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow|): R|InnerFlow| { + super() + } + + public final val flow: R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| = R|/flow| + public get(): R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| + + public final operator fun component1(): R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| + + public final fun copy(flow: R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| = this@R|/InnerFlow|.R|/InnerFlow.flow|): R|InnerFlow| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|InnerFlow.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|InnerFlow|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|InnerFlow| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|InnerFlow.$serializer| { + super() + } + + } + + } + @R|kotlinx/serialization/Serializable|() public final data class Wrapper : R|kotlin/Any| { + public constructor(inner: R|T|): R|Wrapper| { + super() + } + + public final val inner: R|T| = R|/inner| + public get(): R|T| + + public final operator fun component1(): R|T| + + public final fun copy(inner: R|T| = this@R|/Wrapper|.R|/Wrapper.inner|): R|Wrapper| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(typeSerial0: R|kotlinx/serialization/KSerializer|): R|kotlinx/serialization/KSerializer>| + + private constructor(): R|Wrapper.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final class $serializer : R|kotlinx/serialization/internal/GeneratedSerializer>| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|Wrapper|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|Wrapper| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + public final override fun typeParametersSerializers(): R|kotlin/Array>| + + private constructor(): R|Wrapper.$serializer| { + super() + } + + public constructor(typeSerial0: R|kotlinx/serialization/KSerializer|): R|Wrapper.$serializer| + + } + + } + @R|kotlinx/serialization/Serializable|() public final data class MultiFlow : R|kotlin/Any| { + public constructor(flow1: R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow|, flow2: R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow|, flow3: R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow|): R|MultiFlow| { + super() + } + + public final val flow1: R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| = R|/flow1| + public get(): R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| + + public final val flow2: R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| = R|/flow2| + public get(): R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| + + public final val flow3: R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| = R|/flow3| + public get(): R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| + + public final operator fun component1(): R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| + + public final operator fun component2(): R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| + + public final operator fun component3(): R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| + + public final fun copy(flow1: R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| = this@R|/MultiFlow|.R|/MultiFlow.flow1|, flow2: R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| = this@R|/MultiFlow|.R|/MultiFlow.flow2|, flow3: R|@R|kotlinx/serialization/Contextual|() kotlinx/coroutines/flow/Flow| = this@R|/MultiFlow|.R|/MultiFlow.flow3|): R|MultiFlow| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MultiFlow.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MultiFlow|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MultiFlow| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MultiFlow.$serializer| { + super() + } + + } + + } + @R|kotlinx/rpc/annotations/Rpc|() public abstract interface MyService : R|kotlin/Any|, R|kotlinx/rpc/RemoteService| { + public abstract val flow: R|kotlinx/coroutines/flow/Flow| + public get(): R|kotlinx/coroutines/flow/Flow| + + public abstract val stateFlow: R|kotlinx/coroutines/flow/StateFlow| + public get(): R|kotlinx/coroutines/flow/StateFlow| + + public abstract val sharedFlow: R|kotlinx/coroutines/flow/SharedFlow| + public get(): R|kotlinx/coroutines/flow/SharedFlow| + + public abstract suspend fun state(flow: R|kotlinx/coroutines/flow/StateFlow|): R|kotlinx/coroutines/flow/StateFlow| + + public abstract suspend fun shared(flow: R|kotlinx/coroutines/flow/SharedFlow|): R|kotlinx/coroutines/flow/SharedFlow| + + public abstract suspend fun deepState(flow: R|Wrapper>|): R|kotlin/Unit| + + public abstract suspend fun deepShared(flow: R|Wrapper>|): R|kotlin/Unit| + + public abstract suspend fun flowOk(flow: R|kotlinx/coroutines/flow/Flow|): R|kotlin/Int| + + public abstract fun serverFlowOk(): R|kotlinx/coroutines/flow/Flow| + + public abstract suspend fun serverFlowFail(): R|kotlinx/coroutines/flow/Flow| + + public abstract fun notTopLevelServerFlow(): R|InnerFlow| + + public abstract fun wrappedNotTopLevelServerFlow(): R|Wrapper>| + + public abstract suspend fun wrappedALot(): R|Wrapper>>>| + + public abstract fun wrappedALotFlow(): R|Wrapper>>>| + + public abstract suspend fun notTopLevelClientFlow(flow: R|InnerFlow|): R|kotlin/Unit| + + public abstract fun nestedServerFlow(): R|kotlinx/coroutines/flow/Flow>| + + public abstract fun nestedServerTrickyFlow(): R|kotlinx/coroutines/flow/Flow>>| + + public abstract fun serverMultiFlow(): R|MultiFlow| + + public abstract suspend fun clientMultiFlow(flow: R|MultiFlow|): R|kotlin/Unit| + + public abstract suspend fun clientMultiFlowMany(flow: R|MultiFlow|, flow2: R|MultiFlow|, flow3: R|MultiFlow|): R|kotlin/Unit| + + public abstract suspend fun clientMultiFlowPlain(flow: R|kotlinx/coroutines/flow/Flow|, flow2: R|kotlinx/coroutines/flow/Flow|, flow3: R|kotlinx/coroutines/flow/Flow|): R|kotlin/Unit| + + public abstract suspend fun clientInnerFlow(inner: R|InnerFlow|): R|kotlin/Unit| + + public abstract suspend fun clientNestedFlow(inner: R|kotlinx/coroutines/flow/Flow>|): R|kotlin/Unit| + + public abstract suspend fun clientNestedTrickyFlow(inner: R|Wrapper>>>|): R|kotlin/Unit| + + public final class $rpcServiceStub : R|kotlin/Any| { + @R|kotlinx/serialization/Serializable|() public final class state$rpcMethod : R|kotlin/Any| { + @R|kotlinx/serialization/Contextual|() public final val flow: R|kotlinx/coroutines/flow/StateFlow| + public get(): R|kotlinx/coroutines/flow/StateFlow| + + public constructor(flow: R|kotlinx/coroutines/flow/StateFlow|): R|MyService.$rpcServiceStub.state$rpcMethod| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.state$rpcMethod.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MyService.$rpcServiceStub.state$rpcMethod|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MyService.$rpcServiceStub.state$rpcMethod| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MyService.$rpcServiceStub.state$rpcMethod.$serializer| { + super() + } + + } + + } + + @R|kotlinx/serialization/Serializable|() public final class shared$rpcMethod : R|kotlin/Any| { + @R|kotlinx/serialization/Contextual|() public final val flow: R|kotlinx/coroutines/flow/SharedFlow| + public get(): R|kotlinx/coroutines/flow/SharedFlow| + + public constructor(flow: R|kotlinx/coroutines/flow/SharedFlow|): R|MyService.$rpcServiceStub.shared$rpcMethod| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.shared$rpcMethod.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MyService.$rpcServiceStub.shared$rpcMethod|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MyService.$rpcServiceStub.shared$rpcMethod| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MyService.$rpcServiceStub.shared$rpcMethod.$serializer| { + super() + } + + } + + } + + @R|kotlinx/serialization/Serializable|() public final class deepState$rpcMethod : R|kotlin/Any| { + public final val flow: R|Wrapper>| + public get(): R|Wrapper>| + + public constructor(flow: R|Wrapper>|): R|MyService.$rpcServiceStub.deepState$rpcMethod| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.deepState$rpcMethod.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MyService.$rpcServiceStub.deepState$rpcMethod|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MyService.$rpcServiceStub.deepState$rpcMethod| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MyService.$rpcServiceStub.deepState$rpcMethod.$serializer| { + super() + } + + } + + } + + @R|kotlinx/serialization/Serializable|() public final class deepShared$rpcMethod : R|kotlin/Any| { + public final val flow: R|Wrapper>| + public get(): R|Wrapper>| + + public constructor(flow: R|Wrapper>|): R|MyService.$rpcServiceStub.deepShared$rpcMethod| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.deepShared$rpcMethod.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MyService.$rpcServiceStub.deepShared$rpcMethod|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MyService.$rpcServiceStub.deepShared$rpcMethod| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MyService.$rpcServiceStub.deepShared$rpcMethod.$serializer| { + super() + } + + } + + } + + @R|kotlinx/serialization/Serializable|() public final class flowOk$rpcMethod : R|kotlin/Any| { + @R|kotlinx/serialization/Contextual|() public final val flow: R|kotlinx/coroutines/flow/Flow| + public get(): R|kotlinx/coroutines/flow/Flow| + + public constructor(flow: R|kotlinx/coroutines/flow/Flow|): R|MyService.$rpcServiceStub.flowOk$rpcMethod| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.flowOk$rpcMethod.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MyService.$rpcServiceStub.flowOk$rpcMethod|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MyService.$rpcServiceStub.flowOk$rpcMethod| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MyService.$rpcServiceStub.flowOk$rpcMethod.$serializer| { + super() + } + + } + + } + + @R|kotlinx/serialization/Serializable|() public final object serverFlowOk$rpcMethod : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.serverFlowOk$rpcMethod| { + super() + } + + } + + @R|kotlinx/serialization/Serializable|() public final object serverFlowFail$rpcMethod : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.serverFlowFail$rpcMethod| { + super() + } + + } + + @R|kotlinx/serialization/Serializable|() public final object notTopLevelServerFlow$rpcMethod : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.notTopLevelServerFlow$rpcMethod| { + super() + } + + } + + @R|kotlinx/serialization/Serializable|() public final object wrappedNotTopLevelServerFlow$rpcMethod : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.wrappedNotTopLevelServerFlow$rpcMethod| { + super() + } + + } + + @R|kotlinx/serialization/Serializable|() public final object wrappedALot$rpcMethod : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.wrappedALot$rpcMethod| { + super() + } + + } + + @R|kotlinx/serialization/Serializable|() public final object wrappedALotFlow$rpcMethod : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.wrappedALotFlow$rpcMethod| { + super() + } + + } + + @R|kotlinx/serialization/Serializable|() public final class notTopLevelClientFlow$rpcMethod : R|kotlin/Any| { + public final val flow: R|InnerFlow| + public get(): R|InnerFlow| + + public constructor(flow: R|InnerFlow|): R|MyService.$rpcServiceStub.notTopLevelClientFlow$rpcMethod| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.notTopLevelClientFlow$rpcMethod.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MyService.$rpcServiceStub.notTopLevelClientFlow$rpcMethod|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MyService.$rpcServiceStub.notTopLevelClientFlow$rpcMethod| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MyService.$rpcServiceStub.notTopLevelClientFlow$rpcMethod.$serializer| { + super() + } + + } + + } + + @R|kotlinx/serialization/Serializable|() public final object nestedServerFlow$rpcMethod : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.nestedServerFlow$rpcMethod| { + super() + } + + } + + @R|kotlinx/serialization/Serializable|() public final object nestedServerTrickyFlow$rpcMethod : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.nestedServerTrickyFlow$rpcMethod| { + super() + } + + } + + @R|kotlinx/serialization/Serializable|() public final object serverMultiFlow$rpcMethod : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.serverMultiFlow$rpcMethod| { + super() + } + + } + + @R|kotlinx/serialization/Serializable|() public final class clientMultiFlow$rpcMethod : R|kotlin/Any| { + public final val flow: R|MultiFlow| + public get(): R|MultiFlow| + + public constructor(flow: R|MultiFlow|): R|MyService.$rpcServiceStub.clientMultiFlow$rpcMethod| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.clientMultiFlow$rpcMethod.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MyService.$rpcServiceStub.clientMultiFlow$rpcMethod|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MyService.$rpcServiceStub.clientMultiFlow$rpcMethod| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MyService.$rpcServiceStub.clientMultiFlow$rpcMethod.$serializer| { + super() + } + + } + + } + + @R|kotlinx/serialization/Serializable|() public final class clientMultiFlowMany$rpcMethod : R|kotlin/Any| { + public final val flow: R|MultiFlow| + public get(): R|MultiFlow| + + public final val flow2: R|MultiFlow| + public get(): R|MultiFlow| + + public final val flow3: R|MultiFlow| + public get(): R|MultiFlow| + + public constructor(flow: R|MultiFlow|, flow2: R|MultiFlow|, flow3: R|MultiFlow|): R|MyService.$rpcServiceStub.clientMultiFlowMany$rpcMethod| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.clientMultiFlowMany$rpcMethod.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MyService.$rpcServiceStub.clientMultiFlowMany$rpcMethod|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MyService.$rpcServiceStub.clientMultiFlowMany$rpcMethod| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MyService.$rpcServiceStub.clientMultiFlowMany$rpcMethod.$serializer| { + super() + } + + } + + } + + @R|kotlinx/serialization/Serializable|() public final class clientMultiFlowPlain$rpcMethod : R|kotlin/Any| { + @R|kotlinx/serialization/Contextual|() public final val flow: R|kotlinx/coroutines/flow/Flow| + public get(): R|kotlinx/coroutines/flow/Flow| + + @R|kotlinx/serialization/Contextual|() public final val flow2: R|kotlinx/coroutines/flow/Flow| + public get(): R|kotlinx/coroutines/flow/Flow| + + @R|kotlinx/serialization/Contextual|() public final val flow3: R|kotlinx/coroutines/flow/Flow| + public get(): R|kotlinx/coroutines/flow/Flow| + + public constructor(flow: R|kotlinx/coroutines/flow/Flow|, flow2: R|kotlinx/coroutines/flow/Flow|, flow3: R|kotlinx/coroutines/flow/Flow|): R|MyService.$rpcServiceStub.clientMultiFlowPlain$rpcMethod| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.clientMultiFlowPlain$rpcMethod.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MyService.$rpcServiceStub.clientMultiFlowPlain$rpcMethod|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MyService.$rpcServiceStub.clientMultiFlowPlain$rpcMethod| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MyService.$rpcServiceStub.clientMultiFlowPlain$rpcMethod.$serializer| { + super() + } + + } + + } + + @R|kotlinx/serialization/Serializable|() public final class clientInnerFlow$rpcMethod : R|kotlin/Any| { + public final val inner: R|InnerFlow| + public get(): R|InnerFlow| + + public constructor(inner: R|InnerFlow|): R|MyService.$rpcServiceStub.clientInnerFlow$rpcMethod| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.clientInnerFlow$rpcMethod.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MyService.$rpcServiceStub.clientInnerFlow$rpcMethod|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MyService.$rpcServiceStub.clientInnerFlow$rpcMethod| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MyService.$rpcServiceStub.clientInnerFlow$rpcMethod.$serializer| { + super() + } + + } + + } + + @R|kotlinx/serialization/Serializable|() public final class clientNestedFlow$rpcMethod : R|kotlin/Any| { + @R|kotlinx/serialization/Contextual|() public final val inner: R|kotlinx/coroutines/flow/Flow>| + public get(): R|kotlinx/coroutines/flow/Flow>| + + public constructor(inner: R|kotlinx/coroutines/flow/Flow>|): R|MyService.$rpcServiceStub.clientNestedFlow$rpcMethod| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.clientNestedFlow$rpcMethod.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MyService.$rpcServiceStub.clientNestedFlow$rpcMethod|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MyService.$rpcServiceStub.clientNestedFlow$rpcMethod| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MyService.$rpcServiceStub.clientNestedFlow$rpcMethod.$serializer| { + super() + } + + } + + } + + @R|kotlinx/serialization/Serializable|() public final class clientNestedTrickyFlow$rpcMethod : R|kotlin/Any| { + public final val inner: R|Wrapper>>>| + public get(): R|Wrapper>>>| + + public constructor(inner: R|Wrapper>>>|): R|MyService.$rpcServiceStub.clientNestedTrickyFlow$rpcMethod| + + public final companion object Companion : R|kotlin/Any| { + public final fun serializer(): R|kotlinx/serialization/KSerializer| + + private constructor(): R|MyService.$rpcServiceStub.clientNestedTrickyFlow$rpcMethod.Companion| { + super() + } + + } + + @R|kotlin/Deprecated|(message = String(This synthesized declaration should not be used directly), level = Q|kotlin/DeprecationLevel|.R|kotlin/DeprecationLevel.HIDDEN|) public final object $serializer : R|kotlinx/serialization/internal/GeneratedSerializer| { + public final override fun serialize(encoder: R|kotlinx/serialization/encoding/Encoder|, value: R|MyService.$rpcServiceStub.clientNestedTrickyFlow$rpcMethod|): R|kotlin/Unit| + + public final override fun deserialize(decoder: R|kotlinx/serialization/encoding/Decoder|): R|MyService.$rpcServiceStub.clientNestedTrickyFlow$rpcMethod| + + public final val descriptor: R|kotlinx/serialization/descriptors/SerialDescriptor| + public get(): R|kotlinx/serialization/descriptors/SerialDescriptor| + + public final override fun childSerializers(): R|kotlin/Array>| + + private constructor(): R|MyService.$rpcServiceStub.clientNestedTrickyFlow$rpcMethod.$serializer| { + super() + } + + } + + } + + public final companion object Companion : R|kotlin/Any| { + } + + } + + } + public final fun main(): R|kotlin/Unit| { + ^main R|kotlinx/coroutines/runBlocking|( = runBlocking@fun R|kotlinx/coroutines/CoroutineScope|.(): R|kotlin/Unit| { + R|kotlinx/rpc/krpc/streamScoped|( = streamScoped@fun R|kotlinx/coroutines/CoroutineScope|.(): R|kotlin/Unit| { + ^@streamScoped Unit + } + ) + lval scope: R|kotlinx/rpc/krpc/StreamScope| = R|kotlinx/rpc/krpc/StreamScope|(R|kotlinx/coroutines/Job|()) + R|kotlinx/rpc/krpc/withStreamScope|(R|/scope|, = withStreamScope@fun R|kotlinx/coroutines/CoroutineScope|.(): R|kotlin/Unit| { + ^@withStreamScope Unit + } + ) + R|kotlinx/rpc/krpc/invokeOnStreamScopeCompletion|( = invokeOnStreamScopeCompletion@fun (it: R|kotlin/Throwable?|): R|kotlin/Unit| { + ^@invokeOnStreamScopeCompletion Unit + } + ) + } + ) + } diff --git a/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.kt b/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.kt new file mode 100644 index 00000000..0a853f94 --- /dev/null +++ b/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +// MODULE: none +// RPC_STRICT_MODE: none +// FILE: a.kt + +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import kotlinx.rpc.annotations.Rpc + +@Rpc +interface MyServiceWithStateFlow { + suspend fun hello(flow: StateFlow) +} + +// MODULE: main +// RPC_STRICT_MODE: warning +// FILE: b.kt + +@file:OptIn(ExperimentalRpcApi::class) + +import kotlin.coroutines.* +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import kotlinx.serialization.Serializable +import kotlinx.serialization.Contextual +import kotlinx.rpc.annotations.Rpc +import kotlinx.rpc.krpc.streamScoped +import kotlinx.rpc.krpc.withStreamScope +import kotlinx.rpc.krpc.StreamScope +import kotlinx.rpc.krpc.invokeOnStreamScopeCompletion +import kotlinx.rpc.internal.utils.ExperimentalRpcApi + +@Serializable +data class InnerFlow( + val flow: @Contextual Flow, +) + +@Serializable +data class Wrapper( + val inner: T, +) + +@Serializable +data class MultiFlow( + val flow1: @Contextual Flow, + val flow2: @Contextual Flow, + val flow3: @Contextual Flow, +) + +@Rpc +interface MyService { + val flow: Flow + val stateFlow: StateFlow + val sharedFlow: SharedFlow + suspend fun state(flow: StateFlow): StateFlow + suspend fun shared(flow: SharedFlow): SharedFlow + suspend fun deepState(flow: Wrapper>) + suspend fun deepShared(flow: Wrapper>) + suspend fun flowOk(flow: Flow): Int + fun serverFlowOk(): Flow + suspend fun serverFlowFail(): Flow + fun notTopLevelServerFlow(): InnerFlow + fun wrappedNotTopLevelServerFlow(): Wrapper> + suspend fun wrappedALot(): Wrapper>>> + fun wrappedALotFlow(): Wrapper>>> + suspend fun notTopLevelClientFlow(flow: InnerFlow) + fun nestedServerFlow(): Flow> + fun nestedServerTrickyFlow(): Flow>> + fun serverMultiFlow(): MultiFlow + suspend fun clientMultiFlow(flow: MultiFlow) + suspend fun clientMultiFlowMany(flow: MultiFlow, flow2: MultiFlow, flow3: MultiFlow) + suspend fun clientMultiFlowPlain(flow: Flow, flow2: Flow, flow3: Flow) + suspend fun clientInnerFlow(inner: InnerFlow) + suspend fun clientNestedFlow(inner: Flow>) + suspend fun clientNestedTrickyFlow(inner: Wrapper>>>) +} + +fun main(): Unit = runBlocking { + streamScoped {} + val scope = StreamScope(Job()) + withStreamScope(scope) {} + invokeOnStreamScopeCompletion {} +} diff --git a/versions-root/libs.versions.toml b/versions-root/libs.versions.toml index 3a85ad59..08f1e717 100644 --- a/versions-root/libs.versions.toml +++ b/versions-root/libs.versions.toml @@ -20,6 +20,7 @@ junit5 = "5.11.3" intellij = "233.13135.128" gradle-doctor = "0.10.0" kotlinx-browser = "0.2" +shadow-jar = "9.0.0-beta2" # Stub versions – relpaced based on kotlin, mostly for gradle-related (plugins) dependencies # but also for dependencies for compiler-specific modules. @@ -112,6 +113,7 @@ atomicfu = { id = "org.jetbrains.kotlinx.atomicfu", version.ref = "atomicfu" } gradle-kotlin-dsl = { id = "org.gradle.kotlin.kotlin-dsl", version.ref = "gradle-kotlin-dsl" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } gradle-plugin-publish = { id = "com.gradle.plugin-publish", version.ref = "gradle-plugin-publish" } +shadow-jar = { id = "com.gradleup.shadow", version.ref = "shadow-jar" } # gradle-conventions project conventions-common = { id = "conventions-common", version.ref = "kotlinx-rpc" }