Fix suspend function types (i.e. suspend lambdas) in ObjCExport
#KT-40976 Fixed
This commit is contained in:
committed by
GitHub
parent
494916b19d
commit
685168780b
+6
@@ -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")))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user