Add basic support for suspend functions in produced Obj-C header
This commit is contained in:
committed by
SvyatoslavScherbina
parent
4e048b5e25
commit
e79b055c81
+5
@@ -207,6 +207,11 @@ internal class KonanSymbols(
|
||||
NoLookupLocation.FROM_BACKEND
|
||||
).single())
|
||||
|
||||
val objCExportResumeContinuation = internalFunction("resumeContinuation")
|
||||
val objCExportResumeContinuationWithException = internalFunction("resumeContinuationWithException")
|
||||
val objCExportGetCoroutineSuspended = internalFunction("getCoroutineSuspended")
|
||||
val objCExportInterceptedContinuation = internalFunction("interceptedContinuation")
|
||||
|
||||
val getNativeNullPtr = symbolTable.referenceSimpleFunction(context.getNativeNullPtr)
|
||||
|
||||
val boxCachePredicates = BoxCache.values().associate {
|
||||
|
||||
+2
@@ -488,6 +488,8 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
|
||||
val Kotlin_ObjCExport_RethrowExceptionAsNSError by lazyRtFunction
|
||||
val Kotlin_ObjCExport_RethrowNSErrorAsException by lazyRtFunction
|
||||
val Kotlin_ObjCExport_AllocInstanceWithAssociatedObject by lazyRtFunction
|
||||
val Kotlin_ObjCExport_createContinuationArgument by lazyRtFunction
|
||||
val Kotlin_ObjCExport_resumeContinuation by lazyRtFunction
|
||||
|
||||
val tlsMode by lazy {
|
||||
when (target) {
|
||||
|
||||
+93
-8
@@ -86,7 +86,7 @@ internal open class ObjCExportCodeGeneratorBase(codegen: CodeGenerator) : ObjCCo
|
||||
generateBlockToKotlinFunctionConverter(bridge)
|
||||
}
|
||||
|
||||
private val blockGenerator = BlockGenerator(this.codegen)
|
||||
protected val blockGenerator = BlockGenerator(this.codegen)
|
||||
private val functionToBlockConverterCache = mutableMapOf<BlockPointerBridge, LLVMValueRef>()
|
||||
|
||||
internal fun kotlinFunctionToBlockConverter(bridge: BlockPointerBridge): LLVMValueRef =
|
||||
@@ -123,6 +123,10 @@ internal class ObjCExportCodeGenerator(
|
||||
|
||||
val externalGlobalInitializers = mutableMapOf<LLVMValueRef, ConstValue>()
|
||||
|
||||
internal val continuationToCompletionConverter: LLVMValueRef by lazy {
|
||||
generateContinuationToCompletionConverter(blockGenerator)
|
||||
}
|
||||
|
||||
fun FunctionGenerationContext.genSendMessage(
|
||||
returnType: LLVMTypeRef,
|
||||
receiver: LLVMValueRef,
|
||||
@@ -584,6 +588,20 @@ private fun ObjCExportCodeGenerator.emitBoxConverter(
|
||||
setObjCExportTypeInfo(boxClass, constPointer(converter))
|
||||
}
|
||||
|
||||
private fun ObjCExportCodeGenerator.generateContinuationToCompletionConverter(
|
||||
blockGenerator: BlockGenerator
|
||||
): LLVMValueRef = with(blockGenerator) {
|
||||
generateWrapKotlinObjectToBlock(
|
||||
BlockType(numberOfParameters = 2, returnsVoid = true),
|
||||
convertName = "convertContinuation",
|
||||
invokeName = "invokeCompletion"
|
||||
) { continuation, arguments ->
|
||||
check(arguments.size == 2)
|
||||
callFromBridge(context.llvm.Kotlin_ObjCExport_resumeContinuation, listOf(continuation) + arguments)
|
||||
ret(null)
|
||||
}
|
||||
}
|
||||
|
||||
private const val maxConvertorsInCache = 33
|
||||
|
||||
private fun ObjCExportBlockCodeGenerator.emitFunctionConverters() {
|
||||
@@ -760,6 +778,7 @@ private fun ObjCExportCodeGenerator.generateObjCImp(
|
||||
}
|
||||
|
||||
var errorOutPtr: LLVMValueRef? = null
|
||||
var continuation: LLVMValueRef? = null
|
||||
|
||||
val kotlinArgs = methodBridge.paramBridges.mapIndexedNotNull { index, paramBridge ->
|
||||
val parameter = param(index)
|
||||
@@ -777,17 +796,22 @@ private fun ObjCExportCodeGenerator.generateObjCImp(
|
||||
errorOutPtr = parameter
|
||||
null
|
||||
}
|
||||
|
||||
MethodBridgeValueParameter.SuspendCompletion -> {
|
||||
callFromBridge(
|
||||
context.llvm.Kotlin_ObjCExport_createContinuationArgument,
|
||||
listOf(parameter, generateExceptionTypeInfoArray(baseMethod!!)),
|
||||
Lifetime.ARGUMENT
|
||||
).also {
|
||||
continuation = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: consider merging this handler with function cleanup.
|
||||
val exceptionHandler = if (errorOutPtr == null) {
|
||||
kotlinExceptionHandler { exception ->
|
||||
callFromBridge(symbols.objCExportTrapOnUndeclaredException.owner.llvmFunction, listOf(exception))
|
||||
unreachable()
|
||||
}
|
||||
} else {
|
||||
kotlinExceptionHandler { exception ->
|
||||
val exceptionHandler = when {
|
||||
errorOutPtr != null -> kotlinExceptionHandler { exception ->
|
||||
callFromBridge(
|
||||
context.llvm.Kotlin_ObjCExport_RethrowExceptionAsNSError,
|
||||
listOf(exception, errorOutPtr!!, generateExceptionTypeInfoArray(baseMethod!!))
|
||||
@@ -810,6 +834,22 @@ private fun ObjCExportCodeGenerator.generateObjCImp(
|
||||
|
||||
ret(returnValue)
|
||||
}
|
||||
|
||||
continuation != null -> kotlinExceptionHandler { exception ->
|
||||
// Callee haven't suspended, so it isn't going to call the completion. Call it here:
|
||||
callFromBridge(
|
||||
context.ir.symbols.objCExportResumeContinuationWithException.owner.llvmFunction,
|
||||
listOf(continuation!!, exception)
|
||||
)
|
||||
// Note: completion block could be called directly instead, but this implementation is
|
||||
// simpler and avoids duplication.
|
||||
ret(null)
|
||||
}
|
||||
|
||||
else -> kotlinExceptionHandler { exception ->
|
||||
callFromBridge(symbols.objCExportTrapOnUndeclaredException.owner.llvmFunction, listOf(exception))
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
val targetResult = callKotlin(kotlinArgs, Lifetime.ARGUMENT, exceptionHandler)
|
||||
@@ -825,6 +865,23 @@ private fun ObjCExportCodeGenerator.generateObjCImp(
|
||||
is MethodBridge.ReturnValue.WithError.ZeroForError -> genReturnValueOnSuccess(returnBridge.successBridge)
|
||||
MethodBridge.ReturnValue.Instance.InitResult -> param(0)
|
||||
MethodBridge.ReturnValue.Instance.FactoryResult -> kotlinReferenceToObjC(targetResult!!) // provided by [callKotlin]
|
||||
MethodBridge.ReturnValue.Suspend -> {
|
||||
val coroutineSuspended = callFromBridge(
|
||||
codegen.llvmFunction(context.ir.symbols.objCExportGetCoroutineSuspended.owner),
|
||||
emptyList(),
|
||||
Lifetime.LOCAL
|
||||
)
|
||||
ifThen(icmpNe(targetResult!!, coroutineSuspended)) {
|
||||
// Callee haven't suspended, so it isn't going to call the completion. Call it here:
|
||||
callFromBridge(
|
||||
context.ir.symbols.objCExportResumeContinuation.owner.llvmFunction,
|
||||
listOf(continuation!!, targetResult)
|
||||
)
|
||||
// Note: completion block could be called directly instead, but this implementation is
|
||||
// simpler and avoids duplication.
|
||||
}
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
ret(genReturnValueOnSuccess(returnType))
|
||||
@@ -928,6 +985,17 @@ private fun ObjCExportCodeGenerator.generateKotlinToObjCBridge(
|
||||
store(kNullInt8Ptr, it)
|
||||
errorOutPtr = it
|
||||
}
|
||||
|
||||
MethodBridgeValueParameter.SuspendCompletion -> {
|
||||
val continuation = param(irFunction.allParameters.size) // The last argument.
|
||||
// TODO: consider placing interception into the converter to reduce code size.
|
||||
val intercepted = callFromBridge(
|
||||
context.ir.symbols.objCExportInterceptedContinuation.owner.llvmFunction,
|
||||
listOf(continuation),
|
||||
Lifetime.ARGUMENT
|
||||
)
|
||||
callFromBridge(continuationToCompletionConverter, listOf(intercepted))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -985,12 +1053,25 @@ private fun ObjCExportCodeGenerator.generateKotlinToObjCBridge(
|
||||
MethodBridge.ReturnValue.Instance.InitResult,
|
||||
MethodBridge.ReturnValue.Instance.FactoryResult ->
|
||||
error("init or factory method can't have bridge for overriding: $baseMethod")
|
||||
|
||||
MethodBridge.ReturnValue.Suspend -> {
|
||||
// Objective-C implementation of Kotlin suspend function is always responsible
|
||||
// for calling the completion, so in Kotlin coroutines machinery terms it suspends,
|
||||
// which is indicated by the return value:
|
||||
callFromBridge(
|
||||
context.ir.symbols.objCExportGetCoroutineSuspended.owner.llvmFunction,
|
||||
emptyList(),
|
||||
Lifetime.RETURN_VALUE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val baseReturnType = baseIrFunction.returnType
|
||||
val actualReturnType = irFunction.returnType
|
||||
|
||||
val retVal = when {
|
||||
baseIrFunction.isSuspend -> genKotlinBaseMethodResult(Lifetime.RETURN_VALUE, methodBridge.returnBridge)
|
||||
|
||||
actualReturnType.isUnit() || actualReturnType.isNothing() -> {
|
||||
genKotlinBaseMethodResult(Lifetime.ARGUMENT, methodBridge.returnBridge)
|
||||
null
|
||||
@@ -1433,10 +1514,12 @@ private val MethodBridgeParameter.objCType: LLVMTypeRef get() = when (this) {
|
||||
is MethodBridgeReceiver -> ReferenceBridge.objCType
|
||||
MethodBridgeSelector -> int8TypePtr
|
||||
MethodBridgeValueParameter.ErrorOutParameter -> pointerType(ReferenceBridge.objCType)
|
||||
MethodBridgeValueParameter.SuspendCompletion -> int8TypePtr
|
||||
}
|
||||
|
||||
private fun MethodBridge.ReturnValue.objCType(context: Context): LLVMTypeRef {
|
||||
return when (this) {
|
||||
MethodBridge.ReturnValue.Suspend,
|
||||
MethodBridge.ReturnValue.Void -> voidType
|
||||
MethodBridge.ReturnValue.HashCode -> if (context.is64BitNSInteger()) int64Type else int32Type
|
||||
is MethodBridge.ReturnValue.Mapped -> this.bridge.objCType
|
||||
@@ -1483,6 +1566,7 @@ private val Family.nsUIntegerEncoding: String get() = when (this) {
|
||||
}
|
||||
|
||||
private fun MethodBridge.ReturnValue.getObjCEncoding(targetFamily: Family): String = when (this) {
|
||||
MethodBridge.ReturnValue.Suspend,
|
||||
MethodBridge.ReturnValue.Void -> "v"
|
||||
MethodBridge.ReturnValue.HashCode -> targetFamily.nsUIntegerEncoding
|
||||
is MethodBridge.ReturnValue.Mapped -> this.bridge.objCEncoding
|
||||
@@ -1498,6 +1582,7 @@ private val MethodBridgeParameter.objCEncoding: String get() = when (this) {
|
||||
is MethodBridgeReceiver -> ReferenceBridge.objCEncoding
|
||||
MethodBridgeSelector -> ":"
|
||||
MethodBridgeValueParameter.ErrorOutParameter -> "^${ReferenceBridge.objCEncoding}"
|
||||
MethodBridgeValueParameter.SuspendCompletion -> "@"
|
||||
}
|
||||
|
||||
private val TypeBridge.objCEncoding: String get() = when (this) {
|
||||
|
||||
+5
@@ -35,6 +35,7 @@ internal object MethodBridgeSelector : MethodBridgeParameter()
|
||||
internal sealed class MethodBridgeValueParameter : MethodBridgeParameter() {
|
||||
data class Mapped(val bridge: TypeBridge) : MethodBridgeValueParameter()
|
||||
object ErrorOutParameter : MethodBridgeValueParameter()
|
||||
object SuspendCompletion : MethodBridgeValueParameter()
|
||||
}
|
||||
|
||||
internal data class MethodBridge(
|
||||
@@ -56,6 +57,8 @@ internal data class MethodBridge(
|
||||
object Success : WithError()
|
||||
data class ZeroForError(val successBridge: ReturnValue, val successMayBeZero: Boolean) : WithError()
|
||||
}
|
||||
|
||||
object Suspend : ReturnValue()
|
||||
}
|
||||
|
||||
val paramBridges: List<MethodBridgeParameter> =
|
||||
@@ -86,6 +89,7 @@ internal fun MethodBridge.valueParametersAssociated(
|
||||
when (it) {
|
||||
is MethodBridgeValueParameter.Mapped -> it to kotlinParameters.next()
|
||||
|
||||
MethodBridgeValueParameter.SuspendCompletion,
|
||||
is MethodBridgeValueParameter.ErrorOutParameter -> it to null
|
||||
}
|
||||
}.also { assert(!kotlinParameters.hasNext()) }
|
||||
@@ -101,6 +105,7 @@ internal fun MethodBridge.parametersAssociated(
|
||||
is MethodBridgeValueParameter.Mapped, MethodBridgeReceiver.Instance ->
|
||||
it to kotlinParameters.next()
|
||||
|
||||
MethodBridgeValueParameter.SuspendCompletion,
|
||||
MethodBridgeReceiver.Static, MethodBridgeSelector, MethodBridgeValueParameter.ErrorOutParameter ->
|
||||
it to null
|
||||
|
||||
|
||||
+12
@@ -581,6 +581,7 @@ internal class ObjCExportTranslatorImpl(
|
||||
}
|
||||
}
|
||||
MethodBridgeValueParameter.ErrorOutParameter -> "error"
|
||||
MethodBridgeValueParameter.SuspendCompletion -> "completionHandler"
|
||||
}
|
||||
|
||||
val uniqueName = unifyName(candidateName, usedNames)
|
||||
@@ -590,6 +591,16 @@ internal class ObjCExportTranslatorImpl(
|
||||
is MethodBridgeValueParameter.Mapped -> mapType(p!!.type, bridge.bridge, objCExportScope)
|
||||
MethodBridgeValueParameter.ErrorOutParameter ->
|
||||
ObjCPointerType(ObjCNullableReferenceType(ObjCClassType("NSError")), nullable = true)
|
||||
|
||||
MethodBridgeValueParameter.SuspendCompletion -> {
|
||||
ObjCBlockPointerType(
|
||||
returnType = ObjCVoidType,
|
||||
parameterTypes = listOf(
|
||||
mapReferenceType(method.returnType!!, objCExportScope).makeNullable(),
|
||||
ObjCNullableReferenceType(ObjCClassType("NSError"))
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
parameters += ObjCParameter(uniqueName, p, type)
|
||||
@@ -652,6 +663,7 @@ internal class ObjCExportTranslatorImpl(
|
||||
}
|
||||
|
||||
private fun mapReturnType(returnBridge: MethodBridge.ReturnValue, method: FunctionDescriptor, objCExportScope: ObjCExportScope): ObjCType = when (returnBridge) {
|
||||
MethodBridge.ReturnValue.Suspend,
|
||||
MethodBridge.ReturnValue.Void -> ObjCVoidType
|
||||
MethodBridge.ReturnValue.HashCode -> ObjCPrimitiveType.NSUInteger
|
||||
is MethodBridge.ReturnValue.Mapped -> mapType(method.returnType!!, returnBridge.bridge, objCExportScope)
|
||||
|
||||
+1
-4
@@ -160,7 +160,7 @@ internal class ObjCExportLazyImpl(
|
||||
|
||||
file.children.filterIsInstance<KtCallableDeclaration>().forEach {
|
||||
// Supposed to be similar to ObjCExportMapper.shouldBeVisible.
|
||||
if ((it is KtFunction || it is KtProperty) && it.isPublic && !it.isSuspend && !it.hasExpectModifier()) {
|
||||
if ((it is KtFunction || it is KtProperty) && it.isPublic && !it.hasExpectModifier()) {
|
||||
val classDescriptor = getClassIfExtension(it)
|
||||
if (classDescriptor != null) {
|
||||
extensions.getOrPut(classDescriptor, { mutableListOf() }) += it
|
||||
@@ -438,9 +438,6 @@ private fun ModuleDescriptor.isCommonStdlib() =
|
||||
private val KtModifierListOwner.isPublic: Boolean
|
||||
get() = this.visibilityModifierTypeOrDefault() == KtTokens.PUBLIC_KEYWORD
|
||||
|
||||
private val KtCallableDeclaration.isSuspend: Boolean
|
||||
get() = this.hasModifier(KtTokens.SUSPEND_KEYWORD)
|
||||
|
||||
internal val KtPureClassOrObject.isInterface: Boolean
|
||||
get() = this is KtClass && this.isInterface()
|
||||
|
||||
|
||||
+8
-2
@@ -72,7 +72,7 @@ internal fun ObjCExportMapper.getClassIfCategory(extensionReceiverType: KotlinTy
|
||||
|
||||
// Note: partially duplicated in ObjCExportLazyImpl.translateTopLevels.
|
||||
internal fun ObjCExportMapper.shouldBeExposed(descriptor: CallableMemberDescriptor): Boolean =
|
||||
descriptor.isEffectivelyPublicApi && !descriptor.isSuspend && !descriptor.isExpect &&
|
||||
descriptor.isEffectivelyPublicApi && !descriptor.isExpect &&
|
||||
!isHiddenByDeprecation(descriptor)
|
||||
|
||||
internal fun ObjCExportMapper.shouldBeExposed(descriptor: ClassDescriptor): Boolean =
|
||||
@@ -241,6 +241,8 @@ private fun ObjCExportMapper.bridgeReturnType(
|
||||
): MethodBridge.ReturnValue {
|
||||
val returnType = descriptor.returnType!!
|
||||
return when {
|
||||
descriptor.isSuspend -> MethodBridge.ReturnValue.Suspend
|
||||
|
||||
descriptor is ConstructorDescriptor -> if (descriptor.constructedClass.isArray) {
|
||||
MethodBridge.ReturnValue.Instance.FactoryResult
|
||||
} else {
|
||||
@@ -315,7 +317,10 @@ private fun ObjCExportMapper.bridgeMethodImpl(descriptor: FunctionDescriptor): M
|
||||
}
|
||||
|
||||
val returnBridge = bridgeReturnType(descriptor, convertExceptionsToErrors)
|
||||
if (convertExceptionsToErrors) {
|
||||
|
||||
if (descriptor.isSuspend) {
|
||||
valueParameters += MethodBridgeValueParameter.SuspendCompletion
|
||||
} else if (convertExceptionsToErrors) {
|
||||
// Add error out parameter before tail block parameters. The convention allows this.
|
||||
// Placing it after would trigger https://bugs.swift.org/browse/SR-12201
|
||||
// (see also https://github.com/JetBrains/kotlin-native/issues/3825).
|
||||
@@ -332,6 +337,7 @@ private fun MethodBridgeValueParameter.isBlockPointer(): Boolean = when (this) {
|
||||
is BlockPointerBridge -> true
|
||||
}
|
||||
MethodBridgeValueParameter.ErrorOutParameter -> false
|
||||
MethodBridgeValueParameter.SuspendCompletion -> true
|
||||
}
|
||||
|
||||
internal fun ObjCExportMapper.bridgePropertyType(descriptor: PropertyDescriptor): TypeBridge {
|
||||
|
||||
+5
@@ -453,13 +453,17 @@ internal class ObjCExportNamerImpl(
|
||||
else -> it!!.name.asString().toIdentifier()
|
||||
}
|
||||
MethodBridgeValueParameter.ErrorOutParameter -> "error"
|
||||
MethodBridgeValueParameter.SuspendCompletion -> "completionHandler"
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
append(when {
|
||||
bridge is MethodBridgeValueParameter.ErrorOutParameter -> "AndReturn"
|
||||
|
||||
bridge is MethodBridgeValueParameter.SuspendCompletion -> "With"
|
||||
|
||||
method is ConstructorDescriptor -> "With"
|
||||
|
||||
else -> ""
|
||||
})
|
||||
append(name.capitalize())
|
||||
@@ -502,6 +506,7 @@ internal class ObjCExportNamerImpl(
|
||||
else -> it!!.name.asString().toIdentifier()
|
||||
}
|
||||
MethodBridgeValueParameter.ErrorOutParameter -> continue@parameters
|
||||
MethodBridgeValueParameter.SuspendCompletion -> "completionHandler"
|
||||
}
|
||||
|
||||
append(label)
|
||||
|
||||
+5
@@ -163,3 +163,8 @@ internal fun ObjCType.makeNullableIfReferenceOrPointer(): ObjCType = when (this)
|
||||
|
||||
is ObjCNullableReferenceType, is ObjCRawType, is ObjCPrimitiveType, ObjCVoidType -> this
|
||||
}
|
||||
|
||||
internal fun ObjCReferenceType.makeNullable(): ObjCNullableReferenceType = when (this) {
|
||||
is ObjCNonNullReferenceType -> ObjCNullableReferenceType(this)
|
||||
is ObjCNullableReferenceType -> this
|
||||
}
|
||||
|
||||
@@ -37,6 +37,15 @@ func assertEquals<T: Equatable>(actual: T, expected: T,
|
||||
}
|
||||
}
|
||||
|
||||
func assertSame(actual: AnyObject?, expected: AnyObject?,
|
||||
_ message: String = "Assertion failed:",
|
||||
file: String = #file, line: Int = #line) throws {
|
||||
if (actual !== expected) {
|
||||
try throwAssertFailed(message: message + " Expected value: \(expected), but got: \(actual)",
|
||||
file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
func assertEquals<T: Equatable>(actual: [T], expected: [T],
|
||||
_ message: String = "Assertion failed: arrays not equal",
|
||||
file: String = #file, line: Int = #line) throws {
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package coroutines
|
||||
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.coroutines.intrinsics.*
|
||||
import kotlin.native.concurrent.isFrozen
|
||||
import kotlin.native.internal.ObjCErrorException
|
||||
import kotlin.test.*
|
||||
|
||||
class CoroutineException : Throwable()
|
||||
|
||||
suspend fun suspendFun() = 42
|
||||
|
||||
@Throws(CoroutineException::class)
|
||||
suspend fun suspendFun(result: Any?, doSuspend: Boolean, doThrow: Boolean): Any? {
|
||||
if (doSuspend) {
|
||||
suspendCoroutineUninterceptedOrReturn<Unit> {
|
||||
it.resume(Unit)
|
||||
COROUTINE_SUSPENDED
|
||||
}
|
||||
}
|
||||
|
||||
if (doThrow) throw CoroutineException()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
class ContinuationHolder<T> {
|
||||
internal lateinit var continuation: Continuation<T>
|
||||
|
||||
fun resume(value: T) {
|
||||
continuation.resume(value)
|
||||
}
|
||||
|
||||
fun resumeWithException(exception: Throwable) {
|
||||
continuation.resumeWithException(exception)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(CoroutineException::class)
|
||||
suspend fun suspendFunAsync(result: Any?, continuationHolder: ContinuationHolder<Any?>): Any? =
|
||||
suspendCoroutineUninterceptedOrReturn<Any?> {
|
||||
continuationHolder.continuation = it
|
||||
COROUTINE_SUSPENDED
|
||||
} ?: result
|
||||
|
||||
@Throws(CoroutineException::class)
|
||||
fun throwException(exception: Throwable) {
|
||||
throw exception
|
||||
}
|
||||
|
||||
interface SuspendFun {
|
||||
@Throws(CoroutineException::class)
|
||||
suspend fun suspendFun(doYield: Boolean, doThrow: Boolean): Int
|
||||
}
|
||||
|
||||
class ResultHolder<T> {
|
||||
var completed: Int = 0
|
||||
var result: T? = null
|
||||
var exception: Throwable? = null
|
||||
|
||||
internal fun complete(result: Result<T>) {
|
||||
this.result = result.getOrNull()
|
||||
this.exception = result.exceptionOrNull()
|
||||
this.completed += 1
|
||||
}
|
||||
}
|
||||
|
||||
private class ResultHolderCompletion<T>(val resultHolder: ResultHolder<T>) : Continuation<T> {
|
||||
override val context: CoroutineContext
|
||||
get() = EmptyCoroutineContext
|
||||
|
||||
override fun resumeWith(result: Result<T>) {
|
||||
resultHolder.complete(result)
|
||||
}
|
||||
}
|
||||
|
||||
fun callSuspendFun(suspendFun: SuspendFun, doYield: Boolean, doThrow: Boolean, resultHolder: ResultHolder<Int>) {
|
||||
suspend { suspendFun.suspendFun(doYield = doYield, doThrow = doThrow) }
|
||||
.startCoroutine(ResultHolderCompletion(resultHolder))
|
||||
}
|
||||
|
||||
@Throws
|
||||
suspend fun callSuspendFun2(suspendFun: SuspendFun, doYield: Boolean, doThrow: Boolean): Int {
|
||||
return suspendFun.suspendFun(doYield = doYield, doThrow = doThrow)
|
||||
}
|
||||
|
||||
interface SuspendBridge<T> {
|
||||
suspend fun int(value: T): Int
|
||||
suspend fun intAsAny(value: T): Any?
|
||||
|
||||
suspend fun unit(value: T): Unit
|
||||
suspend fun unitAsAny(value: T): Any?
|
||||
|
||||
@Throws(CoroutineException::class) suspend fun nothing(value: T): Nothing
|
||||
@Throws(CoroutineException::class) suspend fun nothingAsInt(value: T): Int
|
||||
@Throws(CoroutineException::class) suspend fun nothingAsAny(value: T): Any?
|
||||
@Throws(CoroutineException::class) suspend fun nothingAsUnit(value: T): Unit
|
||||
}
|
||||
|
||||
abstract class AbstractSuspendBridge : SuspendBridge<Int> {
|
||||
override suspend fun intAsAny(value: Int): Int = TODO()
|
||||
|
||||
override suspend fun unitAsAny(value: Int): Unit = TODO()
|
||||
|
||||
override suspend fun nothingAsInt(value: Int): Nothing = TODO()
|
||||
override suspend fun nothingAsAny(value: Int): Nothing = TODO()
|
||||
override suspend fun nothingAsUnit(value: Int): Nothing = TODO()
|
||||
}
|
||||
|
||||
private suspend fun callSuspendBridgeImpl(bridge: SuspendBridge<Int>) {
|
||||
assertEquals(1, bridge.intAsAny(1))
|
||||
|
||||
assertSame(Unit, bridge.unitAsAny(2))
|
||||
|
||||
assertFailsWith<ObjCErrorException> { bridge.nothingAsInt(3) }
|
||||
assertFailsWith<ObjCErrorException> { bridge.nothingAsAny(4) }
|
||||
assertFailsWith<ObjCErrorException> { bridge.nothingAsUnit(5) }
|
||||
}
|
||||
|
||||
private suspend fun callAbstractSuspendBridgeImpl(bridge: AbstractSuspendBridge) {
|
||||
assertEquals(6, bridge.intAsAny(6))
|
||||
|
||||
assertSame(Unit, bridge.unitAsAny(7))
|
||||
|
||||
assertFailsWith<ObjCErrorException> { bridge.nothingAsInt(8) }
|
||||
assertFailsWith<ObjCErrorException> { bridge.nothingAsAny(9) }
|
||||
assertFailsWith<ObjCErrorException> { bridge.nothingAsUnit(10) }
|
||||
}
|
||||
|
||||
@Throws
|
||||
fun callSuspendBridge(bridge: AbstractSuspendBridge, resultHolder: ResultHolder<Unit>) {
|
||||
suspend {
|
||||
callSuspendBridgeImpl(bridge)
|
||||
callAbstractSuspendBridgeImpl(bridge)
|
||||
}.startCoroutine(ResultHolderCompletion(resultHolder))
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
import Kt
|
||||
|
||||
private func testCallSimple() throws {
|
||||
var result: KotlinInt? = nil
|
||||
var error: Error? = nil
|
||||
var completionCalled = 0
|
||||
|
||||
CoroutinesKt.suspendFun { _result, _error in
|
||||
completionCalled += 1
|
||||
result = _result
|
||||
error = _error
|
||||
}
|
||||
|
||||
try assertEquals(actual: completionCalled, expected: 1)
|
||||
try assertEquals(actual: result, expected: 42)
|
||||
try assertNil(error)
|
||||
}
|
||||
|
||||
private func testCallSuspendFun(doSuspend: Bool, doThrow: Bool) throws {
|
||||
class C {}
|
||||
let expectedResult = C()
|
||||
|
||||
var completionCalled = 0
|
||||
var result: AnyObject? = nil
|
||||
var error: Error? = nil
|
||||
|
||||
CoroutinesKt.suspendFun(result: expectedResult, doSuspend: doSuspend, doThrow: doThrow) { _result, _error in
|
||||
completionCalled += 1
|
||||
result = _result as AnyObject?
|
||||
error = _error
|
||||
}
|
||||
|
||||
try assertEquals(actual: completionCalled, expected: 1)
|
||||
|
||||
if doThrow {
|
||||
try assertNil(result)
|
||||
try assertTrue(error?.kotlinException is CoroutineException)
|
||||
} else {
|
||||
try assertSame(actual: result, expected: expectedResult)
|
||||
try assertNil(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func testSuspendFuncAsync(doThrow: Bool) throws {
|
||||
var completionCalled = 0
|
||||
var result: AnyObject? = nil
|
||||
var error: Error? = nil
|
||||
|
||||
let continuationHolder = ContinuationHolder<AnyObject>()
|
||||
|
||||
CoroutinesKt.suspendFunAsync(result: nil, continuationHolder: continuationHolder) { _result, _error in
|
||||
completionCalled += 1
|
||||
result = _result as AnyObject?
|
||||
error = _error
|
||||
}
|
||||
|
||||
try assertEquals(actual: completionCalled, expected: 0)
|
||||
|
||||
if doThrow {
|
||||
let exception = CoroutineException()
|
||||
continuationHolder.resumeWithException(exception: exception)
|
||||
|
||||
try assertEquals(actual: completionCalled, expected: 1)
|
||||
|
||||
try assertNil(result)
|
||||
try assertSame(actual: error?.kotlinException as AnyObject?, expected: exception)
|
||||
} else {
|
||||
class C {}
|
||||
let expectedResult = C()
|
||||
continuationHolder.resume(value: expectedResult)
|
||||
|
||||
try assertEquals(actual: completionCalled, expected: 1)
|
||||
|
||||
try assertSame(actual: result, expected: expectedResult)
|
||||
try assertNil(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func testCall() throws {
|
||||
try testCallSuspendFun(doSuspend: true, doThrow: false)
|
||||
try testCallSuspendFun(doSuspend: false, doThrow: false)
|
||||
try testCallSuspendFun(doSuspend: true, doThrow: true)
|
||||
try testCallSuspendFun(doSuspend: false, doThrow: true)
|
||||
|
||||
try testSuspendFuncAsync(doThrow: false)
|
||||
try testSuspendFuncAsync(doThrow: true)
|
||||
}
|
||||
|
||||
private class SuspendFunImpl : SuspendFun {
|
||||
class E : Error {}
|
||||
|
||||
var completion: (() -> Void)? = nil
|
||||
|
||||
func suspendFun(doYield: Bool, doThrow: Bool, completionHandler: @escaping (KotlinInt?, Error?) -> Void) {
|
||||
func callCompletion() {
|
||||
if doThrow {
|
||||
completionHandler(nil, E())
|
||||
} else {
|
||||
completionHandler(17, nil)
|
||||
}
|
||||
}
|
||||
|
||||
if doYield {
|
||||
self.completion = callCompletion
|
||||
} else {
|
||||
callCompletion()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func testSuspendFunImpl(doYield: Bool, doThrow: Bool) throws {
|
||||
let resultHolder = ResultHolder<KotlinInt>()
|
||||
|
||||
let impl = SuspendFunImpl()
|
||||
|
||||
CoroutinesKt.callSuspendFun(
|
||||
suspendFun: impl,
|
||||
doYield: doYield,
|
||||
doThrow: doThrow,
|
||||
resultHolder: resultHolder
|
||||
)
|
||||
|
||||
if doYield {
|
||||
try assertEquals(actual: resultHolder.completed, expected: 0)
|
||||
guard let completion = impl.completion else { try fail() }
|
||||
completion()
|
||||
}
|
||||
|
||||
try assertEquals(actual: resultHolder.completed, expected: 1)
|
||||
|
||||
if doThrow {
|
||||
try assertNil(resultHolder.result)
|
||||
if let e = resultHolder.exception {
|
||||
try assertFailsWith(SuspendFunImpl.E.self) { try CoroutinesKt.throwException(exception: e) }
|
||||
} else {
|
||||
try fail()
|
||||
}
|
||||
} else {
|
||||
try assertEquals(actual: resultHolder.result, expected: 17)
|
||||
try assertNil(resultHolder.exception)
|
||||
}
|
||||
}
|
||||
|
||||
private func testSuspendFunImpl2(doYield: Bool, doThrow: Bool) throws {
|
||||
let impl = SuspendFunImpl()
|
||||
|
||||
var completionCalled = 0
|
||||
var result: KotlinInt? = nil
|
||||
var error: Error? = nil
|
||||
|
||||
CoroutinesKt.callSuspendFun2(suspendFun: impl, doYield: doYield, doThrow: doThrow) { _result, _error in
|
||||
completionCalled += 1
|
||||
result = _result
|
||||
error = _error
|
||||
}
|
||||
|
||||
if doYield {
|
||||
try assertEquals(actual: completionCalled, expected: 0)
|
||||
guard let completion = impl.completion else { try fail() }
|
||||
completion()
|
||||
}
|
||||
|
||||
try assertEquals(actual: completionCalled, expected: 1)
|
||||
|
||||
if doThrow {
|
||||
try assertNil(result)
|
||||
try assertTrue(error is SuspendFunImpl.E)
|
||||
} else {
|
||||
try assertEquals(actual: result, expected: 17)
|
||||
try assertNil(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func testOverride() throws {
|
||||
try testSuspendFunImpl(doYield: false, doThrow: false)
|
||||
try testSuspendFunImpl(doYield: false, doThrow: true)
|
||||
try testSuspendFunImpl(doYield: true, doThrow: false)
|
||||
try testSuspendFunImpl(doYield: true, doThrow: true)
|
||||
|
||||
try testSuspendFunImpl2(doYield: false, doThrow: false)
|
||||
try testSuspendFunImpl2(doYield: false, doThrow: true)
|
||||
try testSuspendFunImpl2(doYield: true, doThrow: false)
|
||||
try testSuspendFunImpl2(doYield: true, doThrow: true)
|
||||
}
|
||||
|
||||
private class SwiftSuspendBridge : AbstractSuspendBridge {
|
||||
class E : Error {}
|
||||
|
||||
override func intAsAny(value: KotlinInt, completionHandler: @escaping (KotlinInt?, Error?) -> Void) {
|
||||
completionHandler(value, nil)
|
||||
}
|
||||
|
||||
override func unitAsAny(value: KotlinInt, completionHandler: @escaping (KotlinUnit?, Error?) -> Void) {
|
||||
completionHandler(KotlinUnit(), nil)
|
||||
}
|
||||
|
||||
override func nothingAsInt(value: KotlinInt, completionHandler: @escaping (KotlinNothing?, Error?) -> Void) {
|
||||
completionHandler(nil, E())
|
||||
}
|
||||
|
||||
override func nothingAsAny(value: KotlinInt, completionHandler: @escaping (KotlinNothing?, Error?) -> Void) {
|
||||
completionHandler(nil, E())
|
||||
}
|
||||
|
||||
override func nothingAsUnit(value: KotlinInt, completionHandler: @escaping (KotlinNothing?, Error?) -> Void) {
|
||||
completionHandler(nil, E())
|
||||
}
|
||||
}
|
||||
|
||||
private func testBridges() throws {
|
||||
let resultHolder = ResultHolder<KotlinUnit>()
|
||||
try CoroutinesKt.callSuspendBridge(bridge: SwiftSuspendBridge(), resultHolder: resultHolder)
|
||||
|
||||
try assertEquals(actual: resultHolder.completed, expected: 1)
|
||||
try assertNil(resultHolder.exception)
|
||||
try assertSame(actual: resultHolder.result, expected: KotlinUnit())
|
||||
}
|
||||
|
||||
class CoroutinesTests : SimpleTestProvider {
|
||||
override init() {
|
||||
super.init()
|
||||
|
||||
test("TestCallSimple", testCallSimple)
|
||||
test("TestCall", testCall)
|
||||
test("TestOverride", testOverride)
|
||||
test("TestBridges", testBridges)
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,77 @@ __attribute__((swift_name("KotlinBoolean")))
|
||||
+ (instancetype)numberWithBool:(BOOL)value;
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("CoroutineException")))
|
||||
@interface KtCoroutineException : KtKotlinThrowable
|
||||
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
||||
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
||||
- (instancetype)initWithMessage:(NSString * _Nullable)message __attribute__((swift_name("init(message:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable));
|
||||
- (instancetype)initWithCause:(KtKotlinThrowable * _Nullable)cause __attribute__((swift_name("init(cause:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable));
|
||||
- (instancetype)initWithMessage:(NSString * _Nullable)message cause:(KtKotlinThrowable * _Nullable)cause __attribute__((swift_name("init(message:cause:)"))) __attribute__((objc_designated_initializer)) __attribute__((unavailable));
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("ContinuationHolder")))
|
||||
@interface KtContinuationHolder<T> : KtBase
|
||||
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
||||
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
||||
- (void)resumeValue:(T _Nullable)value __attribute__((swift_name("resume(value:)")));
|
||||
- (void)resumeWithExceptionException:(KtKotlinThrowable *)exception __attribute__((swift_name("resumeWithException(exception:)")));
|
||||
@end;
|
||||
|
||||
__attribute__((swift_name("SuspendFun")))
|
||||
@protocol KtSuspendFun
|
||||
@required
|
||||
- (void)suspendFunDoYield:(BOOL)doYield doThrow:(BOOL)doThrow completionHandler:(void (^)(KtInt * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("suspendFun(doYield:doThrow:completionHandler:)")));
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("ResultHolder")))
|
||||
@interface KtResultHolder<T> : KtBase
|
||||
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
||||
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
||||
@property int32_t completed __attribute__((swift_name("completed")));
|
||||
@property T _Nullable result __attribute__((swift_name("result")));
|
||||
@property KtKotlinThrowable * _Nullable exception __attribute__((swift_name("exception")));
|
||||
@end;
|
||||
|
||||
__attribute__((swift_name("SuspendBridge")))
|
||||
@protocol KtSuspendBridge
|
||||
@required
|
||||
- (void)intValue:(id _Nullable)value completionHandler:(void (^)(KtInt * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("int(value:completionHandler:)")));
|
||||
- (void)intAsAnyValue:(id _Nullable)value completionHandler:(void (^)(id _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("intAsAny(value:completionHandler:)")));
|
||||
- (void)unitValue:(id _Nullable)value completionHandler:(void (^)(KtKotlinUnit * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("unit(value:completionHandler:)")));
|
||||
- (void)unitAsAnyValue:(id _Nullable)value completionHandler:(void (^)(id _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("unitAsAny(value:completionHandler:)")));
|
||||
- (void)nothingValue:(id _Nullable)value completionHandler:(void (^)(KtKotlinNothing * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("nothing(value:completionHandler:)")));
|
||||
- (void)nothingAsIntValue:(id _Nullable)value completionHandler:(void (^)(KtInt * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("nothingAsInt(value:completionHandler:)")));
|
||||
- (void)nothingAsAnyValue:(id _Nullable)value completionHandler:(void (^)(id _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("nothingAsAny(value:completionHandler:)")));
|
||||
- (void)nothingAsUnitValue:(id _Nullable)value completionHandler:(void (^)(KtKotlinUnit * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("nothingAsUnit(value:completionHandler:)")));
|
||||
@end;
|
||||
|
||||
__attribute__((swift_name("AbstractSuspendBridge")))
|
||||
@interface KtAbstractSuspendBridge : KtBase <KtSuspendBridge>
|
||||
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
||||
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
||||
- (void)intAsAnyValue:(KtInt *)value completionHandler:(void (^)(KtInt * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("intAsAny(value:completionHandler:)")));
|
||||
- (void)unitAsAnyValue:(KtInt *)value completionHandler:(void (^)(KtKotlinUnit * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("unitAsAny(value:completionHandler:)")));
|
||||
- (void)nothingAsIntValue:(KtInt *)value completionHandler:(void (^)(KtKotlinNothing * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("nothingAsInt(value:completionHandler:)")));
|
||||
- (void)nothingAsAnyValue:(KtInt *)value completionHandler:(void (^)(KtKotlinNothing * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("nothingAsAny(value:completionHandler:)")));
|
||||
- (void)nothingAsUnitValue:(KtInt *)value completionHandler:(void (^)(KtKotlinNothing * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("nothingAsUnit(value:completionHandler:)")));
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("CoroutinesKt")))
|
||||
@interface KtCoroutinesKt : KtBase
|
||||
+ (void)suspendFunWithCompletionHandler:(void (^)(KtInt * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("suspendFun(completionHandler:)")));
|
||||
+ (void)suspendFunResult:(id _Nullable)result doSuspend:(BOOL)doSuspend doThrow:(BOOL)doThrow completionHandler:(void (^)(id _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("suspendFun(result:doSuspend:doThrow:completionHandler:)")));
|
||||
+ (void)suspendFunAsyncResult:(id _Nullable)result continuationHolder:(KtContinuationHolder<id> *)continuationHolder completionHandler:(void (^)(id _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("suspendFunAsync(result:continuationHolder:completionHandler:)")));
|
||||
+ (BOOL)throwExceptionException:(KtKotlinThrowable *)exception error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("throwException(exception:)")));
|
||||
+ (void)callSuspendFunSuspendFun:(id<KtSuspendFun>)suspendFun doYield:(BOOL)doYield doThrow:(BOOL)doThrow resultHolder:(KtResultHolder<KtInt *> *)resultHolder __attribute__((swift_name("callSuspendFun(suspendFun:doYield:doThrow:resultHolder:)")));
|
||||
+ (void)callSuspendFun2SuspendFun:(id<KtSuspendFun>)suspendFun doYield:(BOOL)doYield doThrow:(BOOL)doThrow completionHandler:(void (^)(KtInt * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("callSuspendFun2(suspendFun:doYield:doThrow:completionHandler:)")));
|
||||
+ (BOOL)callSuspendBridgeBridge:(KtAbstractSuspendBridge *)bridge resultHolder:(KtResultHolder<KtKotlinUnit *> *)resultHolder error:(NSError * _Nullable * _Nullable)error __attribute__((swift_name("callSuspendBridge(bridge:resultHolder:)")));
|
||||
@end;
|
||||
|
||||
__attribute__((objc_subclassing_restricted))
|
||||
__attribute__((swift_name("DelegateClass")))
|
||||
@interface KtDelegateClass : KtBase <KtKotlinReadWriteProperty>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#import "Memory.h"
|
||||
|
||||
extern "C" id objc_retain(id self);
|
||||
extern "C" id objc_retainBlock(id self);
|
||||
extern "C" void objc_release(id self);
|
||||
|
||||
inline static id GetAssociatedObject(ObjHeader* obj) {
|
||||
|
||||
@@ -444,8 +444,6 @@ static const char* getBlockEncoding(id block) {
|
||||
extern "C" convertReferenceFromObjC* Kotlin_ObjCExport_blockToFunctionConverters;
|
||||
extern "C" int Kotlin_ObjCExport_blockToFunctionConverters_size;
|
||||
|
||||
extern "C" id objc_retainBlock(id self);
|
||||
|
||||
static OBJ_GETTER(blockToKotlinImp, id block, SEL cmd) {
|
||||
const char* encoding = getBlockEncoding(block);
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#if KONAN_OBJC_INTEROP
|
||||
|
||||
#import <pthread.h>
|
||||
#import <Foundation/NSException.h>
|
||||
#import <Foundation/NSObject.h>
|
||||
|
||||
#import "ObjCExport.h"
|
||||
#import "ObjCExportErrors.h"
|
||||
|
||||
typedef void (^Completion)(id _Nullable, NSError* _Nullable);
|
||||
|
||||
extern "C" void Kotlin_ObjCExport_runCompletionSuccess(KRef completionHolder, KRef result) {
|
||||
Completion completion = (Completion)GetAssociatedObject(completionHolder);
|
||||
completion(Kotlin_ObjCExport_refToObjC(result), nullptr);
|
||||
}
|
||||
|
||||
extern "C" void Kotlin_ObjCExport_runCompletionFailure(
|
||||
KRef completionHolder,
|
||||
KRef exception,
|
||||
const TypeInfo** exceptionTypes
|
||||
) {
|
||||
id error = Kotlin_ObjCExport_ExceptionAsNSError(exception, exceptionTypes);
|
||||
Completion completion = (Completion)GetAssociatedObject(completionHolder);
|
||||
completion(nullptr, error);
|
||||
}
|
||||
|
||||
extern "C" OBJ_GETTER(Kotlin_ObjCExport_createContinuationArgumentImpl,
|
||||
KRef completionHolder, const TypeInfo** exceptionTypes);
|
||||
|
||||
extern "C" OBJ_GETTER(Kotlin_ObjCExport_createContinuationArgument, id completion, const TypeInfo** exceptionTypes) {
|
||||
if (pthread_main_np() != 1) {
|
||||
[NSException raise:NSGenericException
|
||||
format:@"Calling Kotlin suspend functions from Swift/Objective-C is currently supported only on main thread"];
|
||||
}
|
||||
ObjHolder slot;
|
||||
KRef completionHolder = AllocInstanceWithAssociatedObject(theForeignObjCObjectTypeInfo,
|
||||
objc_retainBlock(completion), slot.slot());
|
||||
|
||||
RETURN_RESULT_OF(Kotlin_ObjCExport_createContinuationArgumentImpl, completionHolder, exceptionTypes);
|
||||
}
|
||||
|
||||
extern "C" void Kotlin_ObjCExport_resumeContinuationSuccess(KRef continuation, KRef result);
|
||||
extern "C" void Kotlin_ObjCExport_resumeContinuationFailure(KRef continuation, KRef exception);
|
||||
|
||||
extern "C" void Kotlin_ObjCExport_resumeContinuation(KRef continuation, id result, id error) {
|
||||
ObjHolder holder;
|
||||
|
||||
if (error != nullptr) {
|
||||
if (result != nullptr) {
|
||||
[NSException raise:NSGenericException
|
||||
format:@"Kotlin completion handler is called with both result (%@) and error (%@) specified",
|
||||
result, error];
|
||||
}
|
||||
|
||||
KRef exception = Kotlin_ObjCExport_NSErrorAsException(error, holder.slot());
|
||||
Kotlin_ObjCExport_resumeContinuationFailure(continuation, exception);
|
||||
} else {
|
||||
KRef kotlinResult = Kotlin_ObjCExport_refFromObjC(result, holder.slot());
|
||||
Kotlin_ObjCExport_resumeContinuationSuccess(continuation, kotlinResult);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#import "Types.h"
|
||||
|
||||
extern "C" id Kotlin_ObjCExport_ExceptionAsNSError(KRef exception, const TypeInfo** types);
|
||||
extern "C" OBJ_GETTER(Kotlin_ObjCExport_NSErrorAsException, id error);
|
||||
@@ -26,6 +26,8 @@
|
||||
#import "Runtime.h"
|
||||
#import "Utils.h"
|
||||
|
||||
#import "ObjCExportErrors.h"
|
||||
|
||||
extern "C" OBJ_GETTER(Kotlin_Throwable_getMessage, KRef throwable);
|
||||
extern "C" OBJ_GETTER(Kotlin_ObjCExport_getWrappedError, KRef throwable);
|
||||
|
||||
@@ -52,14 +54,13 @@ static bool isExceptionOfType(KRef exception, const TypeInfo** types) {
|
||||
return false;
|
||||
}
|
||||
|
||||
extern "C" void Kotlin_ObjCExport_RethrowExceptionAsNSError(KRef exception, id* outError, const TypeInfo** types) {
|
||||
extern "C" id Kotlin_ObjCExport_ExceptionAsNSError(KRef exception, const TypeInfo** types) {
|
||||
ObjHolder errorHolder;
|
||||
KRef error = Kotlin_ObjCExport_getWrappedError(exception, errorHolder.slot());
|
||||
if (error != nullptr) {
|
||||
// Thrown originally by Swift/Objective-C.
|
||||
// Not actually a Kotlin exception, so don't check if it matches [types].
|
||||
if (outError != nullptr) *outError = Kotlin_ObjCExport_refToObjC(error);
|
||||
return;
|
||||
return Kotlin_ObjCExport_refToObjC(error);
|
||||
}
|
||||
|
||||
if (!isExceptionOfType(exception, types)) {
|
||||
@@ -69,10 +70,6 @@ extern "C" void Kotlin_ObjCExport_RethrowExceptionAsNSError(KRef exception, id*
|
||||
TerminateWithUnhandledException(exception);
|
||||
}
|
||||
|
||||
if (outError == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary<NSErrorUserInfoKey, id>* userInfo = [[NSMutableDictionary new] autorelease];
|
||||
userInfo[@"KotlinException"] = Kotlin_ObjCExport_refToObjC(exception);
|
||||
userInfo[@"KotlinExceptionOrigin"] = @(&kotlinExceptionOriginChar); // Support for different Kotlin runtimes loaded.
|
||||
@@ -84,13 +81,17 @@ extern "C" void Kotlin_ObjCExport_RethrowExceptionAsNSError(KRef exception, id*
|
||||
userInfo[NSLocalizedDescriptionKey] = description;
|
||||
}
|
||||
|
||||
*outError = [NSError errorWithDomain:@"KotlinException" code:0 userInfo:userInfo];
|
||||
return;
|
||||
return [NSError errorWithDomain:@"KotlinException" code:0 userInfo:userInfo];
|
||||
}
|
||||
|
||||
extern "C" void Kotlin_ObjCExport_RethrowNSErrorAsExceptionImpl(KRef message, KRef error);
|
||||
extern "C" void Kotlin_ObjCExport_RethrowExceptionAsNSError(KRef exception, id* outError, const TypeInfo** types) {
|
||||
id error = Kotlin_ObjCExport_ExceptionAsNSError(exception, types); // Also traps on unexpected exception.
|
||||
if (outError != nullptr) *outError = error;
|
||||
}
|
||||
|
||||
extern "C" void Kotlin_ObjCExport_RethrowNSErrorAsException(id error) {
|
||||
extern "C" OBJ_GETTER(Kotlin_ObjCExport_NSErrorAsExceptionImpl, KRef message, KRef error);
|
||||
|
||||
extern "C" OBJ_GETTER(Kotlin_ObjCExport_NSErrorAsException, id error) {
|
||||
NSString* description;
|
||||
|
||||
NSError* e = (NSError*) error;
|
||||
@@ -102,9 +103,7 @@ extern "C" void Kotlin_ObjCExport_RethrowNSErrorAsException(id error) {
|
||||
if (kotlinException != nullptr &&
|
||||
kotlinExceptionOrigin != nullptr && [kotlinExceptionOrigin isEqual:@(&kotlinExceptionOriginChar)]
|
||||
) {
|
||||
ObjHolder kotlinExceptionHolder;
|
||||
ThrowException(Kotlin_ObjCExport_refFromObjC(kotlinException, kotlinExceptionHolder.slot()));
|
||||
return;
|
||||
RETURN_RESULT_OF(Kotlin_ObjCExport_refFromObjC, kotlinException);
|
||||
}
|
||||
}
|
||||
description = e.localizedDescription;
|
||||
@@ -116,7 +115,13 @@ extern "C" void Kotlin_ObjCExport_RethrowNSErrorAsException(id error) {
|
||||
KRef message = Kotlin_Interop_CreateKStringFromNSString(description, messageHolder.slot());
|
||||
KRef kotlinError = Kotlin_ObjCExport_refFromObjC(error, errorHolder.slot()); // TODO: a simple opaque wrapper would be enough.
|
||||
|
||||
Kotlin_ObjCExport_RethrowNSErrorAsExceptionImpl(message, kotlinError);
|
||||
RETURN_RESULT_OF(Kotlin_ObjCExport_NSErrorAsExceptionImpl, message, kotlinError);
|
||||
}
|
||||
|
||||
extern "C" void Kotlin_ObjCExport_RethrowNSErrorAsException(id error) {
|
||||
ObjHolder holder;
|
||||
KRef exception = Kotlin_ObjCExport_NSErrorAsException(error, holder.slot());
|
||||
ThrowException(exception);
|
||||
}
|
||||
|
||||
@interface NSError (NSErrorKotlinException)
|
||||
|
||||
@@ -193,4 +193,35 @@ private inline fun <T> createCoroutineFromSuspendFunction(
|
||||
else -> error("This coroutine had already completed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function creates continuation suitable for passing as implicit argument to suspend functions.
|
||||
* The continuation calls [callback] and then delegates to [completion].
|
||||
*
|
||||
* The result is [ContinuationImpl] because that is an expectation of all coroutines machinery.
|
||||
*
|
||||
* It can be thought as a state machine of
|
||||
* ```
|
||||
* suspend fun foo() {
|
||||
* val result = runCatching { <suspended here> }
|
||||
* callback(result)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal inline fun createContinuationArgumentFromCallback(
|
||||
completion: Continuation<Unit>,
|
||||
crossinline callback: (Result<Any?>) -> Unit
|
||||
): Continuation<Any?> = object : ContinuationImpl(completion as Continuation<Any?>) {
|
||||
private var invoked = false
|
||||
|
||||
override fun invokeSuspend(result: Result<Any?>): Any? {
|
||||
if (invoked) error("This coroutine had already completed")
|
||||
invoked = true
|
||||
|
||||
callback(result)
|
||||
|
||||
return Unit // Not suspended.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package kotlin.native.internal
|
||||
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.coroutines.intrinsics.*
|
||||
import kotlin.coroutines.native.internal.*
|
||||
import kotlin.native.concurrent.*
|
||||
|
||||
@ExportForCppRuntime
|
||||
private fun Kotlin_ObjCExport_createContinuationArgumentImpl(
|
||||
completionHolder: Any,
|
||||
exceptionTypes: NativePtr
|
||||
): Continuation<Any?> = createContinuationArgumentFromCallback(EmptyCompletion) { result ->
|
||||
result.fold(
|
||||
onSuccess = { value ->
|
||||
runCompletionSuccess(completionHolder, value)
|
||||
},
|
||||
onFailure = { exception ->
|
||||
runCompletionFailure(completionHolder, exception, exceptionTypes)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private object EmptyCompletion : Continuation<Any?> {
|
||||
override val context: CoroutineContext
|
||||
get() = EmptyCoroutineContext
|
||||
|
||||
override fun resumeWith(result: Result<Any?>) {
|
||||
val exception = result.exceptionOrNull() ?: return
|
||||
TerminateWithUnhandledException(exception)
|
||||
// Throwing the exception from [resumeWith] is not generally expected.
|
||||
// Also terminating is consistent with other pieces of ObjCExport machinery.
|
||||
}
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
@ExportForCppRuntime("Kotlin_ObjCExport_resumeContinuationSuccess") // Also makes it a data flow root.
|
||||
internal fun resumeContinuation(continuation: Continuation<Any?>, value: Any?) {
|
||||
continuation.resume(value)
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
@ExportForCppRuntime("Kotlin_ObjCExport_resumeContinuationFailure") // Also makes it a data flow root.
|
||||
internal fun resumeContinuationWithException(continuation: Continuation<Any?>, exception: Throwable) {
|
||||
continuation.resumeWithException(exception)
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
@ExportForCompiler // Mark as data flow root.
|
||||
internal fun getCoroutineSuspended(): Any = COROUTINE_SUSPENDED
|
||||
|
||||
@PublishedApi
|
||||
@ExportForCompiler // Mark as data flow root.
|
||||
internal fun interceptedContinuation(continuation: Continuation<Any?>): Continuation<Any?> = continuation.intercepted()
|
||||
|
||||
@FilterExceptions
|
||||
@SymbolName("Kotlin_ObjCExport_runCompletionSuccess")
|
||||
private external fun runCompletionSuccess(completionHolder: Any, result: Any?)
|
||||
|
||||
@FilterExceptions
|
||||
@SymbolName("Kotlin_ObjCExport_runCompletionFailure")
|
||||
private external fun runCompletionFailure(completionHolder: Any, exception: Throwable, exceptionTypes: NativePtr)
|
||||
@@ -266,12 +266,10 @@ internal class NSEnumeratorAsKIterator : AbstractIterator<Any?>() {
|
||||
@ExportForCppRuntime private fun Kotlin_NSSetAsKSet_create() = NSSetAsKSet()
|
||||
@ExportForCppRuntime private fun Kotlin_NSDictionaryAsKMap_create() = NSDictionaryAsKMap()
|
||||
|
||||
@ExportForCppRuntime private fun Kotlin_ObjCExport_RethrowNSErrorAsExceptionImpl(
|
||||
@ExportForCppRuntime private fun Kotlin_ObjCExport_NSErrorAsExceptionImpl(
|
||||
message: String?,
|
||||
error: Any
|
||||
) {
|
||||
throw ObjCErrorException(message, error)
|
||||
}
|
||||
) = ObjCErrorException(message, error)
|
||||
|
||||
class ObjCErrorException(
|
||||
message: String?,
|
||||
|
||||
@@ -110,6 +110,9 @@ internal fun ReportUnhandledException(throwable: Throwable) {
|
||||
throwable.printStackTrace()
|
||||
}
|
||||
|
||||
@SymbolName("TerminateWithUnhandledException")
|
||||
internal external fun TerminateWithUnhandledException(throwable: Throwable)
|
||||
|
||||
@ExportForCppRuntime
|
||||
internal fun ExceptionReporterLaunchpad(reporter: (Throwable) -> Unit, throwable: Throwable) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user