From 728fe918fe1a165843f4da7c61900d4602cad459 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Thu, 25 Aug 2022 11:51:57 +0200 Subject: [PATCH] Native: don't expect that startCoroutineUninterceptedOrReturn receiver is FunctionN+1 Previous implementation of startCoroutineUninterceptedOrReturn expected that the receiver (i.e. the suspend function object to start) of type SuspendFunctionN (i.e. suspend function type with arity = N) is also an instance of FunctionN+1 (i.e. regular function type with arity = N + 1) with proper convention. While compiler tries to achieve that (for better compatibility with Kotlin/JVM, by generating additional supertypes and methods for classes implementing suspend function types), this doesn't work e.g. for implementations defined in Swift. Fix this by adding fallbacks for cases when the receiver is not a FunctionN+1, while keeping the fastpath for default FunctionN+1 cases. ^KT-51043 Fixed --- .../tests/objcexport/coroutines.kt | 21 +++++++ .../tests/objcexport/coroutines.swift | 62 +++++++++++++++++++ .../tests/objcexport/expectedLazy.h | 25 ++++++++ .../expectedLazyLegacySuspendUnit.h | 25 ++++++++ .../tests/objcexport/expectedLazyNoGenerics.h | 25 ++++++++ .../coroutines/intrinsics/IntrinsicsNative.kt | 62 ++++++++++++++++++- 6 files changed, 217 insertions(+), 3 deletions(-) diff --git a/kotlin-native/backend.native/tests/objcexport/coroutines.kt b/kotlin-native/backend.native/tests/objcexport/coroutines.kt index 3acf45144b7..2e361cc90f4 100644 --- a/kotlin-native/backend.native/tests/objcexport/coroutines.kt +++ b/kotlin-native/backend.native/tests/objcexport/coroutines.kt @@ -222,4 +222,25 @@ suspend fun invoke1(block: suspend (Any?) -> Any?, argument: Any?): Any? = block fun getKSuspendCallableReference0(): KSuspendFunction0 = ::suspendCallableReference0Target fun getKSuspendCallableReference1(): KSuspendFunction1 = ::suspendCallableReference1Target +@Throws(Throwable::class) +fun startCoroutineUninterceptedOrReturn(fn: suspend () -> Any?, resultHolder: ResultHolder) = + fn.startCoroutineUninterceptedOrReturn(ResultHolderCompletion(resultHolder)) + +@Throws(Throwable::class) +fun startCoroutineUninterceptedOrReturn(fn: suspend Any?.() -> Any?, receiver: Any?, resultHolder: ResultHolder) = + fn.startCoroutineUninterceptedOrReturn(receiver, ResultHolderCompletion(resultHolder)) + +@Throws(Throwable::class) +@Suppress("INVISIBLE_MEMBER") +fun startCoroutineUninterceptedOrReturn(fn: suspend Any?.(Any?) -> Any?, receiver: Any?, param: Any?, resultHolder: ResultHolder) = + fn.startCoroutineUninterceptedOrReturn(receiver, param, ResultHolderCompletion(resultHolder)) + +@Throws(Throwable::class) +fun createCoroutineUninterceptedAndResume(fn: suspend () -> Any?, resultHolder: ResultHolder) = + fn.createCoroutine(ResultHolderCompletion(resultHolder)).resume(Unit) + +@Throws(Throwable::class) +fun createCoroutineUninterceptedAndResume(fn: suspend Any?.() -> Any?, receiver: Any?, resultHolder: ResultHolder) = + fn.createCoroutine(receiver, ResultHolderCompletion(resultHolder)).resume(Unit) + fun gc() = kotlin.native.internal.GC.collect() diff --git a/kotlin-native/backend.native/tests/objcexport/coroutines.swift b/kotlin-native/backend.native/tests/objcexport/coroutines.swift index 819e094ba46..7d00f76641d 100644 --- a/kotlin-native/backend.native/tests/objcexport/coroutines.swift +++ b/kotlin-native/backend.native/tests/objcexport/coroutines.swift @@ -653,12 +653,69 @@ private func testSuspendFunctionSwiftImpl() throws { try assertNil(error) } +#if NO_GENERICS +typealias AnyResultHolder = ResultHolder +#else +typealias AnyResultHolder = ResultHolder +#endif + +private extension AnyResultHolder { + func getSuccessfulResult() throws -> Any? { + try assertEquals(actual: completed, expected: 1) + try assertNil(exception) + return result + } +} + +// Reported in https://youtrack.jetbrains.com/issue/KT-51043 +private func testSuspendFunction0SwiftImplStartInKotlin() throws { + let resultHolder = AnyResultHolder() + try CoroutinesKt.startCoroutineUninterceptedOrReturn(fn: SuspendFunction0SwiftImpl(), resultHolder: resultHolder) + try assertEquals(actual: resultHolder.getSuccessfulResult() as? String, expected: "Swift") +} + +private func testSuspendFunction1SwiftImplStartInKotlin() throws { + let resultHolder = AnyResultHolder() + try CoroutinesKt.startCoroutineUninterceptedOrReturn(fn: SuspendFunction1SwiftImpl(), receiver: "receiver", resultHolder: resultHolder) + try assertEquals(actual: resultHolder.getSuccessfulResult() as? String, expected: "receiver Swift") +} + +private func testSuspendFunction2SwiftImplStartInKotlin() throws { + let resultHolder = AnyResultHolder() + try CoroutinesKt.startCoroutineUninterceptedOrReturn(fn: SuspendFunction2SwiftImpl(), receiver: "receiver", param: "param", resultHolder: resultHolder) + try assertEquals(actual: resultHolder.getSuccessfulResult() as? String, expected: "receiver param Swift") +} + +private func testSuspendFunction0SwiftImplCreateInKotlin() throws { + let resultHolder = AnyResultHolder() + try CoroutinesKt.createCoroutineUninterceptedAndResume(fn: SuspendFunction0SwiftImpl(), resultHolder: resultHolder) + try assertEquals(actual: resultHolder.getSuccessfulResult() as? String, expected: "Swift") +} + +private func testSuspendFunction1SwiftImplCreateInKotlin() throws { + let resultHolder = AnyResultHolder() + try CoroutinesKt.createCoroutineUninterceptedAndResume(fn: SuspendFunction1SwiftImpl(), receiver: "receiver", resultHolder: resultHolder) + try assertEquals(actual: resultHolder.getSuccessfulResult() as? String, expected: "receiver Swift") +} + +private class SuspendFunction0SwiftImpl : KotlinSuspendFunction0 { + func invoke(completionHandler: (Any?, Error?) -> Void) { + completionHandler("Swift", nil) + } +} + private class SuspendFunction1SwiftImpl : KotlinSuspendFunction1 { func invoke(p1: Any?, completionHandler: (Any?, Error?) -> Void) { completionHandler("\(p1 ?? "nil") Swift", nil) } } +private class SuspendFunction2SwiftImpl : KotlinSuspendFunction2 { + func invoke(p1: Any?, p2: Any?, completionHandler: (Any?, Error?) -> Void) { + completionHandler("\(p1 ?? "nil") \(p2 ?? "nil") Swift", nil) + } +} + class CoroutinesTests : SimpleTestProvider { override init() { super.init() @@ -675,5 +732,10 @@ class CoroutinesTests : SimpleTestProvider { test("TestSuspendFunctionType", testSuspendFunctionType) test("TestKSuspendFunctionType", testSuspendFunctionType) test("TestSuspendFunctionSwiftImpl", testSuspendFunctionSwiftImpl) + test("TestSuspendFunction0SwiftImplStartInKotlin", testSuspendFunction0SwiftImplStartInKotlin) + test("TestSuspendFunction1SwiftImplStartInKotlin", testSuspendFunction1SwiftImplStartInKotlin) + test("TestSuspendFunction2SwiftImplStartInKotlin", testSuspendFunction2SwiftImplStartInKotlin) + test("TestSuspendFunction0SwiftImplCreateInKotlin", testSuspendFunction0SwiftImplCreateInKotlin) + test("TestSuspendFunction1SwiftImplCreateInKotlin", testSuspendFunction1SwiftImplCreateInKotlin) } } diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h index e7d55a1d9ea..82c842b7c79 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazy.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazy.h @@ -382,6 +382,31 @@ __attribute__((swift_name("CoroutinesKt"))) + (void)invoke1Block:(id)block argument:(id _Nullable)argument completionHandler:(void (^)(id _Nullable_result, NSError * _Nullable))completionHandler __attribute__((swift_name("invoke1(block:argument:completionHandler:)"))); + (id)getKSuspendCallableReference0 __attribute__((swift_name("getKSuspendCallableReference0()"))); + (id)getKSuspendCallableReference1 __attribute__((swift_name("getKSuspendCallableReference1()"))); + +/** + * @note This method converts all Kotlin exceptions to errors. +*/ ++ (id _Nullable)startCoroutineUninterceptedOrReturnFn:(id)fn resultHolder:(KtResultHolder *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("startCoroutineUninterceptedOrReturn(fn:resultHolder:)"))) __attribute__((swift_error(nonnull_error))); + +/** + * @note This method converts all Kotlin exceptions to errors. +*/ ++ (id _Nullable)startCoroutineUninterceptedOrReturnFn:(id)fn receiver:(id _Nullable)receiver resultHolder:(KtResultHolder *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("startCoroutineUninterceptedOrReturn(fn:receiver:resultHolder:)"))) __attribute__((swift_error(nonnull_error))); + +/** + * @note This method converts all Kotlin exceptions to errors. +*/ ++ (id _Nullable)startCoroutineUninterceptedOrReturnFn:(id)fn receiver:(id _Nullable)receiver param:(id _Nullable)param resultHolder:(KtResultHolder *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("startCoroutineUninterceptedOrReturn(fn:receiver:param:resultHolder:)"))) __attribute__((swift_error(nonnull_error))); + +/** + * @note This method converts all Kotlin exceptions to errors. +*/ ++ (BOOL)createCoroutineUninterceptedAndResumeFn:(id)fn resultHolder:(KtResultHolder *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("createCoroutineUninterceptedAndResume(fn:resultHolder:)"))); + +/** + * @note This method converts all Kotlin exceptions to errors. +*/ ++ (BOOL)createCoroutineUninterceptedAndResumeFn:(id)fn receiver:(id _Nullable)receiver resultHolder:(KtResultHolder *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("createCoroutineUninterceptedAndResume(fn:receiver:resultHolder:)"))); + (void)gc __attribute__((swift_name("gc()"))); @end diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h b/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h index b6ef75db3be..5c1cc063616 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazyLegacySuspendUnit.h @@ -382,6 +382,31 @@ __attribute__((swift_name("CoroutinesKt"))) + (void)invoke1Block:(id)block argument:(id _Nullable)argument completionHandler:(void (^)(id _Nullable_result, NSError * _Nullable))completionHandler __attribute__((swift_name("invoke1(block:argument:completionHandler:)"))); + (id)getKSuspendCallableReference0 __attribute__((swift_name("getKSuspendCallableReference0()"))); + (id)getKSuspendCallableReference1 __attribute__((swift_name("getKSuspendCallableReference1()"))); + +/** + * @note This method converts all Kotlin exceptions to errors. +*/ ++ (id _Nullable)startCoroutineUninterceptedOrReturnFn:(id)fn resultHolder:(KtResultHolder *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("startCoroutineUninterceptedOrReturn(fn:resultHolder:)"))) __attribute__((swift_error(nonnull_error))); + +/** + * @note This method converts all Kotlin exceptions to errors. +*/ ++ (id _Nullable)startCoroutineUninterceptedOrReturnFn:(id)fn receiver:(id _Nullable)receiver resultHolder:(KtResultHolder *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("startCoroutineUninterceptedOrReturn(fn:receiver:resultHolder:)"))) __attribute__((swift_error(nonnull_error))); + +/** + * @note This method converts all Kotlin exceptions to errors. +*/ ++ (id _Nullable)startCoroutineUninterceptedOrReturnFn:(id)fn receiver:(id _Nullable)receiver param:(id _Nullable)param resultHolder:(KtResultHolder *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("startCoroutineUninterceptedOrReturn(fn:receiver:param:resultHolder:)"))) __attribute__((swift_error(nonnull_error))); + +/** + * @note This method converts all Kotlin exceptions to errors. +*/ ++ (BOOL)createCoroutineUninterceptedAndResumeFn:(id)fn resultHolder:(KtResultHolder *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("createCoroutineUninterceptedAndResume(fn:resultHolder:)"))); + +/** + * @note This method converts all Kotlin exceptions to errors. +*/ ++ (BOOL)createCoroutineUninterceptedAndResumeFn:(id)fn receiver:(id _Nullable)receiver resultHolder:(KtResultHolder *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("createCoroutineUninterceptedAndResume(fn:receiver:resultHolder:)"))); + (void)gc __attribute__((swift_name("gc()"))); @end diff --git a/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h b/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h index ad94862b71d..83faf80c6b7 100644 --- a/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h +++ b/kotlin-native/backend.native/tests/objcexport/expectedLazyNoGenerics.h @@ -382,6 +382,31 @@ __attribute__((swift_name("CoroutinesKt"))) + (void)invoke1Block:(id)block argument:(id _Nullable)argument completionHandler:(void (^)(id _Nullable_result, NSError * _Nullable))completionHandler __attribute__((swift_name("invoke1(block:argument:completionHandler:)"))); + (id)getKSuspendCallableReference0 __attribute__((swift_name("getKSuspendCallableReference0()"))); + (id)getKSuspendCallableReference1 __attribute__((swift_name("getKSuspendCallableReference1()"))); + +/** + * @note This method converts all Kotlin exceptions to errors. +*/ ++ (id _Nullable)startCoroutineUninterceptedOrReturnFn:(id)fn resultHolder:(KtResultHolder *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("startCoroutineUninterceptedOrReturn(fn:resultHolder:)"))) __attribute__((swift_error(nonnull_error))); + +/** + * @note This method converts all Kotlin exceptions to errors. +*/ ++ (id _Nullable)startCoroutineUninterceptedOrReturnFn:(id)fn receiver:(id _Nullable)receiver resultHolder:(KtResultHolder *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("startCoroutineUninterceptedOrReturn(fn:receiver:resultHolder:)"))) __attribute__((swift_error(nonnull_error))); + +/** + * @note This method converts all Kotlin exceptions to errors. +*/ ++ (id _Nullable)startCoroutineUninterceptedOrReturnFn:(id)fn receiver:(id _Nullable)receiver param:(id _Nullable)param resultHolder:(KtResultHolder *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("startCoroutineUninterceptedOrReturn(fn:receiver:param:resultHolder:)"))) __attribute__((swift_error(nonnull_error))); + +/** + * @note This method converts all Kotlin exceptions to errors. +*/ ++ (BOOL)createCoroutineUninterceptedAndResumeFn:(id)fn resultHolder:(KtResultHolder *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("createCoroutineUninterceptedAndResume(fn:resultHolder:)"))); + +/** + * @note This method converts all Kotlin exceptions to errors. +*/ ++ (BOOL)createCoroutineUninterceptedAndResumeFn:(id)fn receiver:(id _Nullable)receiver resultHolder:(KtResultHolder *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("createCoroutineUninterceptedAndResume(fn:receiver:resultHolder:)"))); + (void)gc __attribute__((swift_name("gc()"))); @end diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/intrinsics/IntrinsicsNative.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/intrinsics/IntrinsicsNative.kt index a9c88b5e08d..ef48e866700 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/intrinsics/IntrinsicsNative.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/intrinsics/IntrinsicsNative.kt @@ -25,7 +25,25 @@ import kotlin.native.internal.* @kotlin.internal.InlineOnly public actual inline fun (suspend () -> T).startCoroutineUninterceptedOrReturn( completion: Continuation -): Any? = (this as Function1, Any?>).invoke(completion) +): Any? { + val function = this as? Function1, Any?> + return if (function != null) + function.invoke(completion) + else + startCoroutineUninterceptedOrReturnFallback(this, completion) +} + +@Suppress("UNCHECKED_CAST") +@PublishedApi +internal fun startCoroutineUninterceptedOrReturnFallback( + function: suspend () -> T, + completion: Continuation +): Any? { + // Unlike `function`, `wrapper` class is generated and lowered entirely by Kotlin compiler, + // so the cast below will succeed. + val wrapper: suspend () -> T = { function() } + return (wrapper as Function1, Any?>).invoke(completion) +} /** * Starts an unintercepted coroutine with receiver type [R] and result type [T] and executes it until its first suspension. @@ -44,7 +62,26 @@ public actual inline fun (suspend () -> T).startCoroutineUninterceptedOrRetu public actual inline fun (suspend R.() -> T).startCoroutineUninterceptedOrReturn( receiver: R, completion: Continuation -): Any? = (this as Function2, Any?>).invoke(receiver, completion) +): Any? { + val function = this as? Function2, Any?> + return if (function != null) + function.invoke(receiver, completion) + else + startCoroutineUninterceptedOrReturnFallback(this, receiver, completion) +} + +@Suppress("UNCHECKED_CAST") +@PublishedApi +internal fun startCoroutineUninterceptedOrReturnFallback( + function: suspend R.() -> T, + receiver: R, + completion: Continuation +): Any? { + // Unlike `function`, `wrapper` class is generated and lowered entirely by Kotlin compiler, + // so the cast below will succeed. + val wrapper: suspend R.() -> T = { this.function() } + return (wrapper as Function2, Any?>).invoke(receiver, completion) +} @Suppress("UNCHECKED_CAST") @kotlin.internal.InlineOnly @@ -52,7 +89,26 @@ internal actual inline fun (suspend R.(P) -> T).startCoroutineUninterc receiver: R, param: P, completion: Continuation -): Any? = (this as Function3, Any?>).invoke(receiver, param, completion) +): Any? { + val function = this as? Function3, Any?> + return if (function != null) + function.invoke(receiver, param, completion) + else + startCoroutineUninterceptedOrReturnFallback(this, receiver, param, completion) +} + +@Suppress("UNCHECKED_CAST") +internal fun startCoroutineUninterceptedOrReturnFallback( + function: suspend R.(P) -> T, + receiver: R, + param: P, + completion: Continuation +): Any? { + // Unlike `function`, `wrapper` class is generated and lowered entirely by Kotlin compiler, + // so the cast below will succeed. + val wrapper: suspend R.(P) -> T = { this.function(it) } + return (wrapper as Function3, Any?>).invoke(receiver, param, completion) +} private object CoroutineSuspendedMarker