KMP-NativeCoroutines icon indicating copy to clipboard operation
KMP-NativeCoroutines copied to clipboard

Add support for Swift to Kotlin cases

Open rickclephas opened this issue 2 years ago • 2 comments

Currently the whole library is focused on Kotlin to Swift cases but there are also valid cases for Swift to Kotlin support.

For example you should be able to call the following function from Swift:

suspend fun doSomething(something: suspend (String) -> String): String = TODO()

https://kotlinlang.slack.com/archives/C3PQML5NU/p1644904527044239

rickclephas avatar Feb 15 '22 11:02 rickclephas

Some thoughts about Swift implementations of Kotlin classes/interfaces.

Kotlin code:

interface A {
    suspend fun foo(): String
    suspend fun bar(): String {
        return "Default"
    }
}

open class AKotlinImpl: A {
    suspend fun foo(): String {
        return "Kotlin"
    }
}

class AKotlinImplNoDefaults: AKotlinImpl {
    override suspend fun bar(): String {
        return "Kotlin"
    }
}

suspend fun main() {
    AKotlinImpl().run {
        println(foo()) // Kotlin
        println(bar()) // Default
    }
    AKotlinImplNoDefaults().run {
        println(foo()) // Kotlin
        println(bar()) // Kotlin
    }
}

Generated Kotlin code:

abstract class ANativeImpl: A {
    final override suspend fun foo(): String { /* ... */ }
    abstract fun fooNativeImpl(): NativeSuspend<String>

    final override suspend fun bar(): String { /* ... */ }
    open fun barNativeImpl(): NativeSuspend<String> { /* ... */ }
}

fun A.fooNative(): NativeSuspend<String> { /* ... */ }
fun A.barNative(): NativeSuspend<String> { /* ... */ }

Swift code:

class ASwiftImpl: ANativeImpl {
    override func fooNativeImpl() -> NativeSuspend<String> {
        return nativeSuspend {
            return "Swift"
        }
    }
}

class ASwiftImplNoDefaults: ASwiftImpl {
    override func barNativeImpl() -> NativeSuspend<String> {
        return nativeSuspend {
            return "Swift"
        }
    }
}

func main() async {
    let impl = ASwiftImpl()
    print(await asyncFunction(for: impl.fooNative())) // Swift
    print(await asyncFunction(for: impl.barNative())) // Default
    let implNoDefaults = ASwiftImplNoDefaults()
    print(await asyncFunction(for: implNoDefaults.fooNative())) // Swift
    print(await asyncFunction(for: implNoDefaults.barNative())) // Swift
}

Note: this won't work because A is an interface and doesn't expose the extension functions like this. Support for interfaces is limited anyway because ObjC doesn't support default implementations.

Note: this way calling the Swift implementations from Swift will convert from async to NativeSuspend and back to async. To fix that we would need an extension/wrapper function in Swift that can directly call the async version instead. That would need some kind of Swift codegen (during Kotlin compile) to make this easy to use.

rickclephas avatar Apr 22 '22 18:04 rickclephas

Just dropping a note to say I too arrived here looking for a library to help with consuming a Swift ASyncStream from Kotlin/Native.

chris-hatton avatar Jul 28 '23 02:07 chris-hatton