diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt index bd6c266ead9..7869da77641 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt @@ -65,6 +65,11 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { } if (irClass.isInterface) result = result or TF_INTERFACE + + if (irClass.defaultType.isSuspendFunction()) { + result = result or TF_SUSPEND_FUNCTION + } + return result } @@ -623,4 +628,5 @@ private const val TF_ACYCLIC = 2 private const val TF_INTERFACE = 4 private const val TF_OBJC_DYNAMIC = 8 private const val TF_LEAK_DETECTOR_CANDIDATE = 16 +private const val TF_SUSPEND_FUNCTION = 32 diff --git a/backend.native/tests/objcexport/coroutines.kt b/backend.native/tests/objcexport/coroutines.kt index ca20f682b02..15f60c76955 100644 --- a/backend.native/tests/objcexport/coroutines.kt +++ b/backend.native/tests/objcexport/coroutines.kt @@ -164,3 +164,15 @@ class ThrowCancellationExceptionImpl : ThrowCancellationException() { throw CancellationException() } } + +fun getSuspendLambda0(): suspend () -> String = { "lambda 0" } + +private suspend fun suspendCallableReference0Target(): String = "callable reference 0" +fun getSuspendCallableReference0(): suspend () -> String = ::suspendCallableReference0Target + +fun getSuspendLambda1(): suspend (String) -> String = { "$it 1" } + +private suspend fun suspendCallableReference1Target(str: String): String = "$str 1" +fun getSuspendCallableReference1(): suspend (String) -> String = ::suspendCallableReference1Target + +suspend fun invoke1(block: suspend (Any?) -> Any?, argument: Any?): Any? = block(argument) diff --git a/backend.native/tests/objcexport/coroutines.swift b/backend.native/tests/objcexport/coroutines.swift index 8ca42a3eecb..a3e37913749 100644 --- a/backend.native/tests/objcexport/coroutines.swift +++ b/backend.native/tests/objcexport/coroutines.swift @@ -253,6 +253,71 @@ private func testImplicitThrows2() throws { try assertTrue(error?.kotlinException is KotlinCancellationException) } +private func testSuspendFunctionType0(f: KotlinSuspendFunction0, expectedResult: String) throws { + try assertTrue((f as AnyObject) is KotlinSuspendFunction0) + + var result: String? = nil + var error: Error? = nil + var completionCalled = 0 + + f.invoke { _result, _error in + completionCalled += 1 + result = _result as? String + error = _error + } + + try assertEquals(actual: completionCalled, expected: 1) + try assertEquals(actual: result, expected: expectedResult) + try assertNil(error) +} + +private func testSuspendFunctionType1(f: KotlinSuspendFunction1) throws { + try assertTrue((f as AnyObject) is KotlinSuspendFunction1) + + var result: String? = nil + var error: Error? = nil + var completionCalled = 0 + + f.invoke(p1: "suspend function type") { _result, _error in + completionCalled += 1 + result = _result as? String + error = _error + } + + try assertEquals(actual: completionCalled, expected: 1) + try assertEquals(actual: result, expected: "suspend function type 1") + try assertNil(error) +} + +private func testSuspendFunctionType() throws { + try testSuspendFunctionType0(f: CoroutinesKt.getSuspendLambda0(), expectedResult: "lambda 0") + try testSuspendFunctionType0(f: CoroutinesKt.getSuspendCallableReference0(), expectedResult: "callable reference 0") + try testSuspendFunctionType1(f: CoroutinesKt.getSuspendLambda1()) + try testSuspendFunctionType1(f: CoroutinesKt.getSuspendCallableReference1()) +} + +private func testSuspendFunctionSwiftImpl() throws { + var result: String? = nil + var error: Error? = nil + var completionCalled = 0 + + CoroutinesKt.invoke1(block: SuspendFunction1SwiftImpl(), argument: "suspend function") { _result, _error in + completionCalled += 1 + result = _result as? String + error = _error + } + + try assertEquals(actual: completionCalled, expected: 1) + try assertEquals(actual: result, expected: "suspend function Swift") + try assertNil(error) +} + +private class SuspendFunction1SwiftImpl : KotlinSuspendFunction1 { + func invoke(p1: Any?, completionHandler: (Any?, Error?) -> Void) { + completionHandler("\(p1 ?? "nil") Swift", nil) + } +} + class CoroutinesTests : SimpleTestProvider { override init() { super.init() @@ -263,5 +328,7 @@ class CoroutinesTests : SimpleTestProvider { test("TestBridges", testBridges) test("TestImplicitThrows1", testImplicitThrows1) test("TestImplicitThrows2", testImplicitThrows2) + test("TestSuspendFunctionType", testSuspendFunctionType) + test("TestSuspendFunctionSwiftImpl", testSuspendFunctionSwiftImpl) } } \ No newline at end of file diff --git a/backend.native/tests/objcexport/expectedLazy.h b/backend.native/tests/objcexport/expectedLazy.h index f67022271e7..8f13bc30c01 100644 --- a/backend.native/tests/objcexport/expectedLazy.h +++ b/backend.native/tests/objcexport/expectedLazy.h @@ -306,6 +306,16 @@ __attribute__((swift_name("CoroutinesKt"))) Other uncaught Kotlin exceptions are fatal. */ + (void)throwCancellationExceptionWithCompletionHandler:(void (^)(KtKotlinUnit * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("throwCancellationException(completionHandler:)"))); ++ (id)getSuspendLambda0 __attribute__((swift_name("getSuspendLambda0()"))); ++ (id)getSuspendCallableReference0 __attribute__((swift_name("getSuspendCallableReference0()"))); ++ (id)getSuspendLambda1 __attribute__((swift_name("getSuspendLambda1()"))); ++ (id)getSuspendCallableReference1 __attribute__((swift_name("getSuspendCallableReference1()"))); + +/** + @note This method converts instances of CancellationException to errors. + Other uncaught Kotlin exceptions are fatal. +*/ ++ (void)invoke1Block:(id)block argument:(id _Nullable)argument completionHandler:(void (^)(id _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("invoke1(block:argument:completionHandler:)"))); @end; __attribute__((swift_name("FHolder"))) diff --git a/runtime/src/main/cpp/ObjCExport.mm b/runtime/src/main/cpp/ObjCExport.mm index fbd15db6b0a..f1b4df001e5 100644 --- a/runtime/src/main/cpp/ObjCExport.mm +++ b/runtime/src/main/cpp/ObjCExport.mm @@ -539,6 +539,26 @@ static convertReferenceToObjC findConverterFromInterfaces(const TypeInfo* typeIn for (int i = 0; i < typeInfo->implementedInterfacesCount_; ++i) { const TypeInfo* interfaceTypeInfo = typeInfo->implementedInterfaces_[i]; + if ((interfaceTypeInfo->flags_ & TF_SUSPEND_FUNCTION) != 0) { + // interfaceTypeInfo is a SuspendFunction$N interface. + // So any instance of typeInfo is a suspend lambda or a suspend callable reference + // (user-defined Kotlin classes implementing SuspendFunction$N are prohibited by the compiler). + // + // Such types also actually implement Function${N+1} interface as an optimization + // (see e.g. [startCoroutineUninterceptedOrReturn implementation). + // This fact is not user-visible, so ignoring Function${N+1} interface here + // (and thus not converting such objects to Obj-C blocks) should be safe enough + // (because such objects aren't expected to be passed from Kotlin to Swift + // under formal Function${N+1} type). + // + // On the other hand, this fixes support for SuspendFunction$N type: it is mapped as + // regular Kotlin interface, so its instances should be converted on a general basis + // (i.e. to objects implementing Obj-C representation of SuspendFunction$N, not to Obj-C blocks). + // + // "If typeInfo is a suspend lambda or callable reference type, convert its instances on a regular basis": + return nullptr; + } + if (interfaceTypeInfo->writableInfo_->objCExport.convert != nullptr) { if (foundTypeInfo == nullptr || IsSubInterface(interfaceTypeInfo, foundTypeInfo)) { foundTypeInfo = interfaceTypeInfo; diff --git a/runtime/src/main/cpp/TypeInfo.h b/runtime/src/main/cpp/TypeInfo.h index 2bbb698e6b1..daf011a5088 100644 --- a/runtime/src/main/cpp/TypeInfo.h +++ b/runtime/src/main/cpp/TypeInfo.h @@ -59,7 +59,8 @@ enum Konan_TypeFlags { TF_ACYCLIC = 1 << 1, TF_INTERFACE = 1 << 2, TF_OBJC_DYNAMIC = 1 << 3, - TF_LEAK_DETECTOR_CANDIDATE = 1 << 4 + TF_LEAK_DETECTOR_CANDIDATE = 1 << 4, + TF_SUSPEND_FUNCTION = 1 << 5, }; // Flags per object instance.