Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a333efa
Update Kotlin to 2.3.0-Beta1
rickclephas Oct 12, 2025
0dedacb
KT-55300 add pluginId to CompilerPluginRegistrar
rickclephas Sep 21, 2025
e50ebb3
Update generateTestGroupSuiteWithJUnit5 import
rickclephas Oct 12, 2025
6c8dea4
Update compiler codegen dumps for Kotlin 2.3.0-Beta1
rickclephas Oct 12, 2025
ecc62c7
Update compiler API dump
rickclephas Oct 12, 2025
84dce58
Update kotlin compile testing to 0.9.0
rickclephas Oct 12, 2025
ac28467
Merge branch 'master' into feature/kotlin-2.3.0
rickclephas Oct 29, 2025
8f285da
Update Kotlin to 2.3.0-Beta2
rickclephas Oct 29, 2025
5481bc3
Merge branch 'feature/kotlin-2.3.0' into feature/swift-export-2.3.0
rickclephas Nov 8, 2025
9ed1bf9
Update codegen test dumps
rickclephas Nov 8, 2025
562da04
Enable Swift export coroutines support
rickclephas Nov 8, 2025
82bad7e
Add NO_THROWS_SUSPEND_FUNC SwiftExport option
rickclephas Nov 8, 2025
450a161
Add codegen tests for 2.3.0-Beta2 swift export
rickclephas Nov 8, 2025
3ff32fc
Add asyncFunction(for:) Swift export migration variant
rickclephas Nov 8, 2025
66b4ab7
Enable most asyncFunction(for:) tests
rickclephas Nov 8, 2025
eee6581
Disable some unsupported suspend functions
rickclephas Nov 8, 2025
d62ad38
Document throwing suspend function limitation
rickclephas Nov 8, 2025
52ca269
Add NativeCoroutinesObjCExport to hide declarations from Swift export
rickclephas Nov 9, 2025
bcaff81
Add YouTrack references to unsupported Swift export code
rickclephas Nov 9, 2025
0e3bc92
Add asyncResult(for:) Swift export migration variant
rickclephas Nov 9, 2025
76020f5
Add createFuture(for:) Swift export migration variant
rickclephas Nov 9, 2025
56dc53e
Add createSingle(for:) Swift export migration variant
rickclephas Nov 9, 2025
df90b6d
Bump sample deployment targets for Kotlin 2.3.0
rickclephas Nov 9, 2025
64951ea
Merge branch 'master' into feature/kotlin-2.3.0
rickclephas Nov 9, 2025
e33b677
Merge branch 'feature/kotlin-2.3.0' into feature/swift-export-2.3.0
rickclephas Nov 9, 2025
ed287fc
Update API dumps
rickclephas Nov 9, 2025
cae87c6
Specify minimum targets for RxSwift Swift export function
rickclephas Nov 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions KMPNativeCoroutinesAsync/AsyncFunction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ public func asyncFunction<Result, Failure: Error, Unit>(
try await AsyncFunctionTask(nativeSuspend: nativeSuspend).awaitResult()
}

/// This function provides source compatibility during the migration to Swift export.
///
/// This is a no-op function and it can be safely removed once you have fully migrated to Swift export.
@available(*, deprecated, message: "Kotlin Coroutines are supported by Swift export")
public func asyncFunction<Result>(for result: Result) async -> Result {
return result
}

/// Wraps the `NativeSuspend` in an async function.
/// - Parameter nativeSuspend: The native suspend function to await.
/// - Throws: Errors thrown by the `nativeSuspend`.
Expand Down
14 changes: 14 additions & 0 deletions KMPNativeCoroutinesAsync/AsyncResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,20 @@ public func asyncResult<Output, Failure: Error, Unit>(
}
}

/// This function provides source compatibility during the migration to Swift export.
///
/// Once you have fully migrated to Swift export you can replace this function with a do-catch.
@available(*, deprecated, message: "Kotlin Coroutines are supported by Swift export")
public func asyncResult<Output>(
for output: @autoclosure () async throws -> Output
) async -> Result<Output, Error> {
do {
return .success(try await output())
} catch {
return .failure(error)
}
}

/// Awaits the `NativeSuspend` and returns the result.
/// - Parameter nativeSuspend: The native suspend function to await.
/// - Returns: The `Result` from the `nativeSuspend`.
Expand Down
59 changes: 59 additions & 0 deletions KMPNativeCoroutinesCombine/Future.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ public func createFuture<Result, Failure: Error, Unit>(
.eraseToAnyPublisher()
}

/// This function provides source compatibility during the migration to Swift export.
///
/// You should migrate away from this function once you have fully migrated to Swift export.
@available(*, deprecated, message: "Kotlin Coroutines are supported by Swift export")
public func createFuture<Result>(
for operation: @escaping () async throws -> Result
) -> AnyPublisher<Result, Error> {
return TaskFuture(operation: operation)
.eraseToAnyPublisher()
}

/// Creates an `AnyPublisher` for the provided `NativeSuspend`.
/// - Parameter nativeSuspend: The native suspend function to await.
/// - Returns: A publisher that either finishes with a single value or fails with an error.
Expand Down Expand Up @@ -78,3 +89,51 @@ internal class NativeSuspendSubscription<Result, Failure, Unit, S: Subscriber>:
nativeCancellable = nil
}
}

internal struct TaskFuture<Result>: Publisher {

typealias Output = Result
typealias Failure = Error

let operation: () async throws -> Result

func receive<S>(subscriber: S) where S : Subscriber, any Failure == S.Failure, Result == S.Input {
let subscription = TaskSubscription(operation: operation, subscriber: subscriber)
subscriber.receive(subscription: subscription)
}
}

internal class TaskSubscription<Result, S: Subscriber>: Subscription where S.Input == Result, S.Failure == Error {

private var operation: (() async throws -> Result)?
private var task: Task<Void, Never>? = nil
private var subscriber: S?

init(operation: @escaping () async throws -> Result, subscriber: S) {
self.operation = operation
self.subscriber = subscriber
}

func request(_ demand: Subscribers.Demand) {
guard let operation = operation, demand >= 1 else { return }
self.operation = nil
task = Task {
do {
let result = try await operation()
if let subscriber = self.subscriber {
_ = subscriber.receive(result)
subscriber.receive(completion: .finished)
}
} catch {
self.subscriber?.receive(completion: .failure(error))
}
}
}

func cancel() {
subscriber = nil
operation = nil
task?.cancel()
task = nil
}
}
23 changes: 23 additions & 0 deletions KMPNativeCoroutinesRxSwift/Single.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,29 @@ public func createSingle<Result, Failure: Error, Unit>(
return createSingleImpl(for: nativeSuspend)
}

/// This function provides source compatibility during the migration to Swift export.
///
/// You should migrate away from this function once you have fully migrated to Swift export.
@available(*, deprecated, message: "Kotlin Coroutines are supported by Swift export")
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
public func createSingle<Result>(
for operation: @escaping () async throws -> Result
) -> Single<Result> {
return Single.deferred {
return Single.create { observer in
let task = Task {
do {
let result = try await operation()
observer(.success(result))
} catch {
observer(.failure(error))
}
}
return Disposables.create { task.cancel() }
}
}
}

/// Creates a `Single` for the provided `NativeSuspend`.
/// - Parameter nativeSuspend: The native suspend function to await.
/// - Returns: A single that either finishes with a single value or fails with an error.
Expand Down
7 changes: 7 additions & 0 deletions SWIFT_EXPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ For now the plugin just clones your original functions and properties to prevent
**Temporary workaround:**
You should disable any relevant code in Swift if you would like to try Swift export.

## 🚨 `@Throws` suspend functions are unsupported

Throwing suspend functions aren't supported yet.

KMP-NativeCoroutines behaves as if a `@Throws(Exception::class)` annotation was added to all suspend functions.
Since throwing suspend functions aren't supported yet, any exception will currently cause a fatal crash.

## ⚠️ `@ObjCName` is ignored

The `@ObjCName` annotation is (currently) ignored by Swift export.
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ buildscript {

allprojects {
group = "com.rickclephas.kmp"
version = "1.0.0-ALPHA-48"
version = "1.0.0-ALPHA-48-kotlin-2.3.0-Beta2"
}

apiValidation {
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[versions]
kotlin = "2.2.21"
kotlin = "2.3.0-Beta2"
kotlin-idea = "2.2.0-ij251-78"
kotlinx-coroutines = "1.10.1"
kotlinx-binary-compatibility-validator = "0.16.3"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public final class com/rickclephas/kmp/nativecoroutines/compiler/KmpNativeCorout

public final class com/rickclephas/kmp/nativecoroutines/compiler/KmpNativeCoroutinesCompilerPluginRegistrar : org/jetbrains/kotlin/compiler/plugin/CompilerPluginRegistrar {
public fun <init> ()V
public fun getPluginId ()Ljava/lang/String;
public fun getSupportsK2 ()Z
public fun registerExtensions (Lorg/jetbrains/kotlin/compiler/plugin/CompilerPluginRegistrar$ExtensionStorage;Lorg/jetbrains/kotlin/config/CompilerConfiguration;)V
}
Expand Down Expand Up @@ -104,6 +105,7 @@ public final class com/rickclephas/kmp/nativecoroutines/compiler/config/Suffixes

public final class com/rickclephas/kmp/nativecoroutines/compiler/config/SwiftExport : java/lang/Enum {
public static final field NO_FUNC_RETURN_TYPES Lcom/rickclephas/kmp/nativecoroutines/compiler/config/SwiftExport;
public static final field NO_THROWS_SUSPEND_FUNC Lcom/rickclephas/kmp/nativecoroutines/compiler/config/SwiftExport;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lcom/rickclephas/kmp/nativecoroutines/compiler/config/SwiftExport;
public static fun values ()[Lcom/rickclephas/kmp/nativecoroutines/compiler/config/SwiftExport;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public final class com/rickclephas/kmp/nativecoroutines/compiler/KmpNativeCorout

public final class com/rickclephas/kmp/nativecoroutines/compiler/KmpNativeCoroutinesCompilerPluginRegistrar : org/jetbrains/kotlin/compiler/plugin/CompilerPluginRegistrar {
public fun <init> ()V
public fun getPluginId ()Ljava/lang/String;
public fun getSupportsK2 ()Z
public fun registerExtensions (Lorg/jetbrains/kotlin/compiler/plugin/CompilerPluginRegistrar$ExtensionStorage;Lorg/jetbrains/kotlin/config/CompilerConfiguration;)V
}
Expand Down Expand Up @@ -104,6 +105,7 @@ public final class com/rickclephas/kmp/nativecoroutines/compiler/config/Suffixes

public final class com/rickclephas/kmp/nativecoroutines/compiler/config/SwiftExport : java/lang/Enum {
public static final field NO_FUNC_RETURN_TYPES Lcom/rickclephas/kmp/nativecoroutines/compiler/config/SwiftExport;
public static final field NO_THROWS_SUSPEND_FUNC Lcom/rickclephas/kmp/nativecoroutines/compiler/config/SwiftExport;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lcom/rickclephas/kmp/nativecoroutines/compiler/config/SwiftExport;
public static fun values ()[Lcom/rickclephas/kmp/nativecoroutines/compiler/config/SwiftExport;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrarAdapter
@OptIn(ExperimentalCompilerApi::class)
public class KmpNativeCoroutinesCompilerPluginRegistrar: CompilerPluginRegistrar() {

override val pluginId: String = "com.rickclephas.kmp.nativecoroutines"
override val supportsK2: Boolean = true

override fun ExtensionStorage.registerExtensions(configuration: CompilerConfiguration) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.jetbrains.kotlin.utils.filterToSetOrEmpty

public enum class SwiftExport {
NO_FUNC_RETURN_TYPES,
NO_THROWS_SUSPEND_FUNC,
}

public val SWIFT_EXPORT: ConfigOptionWithDefault<Set<SwiftExport>> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ internal fun FirExtension.buildNativeFunction(
if (annotation.shouldRefineInSwift) {
annotations.add(buildAnnotation(ClassIds.shouldRefineInSwift))
}
if (SwiftExport.NO_FUNC_RETURN_TYPES in swiftExport && callableSignature.isSuspend) {
if (SwiftExport.NO_FUNC_RETURN_TYPES in swiftExport &&
SwiftExport.NO_THROWS_SUSPEND_FUNC !in swiftExport &&
callableSignature.isSuspend
) {
annotations.add(buildThrowsAnnotation(ClassIds.exception))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.rickclephas.kmp.nativecoroutines.compiler.fir.codegen.buildNativeFunc
import com.rickclephas.kmp.nativecoroutines.compiler.fir.codegen.buildNativeProperty
import com.rickclephas.kmp.nativecoroutines.compiler.fir.codegen.buildSharedFlowReplayCacheProperty
import com.rickclephas.kmp.nativecoroutines.compiler.fir.codegen.buildStateFlowValueProperty
import com.rickclephas.kmp.nativecoroutines.compiler.utils.FqNames
import com.rickclephas.kmp.nativecoroutines.compiler.utils.NativeCoroutinesAnnotation
import com.rickclephas.kmp.nativecoroutines.compiler.utils.NativeCoroutinesAnnotation.NativeCoroutines
import com.rickclephas.kmp.nativecoroutines.compiler.utils.NativeCoroutinesAnnotation.NativeCoroutinesIgnore
Expand All @@ -19,6 +20,7 @@ import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.DirectDeclarationsAccess
import org.jetbrains.kotlin.fir.declarations.utils.isExpect
import org.jetbrains.kotlin.fir.extensions.*
import org.jetbrains.kotlin.fir.extensions.predicate.DeclarationPredicate
import org.jetbrains.kotlin.fir.extensions.predicate.LookupPredicate
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.scopes.getFunctions
Expand All @@ -41,9 +43,13 @@ internal class KmpNativeCoroutinesDeclarationGenerationExtension(
private val lookupPredicate = LookupPredicate.AnnotatedWith(
NativeCoroutinesAnnotation.entries.map { it.fqName }.toSet()
)
private val objcExportPredicate = DeclarationPredicate.AnnotatedWith(
setOf(FqNames.nativeCoroutinesObjCExport)
)

override fun FirDeclarationPredicateRegistrar.registerPredicates() {
register(lookupPredicate)
register(objcExportPredicate)
}

override fun getCallableNamesForClass(
Expand Down Expand Up @@ -102,6 +108,7 @@ internal class KmpNativeCoroutinesDeclarationGenerationExtension(

private fun getAnnotationForSymbol(symbol: FirCallableSymbol<*>): NativeCoroutinesAnnotation? {
if (symbol.rawStatus.isOverride || symbol.isExpect) return null
if (swiftExport.isNotEmpty() && session.predicateBasedProvider.matches(objcExportPredicate, symbol)) return null
return predicates.entries.singleOrNull { (_, predicate) ->
session.predicateBasedProvider.matches(predicate, symbol)
}?.key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ internal object FqNames {

val observableViewModel = FqName("com.rickclephas.kmp.observableviewmodel.ViewModel")
val androidxViewModel = FqName("androidx.lifecycle.ViewModel")

val nativeCoroutinesObjCExport = FqName("com.rickclephas.kmp.nativecoroutines.sample.NativeCoroutinesObjCExport")
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.util.KtTestUtil;
import org.jetbrains.kotlin.test.TargetBackend;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand All @@ -19,7 +18,7 @@
public class FirLightTreeCodegenTestGenerated extends AbstractFirLightTreeCodegenTest {
@Test
public void testAllFilesPresentInCodegen() {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("src/testData/codegen"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kt$"), TargetBackend.JVM_IR, true);
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("src/testData/codegen"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kt$"), true);
}

@Test
Expand Down Expand Up @@ -58,7 +57,7 @@ public void testViewmodelscope() {
public class Swift1 {
@Test
public void testAllFilesPresentInSwift1() {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("src/testData/codegen/swift1"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kt$"), TargetBackend.JVM_IR, true);
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("src/testData/codegen/swift1"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kt$"), true);
}

@Test
Expand Down Expand Up @@ -91,4 +90,44 @@ public void testViewmodelscope() {
runTest("src/testData/codegen/swift1/viewmodelscope.kt");
}
}

@Nested
@TestMetadata("src/testData/codegen/swift3")
@TestDataPath("$PROJECT_ROOT")
public class Swift3 {
@Test
public void testAllFilesPresentInSwift3() {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("src/testData/codegen/swift3"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.fir\\.kt$"), true);
}

@Test
@TestMetadata("annotations.kt")
public void testAnnotations() {
runTest("src/testData/codegen/swift3/annotations.kt");
}

@Test
@TestMetadata("coroutinescope.kt")
public void testCoroutinescope() {
runTest("src/testData/codegen/swift3/coroutinescope.kt");
}

@Test
@TestMetadata("functions.kt")
public void testFunctions() {
runTest("src/testData/codegen/swift3/functions.kt");
}

@Test
@TestMetadata("properties.kt")
public void testProperties() {
runTest("src/testData/codegen/swift3/properties.kt");
}

@Test
@TestMetadata("viewmodelscope.kt")
public void testViewmodelscope() {
runTest("src/testData/codegen/swift3/viewmodelscope.kt");
}
}
}
Loading
Loading