Fix suspend function types (i.e. suspend lambdas) in ObjCExport

#KT-40976 Fixed
This commit is contained in:
SvyatoslavScherbina
2020-08-12 16:39:38 +03:00
committed by GitHub
parent 494916b19d
commit 685168780b
6 changed files with 117 additions and 1 deletions
@@ -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
@@ -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)
@@ -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)
}
}
@@ -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<KtKotlinSuspendFunction0>)getSuspendLambda0 __attribute__((swift_name("getSuspendLambda0()")));
+ (id<KtKotlinSuspendFunction0>)getSuspendCallableReference0 __attribute__((swift_name("getSuspendCallableReference0()")));
+ (id<KtKotlinSuspendFunction1>)getSuspendLambda1 __attribute__((swift_name("getSuspendLambda1()")));
+ (id<KtKotlinSuspendFunction1>)getSuspendCallableReference1 __attribute__((swift_name("getSuspendCallableReference1()")));
/**
@note This method converts instances of CancellationException to errors.
Other uncaught Kotlin exceptions are fatal.
*/
+ (void)invoke1Block:(id<KtKotlinSuspendFunction1>)block argument:(id _Nullable)argument completionHandler:(void (^)(id _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("invoke1(block:argument:completionHandler:)")));
@end;
__attribute__((swift_name("FHolder")))
+20
View File
@@ -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;
+2 -1
View File
@@ -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.