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 12b363b3c..753eb8c52 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 @@ -797,7 +797,7 @@ internal class RpcStubGenerator( type = ctx.rpcCallable.typeWith(declaration.serviceType), symbol = ctx.rpcCallableDefault.constructors.single(), typeArgumentsCount = 1, - valueArgumentsCount = 5, + valueArgumentsCount = 6, constructorTypeArgumentsCount = 1, ) }.apply { @@ -805,15 +805,7 @@ internal class RpcStubGenerator( callable as ServiceDeclaration.Method - val returnType = when { - callable.function.isNonSuspendingWithFlowReturn() -> { - (callable.function.returnType as IrSimpleType).arguments.single().typeOrFail - } - - else -> { - callable.function.returnType - } - } + val returnType = callable.function.returnType val invokator = invokators[callable.name] ?: error("Expected invokator for ${callable.name} in ${declaration.service.name}") @@ -889,6 +881,8 @@ internal class RpcStubGenerator( } } + val returnsFlowFlag = (callable.function.returnType.classOrNull == ctx.flow) + arguments { values { +stringConst(callable.name) @@ -900,6 +894,7 @@ internal class RpcStubGenerator( +arrayOfCall +booleanConst(!callable.function.isSuspend) + +booleanConst(returnsFlowFlag) } } } diff --git a/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptor.kt b/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptor.kt index 77dbe83f7..9473a52d3 100644 --- a/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptor.kt +++ b/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptor.kt @@ -56,6 +56,12 @@ public interface RpcCallable<@Rpc T : Any> { public val invokator: RpcInvokator public val parameters: Array public val isNonSuspendFunction: Boolean + /** + * True if the method returns Flow<...> and should be treated as a streaming return. + * The [returnType] remains the original declared KType (including Flow<...>), + * consumers can use this flag to branch logic without relying on type unwrapping. + */ + public val returnsFlow: Boolean } @ExperimentalRpcApi diff --git a/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptorDefault.kt b/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptorDefault.kt index f3209ba48..8348da67c 100644 --- a/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptorDefault.kt +++ b/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptorDefault.kt @@ -15,6 +15,7 @@ public class RpcCallableDefault<@Rpc T : Any>( override val invokator: RpcInvokator, override val parameters: Array, override val isNonSuspendFunction: Boolean, + override val returnsFlow: Boolean, ) : RpcCallable @InternalRpcApi diff --git a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/descriptor/IsFlowIntegrationTest.kt b/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/descriptor/IsFlowIntegrationTest.kt new file mode 100644 index 000000000..6ef060e55 --- /dev/null +++ b/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/descriptor/IsFlowIntegrationTest.kt @@ -0,0 +1,38 @@ +package kotlinx.rpc.descriptor + +import kotlinx.coroutines.flow.Flow +import kotlinx.rpc.annotations.Rpc +import kotlin.reflect.typeOf +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@Rpc +interface NewsServiceForFlowTest { + fun stream(): Flow + suspend fun greet(name: String): String +} + +class IsFlowIntegrationTest { + @Test + fun stream_isFlow_and_returnType_is_Flow() { + val descriptor = serviceDescriptorOf() + val stream = descriptor.callables["stream"] ?: error("stream not found") + + assertEquals(typeOf>().toString(), stream.returnType.kType.toString()) + assertTrue(stream.returnsFlow) + assertTrue(stream.isNonSuspendFunction) + } + + @Test + fun greet_notFlow_and_returnType_is_String() { + val descriptor = serviceDescriptorOf() + val greet = descriptor.callables["greet"] ?: error("greet not found") + + assertEquals(typeOf().toString(), greet.returnType.kType.toString()) + assertFalse(greet.returnsFlow) + // greet is suspend now + kotlin.test.assertFalse(greet.isNonSuspendFunction) + } +}