diff --git a/compiler-plugin/compiler-plugin-backend/src/main/kotlin/kotlinx/rpc/codegen/extension/RpcDeclarationScanner.kt b/compiler-plugin/compiler-plugin-backend/src/main/kotlin/kotlinx/rpc/codegen/extension/RpcDeclarationScanner.kt index f7296df0e..6cb5c3af2 100644 --- a/compiler-plugin/compiler-plugin-backend/src/main/kotlin/kotlinx/rpc/codegen/extension/RpcDeclarationScanner.kt +++ b/compiler-plugin/compiler-plugin-backend/src/main/kotlin/kotlinx/rpc/codegen/extension/RpcDeclarationScanner.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.ir.declarations.IrDeclaration import org.jetbrains.kotlin.ir.declarations.IrProperty import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.util.dumpKotlinLike +import org.jetbrains.kotlin.ir.util.hasDefaultValue /** * This class scans user declared RPC service @@ -35,7 +36,7 @@ internal object RpcDeclarationScanner { function = declaration, arguments = ctx.versionSpecificApi.run { declaration.valueParametersVS().memoryOptimizedMap { param -> - ServiceDeclaration.Method.Argument(param, param.type) + ServiceDeclaration.Method.Argument(param, param.type, param.hasDefaultValue()) } }, ) diff --git a/compiler-plugin/compiler-plugin-backend/src/main/kotlin/kotlinx/rpc/codegen/extension/RpcStubGenerator.kt b/compiler-plugin/compiler-plugin-backend/src/main/kotlin/kotlinx/rpc/codegen/extension/RpcStubGenerator.kt index 005722a13..11ef4eb13 100644 --- a/compiler-plugin/compiler-plugin-backend/src/main/kotlin/kotlinx/rpc/codegen/extension/RpcStubGenerator.kt +++ b/compiler-plugin/compiler-plugin-backend/src/main/kotlin/kotlinx/rpc/codegen/extension/RpcStubGenerator.kt @@ -815,6 +815,7 @@ internal class RpcStubGenerator( values { +stringConst(parameter.value.name.asString()) +irRpcTypeCall(parameter.type) + +booleanConst(parameter.isOptional) +irListOfAnnotations(parameter.value) } } diff --git a/compiler-plugin/compiler-plugin-backend/src/main/kotlin/kotlinx/rpc/codegen/extension/ServiceDeclaration.kt b/compiler-plugin/compiler-plugin-backend/src/main/kotlin/kotlinx/rpc/codegen/extension/ServiceDeclaration.kt index 98d0f19c9..cfb155c31 100644 --- a/compiler-plugin/compiler-plugin-backend/src/main/kotlin/kotlinx/rpc/codegen/extension/ServiceDeclaration.kt +++ b/compiler-plugin/compiler-plugin-backend/src/main/kotlin/kotlinx/rpc/codegen/extension/ServiceDeclaration.kt @@ -34,6 +34,7 @@ class ServiceDeclaration( class Argument( val value: IrValueParameter, val type: IrType, + val isOptional: Boolean, ) } } diff --git a/compiler-plugin/compiler-plugin-k2/src/main/kotlin/kotlinx/rpc/codegen/checkers/FirRpcStrictModeClassChecker.kt b/compiler-plugin/compiler-plugin-k2/src/main/kotlin/kotlinx/rpc/codegen/checkers/FirRpcStrictModeClassChecker.kt index abe484cac..b4c858ab8 100644 --- a/compiler-plugin/compiler-plugin-k2/src/main/kotlin/kotlinx/rpc/codegen/checkers/FirRpcStrictModeClassChecker.kt +++ b/compiler-plugin/compiler-plugin-k2/src/main/kotlin/kotlinx/rpc/codegen/checkers/FirRpcStrictModeClassChecker.kt @@ -74,7 +74,7 @@ object FirRpcStrictModeClassChecker { } memoryOptimizedPlus (function.resolvedReturnTypeRef.source to function.resolvedReturnTypeRef) types.forEach { (source, symbol) -> - checkSerializableTypes>( + checkSerializableTypes( context = context, typeRef = symbol, serializablePropertiesProvider = serializablePropertiesProvider, @@ -96,8 +96,6 @@ object FirRpcStrictModeClassChecker { } } } - - symbol } } @@ -106,26 +104,28 @@ object FirRpcStrictModeClassChecker { } } - private fun checkSerializableTypes( + private fun checkSerializableTypes( context: CheckerContext, typeRef: FirTypeRef, serializablePropertiesProvider: FirSerializablePropertiesProvider, - parentContext: List = emptyList(), - checker: (FirClassLikeSymbol<*>, List) -> ContextElement?, + parentContext: List> = emptyList(), + checker: (FirClassLikeSymbol<*>, List>) -> Unit, ) { val symbol = typeRef.toClassLikeSymbol(context.session) ?: return - val newElement = checker(symbol, parentContext) - val nextContext = if (newElement != null) { - parentContext memoryOptimizedPlus newElement - } else { - parentContext - } + + checker(symbol, parentContext) if (symbol !is FirClassSymbol<*>) { return } - val extracted = extractArgumentsTypeRefAndSource(typeRef) + val nextContext = parentContext memoryOptimizedPlus symbol + + if (symbol in parentContext && symbol.typeParameterSymbols.isEmpty()) { + return + } + + val typeParameters = extractArgumentsTypeRefAndSource(typeRef) .orEmpty() .withIndex() .associate { (i, refSource) -> @@ -133,7 +133,7 @@ object FirRpcStrictModeClassChecker { } val flowProps: List = if (symbol.classId == RpcClassId.flow) { - listOf(extracted.values.toList()[0]!!) + listOf(typeParameters.values.toList()[0]!!) } else { emptyList() } @@ -144,7 +144,7 @@ object FirRpcStrictModeClassChecker { if (resolvedTypeRef.toClassLikeSymbol(context.session) != null) { resolvedTypeRef } else { - extracted[property.resolvedReturnType] + typeParameters[property.resolvedReturnType] } }.memoryOptimizedPlus(flowProps) .forEach { symbol -> diff --git a/compiler-plugin/compiler-plugin-k2/src/main/kotlin/kotlinx/rpc/codegen/checkers/diagnostics/FirRpcDiagnostics.kt b/compiler-plugin/compiler-plugin-k2/src/main/kotlin/kotlinx/rpc/codegen/checkers/diagnostics/FirRpcDiagnostics.kt index 6cb21ed7f..8d5897fea 100644 --- a/compiler-plugin/compiler-plugin-k2/src/main/kotlin/kotlinx/rpc/codegen/checkers/diagnostics/FirRpcDiagnostics.kt +++ b/compiler-plugin/compiler-plugin-k2/src/main/kotlin/kotlinx/rpc/codegen/checkers/diagnostics/FirRpcDiagnostics.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.KtAnnotated import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtNameReferenceExpression // ########################################################################### // ### BIG WARNING, LISTEN CLOSELY! ### @@ -25,8 +26,8 @@ import org.jetbrains.kotlin.psi.KtElement // ########################################################################### object FirRpcDiagnostics : RpcKtDiagnosticsContainer() { - val WRONG_RPC_ANNOTATION_TARGET by error1() - val CHECKED_ANNOTATION_VIOLATION by error3>() + val WRONG_RPC_ANNOTATION_TARGET by error1() + val CHECKED_ANNOTATION_VIOLATION by error3>() 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) diff --git a/compiler-plugin/gradle.properties b/compiler-plugin/gradle.properties index 1daa2663a..9792d43e3 100644 --- a/compiler-plugin/gradle.properties +++ b/compiler-plugin/gradle.properties @@ -15,7 +15,7 @@ org.gradle.parallel=true org.gradle.workers.max=8 org.gradle.caching=true org.gradle.configuration-cache=true -org.gradle.configureondemand=true +#org.gradle.configureondemand=true // breaks compiler tests org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true diff --git a/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptor.kt b/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptor.kt index 666568ed3..508fcb777 100644 --- a/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptor.kt +++ b/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptor.kt @@ -68,6 +68,7 @@ public sealed interface RpcInvokator<@Rpc T : Any> { public interface RpcParameter { public val name: String public val type: RpcType + public val isOptional: Boolean /** * List of annotations with target [AnnotationTarget.VALUE_PARAMETER]. diff --git a/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptorDefault.kt b/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptorDefault.kt index e4c79456b..f3209ba48 100644 --- a/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptorDefault.kt +++ b/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptorDefault.kt @@ -21,6 +21,7 @@ public class RpcCallableDefault<@Rpc T : Any>( public class RpcParameterDefault( override val name: String, override val type: RpcType, + override val isOptional: Boolean, override val annotations: List, ) : RpcParameter diff --git a/dokka-plugin/gradle.properties b/dokka-plugin/gradle.properties index 1daa2663a..9792d43e3 100644 --- a/dokka-plugin/gradle.properties +++ b/dokka-plugin/gradle.properties @@ -15,7 +15,7 @@ org.gradle.parallel=true org.gradle.workers.max=8 org.gradle.caching=true org.gradle.configuration-cache=true -org.gradle.configureondemand=true +#org.gradle.configureondemand=true // breaks compiler tests org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true diff --git a/gradle-conventions/src/main/kotlin/conventions-common.gradle.kts b/gradle-conventions/src/main/kotlin/conventions-common.gradle.kts index ebf7294c3..42638476a 100644 --- a/gradle-conventions/src/main/kotlin/conventions-common.gradle.kts +++ b/gradle-conventions/src/main/kotlin/conventions-common.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ import io.gitlab.arturbosch.detekt.Detekt diff --git a/gradle-conventions/src/main/kotlin/conventions-kmp.gradle.kts b/gradle-conventions/src/main/kotlin/conventions-kmp.gradle.kts index bf8d777ff..44f80f0d1 100644 --- a/gradle-conventions/src/main/kotlin/conventions-kmp.gradle.kts +++ b/gradle-conventions/src/main/kotlin/conventions-kmp.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ @file:OptIn(ExperimentalAbiValidation::class) diff --git a/gradle-conventions/src/main/kotlin/conventions-publishing.gradle.kts b/gradle-conventions/src/main/kotlin/conventions-publishing.gradle.kts index f374760aa..bc9776bb7 100644 --- a/gradle-conventions/src/main/kotlin/conventions-publishing.gradle.kts +++ b/gradle-conventions/src/main/kotlin/conventions-publishing.gradle.kts @@ -138,15 +138,6 @@ fun RepositoryHandler.configureLocalDevRepository() { } } -val sonatypeRepositoryUri: String? - get() { - val repositoryId: String = project.getSensitiveProperty("libs.repository.id") - ?.takeIf { it.isNotBlank() } - ?: return null - - return "https://oss.sonatype.org/service/local/staging/deployByRepositoryId/$repositoryId" - } - fun configureEmptyJavadocArtifact(): TaskProvider { val javadocJar by project.tasks.registering(Jar::class) { archiveClassifier.set("javadoc") diff --git a/gradle-plugin/gradle.properties b/gradle-plugin/gradle.properties index 1daa2663a..9792d43e3 100644 --- a/gradle-plugin/gradle.properties +++ b/gradle-plugin/gradle.properties @@ -15,7 +15,7 @@ org.gradle.parallel=true org.gradle.workers.max=8 org.gradle.caching=true org.gradle.configuration-cache=true -org.gradle.configureondemand=true +#org.gradle.configureondemand=true // breaks compiler tests org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true diff --git a/gradle.properties b/gradle.properties index 1daa2663a..9792d43e3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,7 +15,7 @@ org.gradle.parallel=true org.gradle.workers.max=8 org.gradle.caching=true org.gradle.configuration-cache=true -org.gradle.configureondemand=true +#org.gradle.configureondemand=true // breaks compiler tests org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true diff --git a/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/internal/SerializationUtils.kt b/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/internal/SerializationUtils.kt index 68165e133..f32b6dd98 100644 --- a/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/internal/SerializationUtils.kt +++ b/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/internal/SerializationUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.internal @@ -15,6 +15,7 @@ import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.* import kotlinx.serialization.modules.SerializersModule import kotlin.reflect.KType +import kotlin.reflect.KTypeParameter @OptIn(ExperimentalSerializationApi::class) internal fun SerializersModule.buildContextualInternal(type: KType): KSerializer? { @@ -113,7 +114,7 @@ public class CallableParametersSerializer( elementName = param.name, descriptor = callableSerializers[i].descriptor, annotations = param.type.annotations, - isOptional = param.type.kType.isMarkedNullable, + isOptional = param.isOptional, ) } } diff --git a/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestService.kt b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestService.kt index 48791b4ee..d62538a1f 100644 --- a/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestService.kt +++ b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestService.kt @@ -60,7 +60,8 @@ interface KrpcTestService { suspend fun doubleGenericParams(arg1: List>) suspend fun mapParams(arg1: Map>>) suspend fun customType(arg1: TestClass): TestClass - suspend fun nullable(arg1: String?): TestClass? + suspend fun nullableParam(arg1: String?): String + suspend fun nullableReturn(returnNull: Boolean): TestClass? suspend fun variance(arg2: TestList, arg3: TestList2): TestList? suspend fun nonSerializableClass(localDate: LocalDate): LocalDate diff --git a/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.kt b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.kt index ba162a66f..a8b924995 100644 --- a/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.kt +++ b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.kt @@ -97,8 +97,12 @@ class KrpcTestServiceBackend : KrpcTestService { return arg1 } - override suspend fun nullable(arg1: String?): TestClass? { - return if (arg1 == null) null else TestClass() + override suspend fun nullableParam(arg1: String?): String { + return arg1 ?: "null" + } + + override suspend fun nullableReturn(returnNull: Boolean): TestClass? { + return if (returnNull) null else TestClass() } override suspend fun variance( diff --git a/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.kt b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.kt index d1720f6e2..ff32015c4 100644 --- a/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.kt +++ b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.kt @@ -226,12 +226,21 @@ abstract class KrpcTransportTestBase { } @Test - open fun nullable() = runTest { - val result = client.nullable("test") - assertEquals(TestClass(), result) + open fun nullableParameter() = runTest { + val result = client.nullableParam("test") + assertEquals("test", result) + + val result2 = client.nullableParam(null) + assertEquals("null", result2) + } + + @Test + open fun nullableReturn() = runTest { + val result = client.nullableReturn(returnNull = true) + assertEquals(null, result) - val result2 = client.nullable(null) - assertEquals(null, result2) + val result2 = client.nullableReturn(returnNull = false) + assertEquals(TestClass(), result2) } @Test @@ -345,7 +354,7 @@ abstract class KrpcTransportTestBase { @Test open fun testNullables() = runTest { assertEquals(1, client.nullableInt(1)) - assertNull(client.nullable(null)) + assertNull(client.nullableReturn(returnNull = true)) } @Test diff --git a/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/LocalTransportTest.kt b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/LocalTransportTest.kt index 2cd704c01..7b6f0ebcf 100644 --- a/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/LocalTransportTest.kt +++ b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/LocalTransportTest.kt @@ -52,7 +52,7 @@ class ProtoBufLocalTransportTest : LocalTransportTest() { // 'null' is not supported in ProtoBuf @Test - override fun nullable(): TestResult = runTest { } + override fun nullableReturn(): TestResult = runTest { } @Test override fun testByteArraySerialization(): TestResult = runTest { } diff --git a/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.fir.txt b/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.fir.txt index 28defc553..75ebeecff 100644 --- a/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.fir.txt +++ b/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.fir.txt @@ -48,6 +48,37 @@ FILE: b.kt public final fun copy(flow1: R|kotlinx/coroutines/flow/Flow| = this@R|/MultiFlow|.R|/MultiFlow.flow1|, flow2: R|kotlinx/coroutines/flow/Flow| = this@R|/MultiFlow|.R|/MultiFlow.flow2|, flow3: R|kotlinx/coroutines/flow/Flow| = this@R|/MultiFlow|.R|/MultiFlow.flow3|): R|MultiFlow| + } + public final data class ComplexFilter : R|kotlin/Any| { + public constructor(a: R|SimpleFilter?|, b: R|ComplexFilter?|): R|ComplexFilter| { + super() + } + + public final val a: R|SimpleFilter?| = R|/a| + public get(): R|SimpleFilter?| + + public final val b: R|ComplexFilter?| = R|/b| + public get(): R|ComplexFilter?| + + public final operator fun component1(): R|SimpleFilter?| + + public final operator fun component2(): R|ComplexFilter?| + + public final fun copy(a: R|SimpleFilter?| = this@R|/ComplexFilter|.R|/ComplexFilter.a|, b: R|ComplexFilter?| = this@R|/ComplexFilter|.R|/ComplexFilter.b|): R|ComplexFilter| + + } + public final data class SimpleFilter : R|kotlin/Any| { + public constructor(text: R|kotlin/String|): R|SimpleFilter| { + super() + } + + public final val text: R|kotlin/String| = R|/text| + public get(): R|kotlin/String| + + public final operator fun component1(): R|kotlin/String| + + public final fun copy(text: R|kotlin/String| = this@R|/SimpleFilter|.R|/SimpleFilter.text|): R|SimpleFilter| + } @R|kotlinx/rpc/annotations/Rpc|() public abstract interface MyService : R|kotlin/Any| { public abstract val flow: R|kotlinx/coroutines/flow/Flow| @@ -105,6 +136,8 @@ FILE: b.kt public abstract fun nonSuspendNoFlowString(): R|kotlin/String| + public abstract suspend fun complex(filter: R|ComplexFilter|): R|kotlin/String| + 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/strictMode.kt b/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.kt index 501d2a397..63293be73 100644 --- a/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.kt +++ b/tests/compiler-plugin-tests/src/testData/diagnostics/strictMode.kt @@ -29,6 +29,13 @@ data class MultiFlow( val flow3: Flow, ) +data class ComplexFilter( + val a: SimpleFilter?, + val b: ComplexFilter? +) + +data class SimpleFilter(val text: String) + @Rpc interface MyService { val flow: Flow @@ -57,4 +64,5 @@ interface MyService { suspend fun clientNestedTrickyFlow(inner: Wrapper>>>) fun nonSuspendNoFlow() fun nonSuspendNoFlowString(): String + suspend fun complex(filter: ComplexFilter): String // doesn't fail on circular dependency } diff --git a/updateTestData.sh b/updateTestData.sh index 6233f8299..00ed61720 100755 --- a/updateTestData.sh +++ b/updateTestData.sh @@ -1,7 +1,7 @@ #!/bin/bash # -# Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. +# Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. # if [ "$#" -ne 1 ] && [ "$#" -ne 2 ]; then @@ -23,4 +23,5 @@ set -o xtrace :tests:compiler-plugin-tests:test \ --tests "kotlinx.rpc.codegen.test.runners.$1Generated$TEST_NAME" \ --continue \ + --stacktrace \ -Pkotlin.test.update.test.data=true