diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPlatform.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPlatform.kt index ed58e9db013..eaa25792491 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPlatform.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPlatform.kt @@ -49,6 +49,8 @@ class KonanBuiltIns(storageManager: StorageManager) : KotlinBuiltIns(storageMana val packageName = FqName("konan.internal") val nativePtr = packageName.child(Name.identifier(nativePtrName)).toUnsafe() + + val throws = FqName("konan.Throws") } private val packageScope by lazy { builtInsModule.getPackage(FqNames.packageName).memberScope } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt index 508a208ab2b..2faed371105 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt @@ -22,6 +22,7 @@ import org.jetbrains.kotlin.backend.common.ir.Symbols import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.KONAN_FUNCTION_INTERFACES_MAX_PARAMETERS import org.jetbrains.kotlin.backend.konan.ValueType +import org.jetbrains.kotlin.backend.konan.descriptors.konanInternal import org.jetbrains.kotlin.backend.konan.llvm.findMainEntryPoint import org.jetbrains.kotlin.backend.konan.lower.TestProcessor import org.jetbrains.kotlin.builtins.KotlinBuiltIns @@ -137,6 +138,12 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym val allocObjCObject = symbolTable.referenceSimpleFunction(context.interopBuiltIns.allocObjCObject) + val objCExportTrapOnUndeclaredException = + symbolTable.referenceSimpleFunction(context.builtIns.konanInternal.getContributedFunctions( + Name.identifier("trapOnUndeclaredException"), + NoLookupLocation.FROM_BACKEND + ).single()) + val getNativeNullPtr = symbolTable.referenceSimpleFunction(context.builtIns.getNativeNullPtr) val boxFunctions = ValueType.values().associate { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt index ae6c29f43f8..bc2fdc890dc 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt @@ -502,6 +502,49 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, return LLVMBuildLandingPad(builder, landingpadType, personalityFunction, numClauses, name)!! } + fun kotlinExceptionHandler( + block: FunctionGenerationContext.(exception: LLVMValueRef) -> Unit + ): ExceptionHandler { + val lpBlock = basicBlock("kotlinExceptionHandler", null) + + appendingTo(lpBlock) { + val exception = catchKotlinException() + block(exception) + } + + return object : ExceptionHandler.Local() { + override val unwind: LLVMBasicBlockRef get() = lpBlock + } + } + + fun catchKotlinException(): LLVMValueRef { + val landingpadResult = gxxLandingpad(numClauses = 1, name = "lp") + + LLVMAddClause(landingpadResult, LLVMConstNull(kInt8Ptr)) + + // FIXME: properly handle C++ exceptions: currently C++ exception can be thrown out from try-finally + // bypassing the finally block. + + val exceptionRecord = extractValue(landingpadResult, 0, "er") + + // __cxa_begin_catch returns pointer to C++ exception object. + val beginCatch = context.llvm.cxaBeginCatchFunction + val exceptionRawPtr = call(beginCatch, listOf(exceptionRecord)) + + // Pointer to KotlinException instance: + val exceptionPtrPtr = bitcast(codegen.kObjHeaderPtrPtr, exceptionRawPtr, "") + + // Pointer to Kotlin exception object: + // We do need a slot here, as otherwise exception instance could be freed by _cxa_end_catch. + val exceptionPtr = loadSlot(exceptionPtrPtr, true, "exception") + + // __cxa_end_catch performs some C++ cleanup, including calling `KotlinException` class destructor. + val endCatch = context.llvm.cxaEndCatchFunction + call(endCatch, listOf()) + + return exceptionPtr + } + inline fun ifThenElse( condition: LLVMValueRef, thenValue: LLVMValueRef, @@ -529,6 +572,20 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, return resultPhi } + inline fun ifThen(condition: LLVMValueRef, thenBlock: () -> Unit) { + val bbExit = basicBlock(locationInfo = position()) + val bbThen = basicBlock(locationInfo = position()) + + condBr(condition, bbThen, bbExit) + + appendingTo(bbThen) { + thenBlock() + if (!isAfterTerminator()) br(bbExit) + } + + positionAtEnd(bbExit) + } + internal fun debugLocation(locationInfo: LocationInfo): DILocationRef? { if (!context.shouldContainDebugInfo()) return null update(currentBlock, locationInfo) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index 0fe411c4391..cd80e20f75f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -428,6 +428,8 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { val Kotlin_ObjCExport_convertUnit by lazyRtFunction val Kotlin_ObjCExport_GetAssociatedObject by lazyRtFunction val Kotlin_ObjCExport_AbstractMethodCalled by lazyRtFunction + val Kotlin_ObjCExport_RethrowExceptionAsNSError by lazyRtFunction + val Kotlin_ObjCExport_RethrowNSErrorAsException by lazyRtFunction val kObjectReservedTailSize = if (context.config.produce.isNativeBinary) { // Note: this defines the global declared in runtime (if any). diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 196479de267..92e8ecafa31 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -952,30 +952,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map kotlinReferenceToObjC(value) is ValueTypeBridge -> kotlinToObjC(value, typeBridge.objCValueType) - is HashCodeBridge -> { - assert(codegen.context.is64Bit()) - zext(value, kInt64) - } } fun FunctionGenerationContext.objCToKotlin( @@ -132,12 +129,6 @@ internal class ObjCExportCodeGenerator( ): LLVMValueRef = when (typeBridge) { is ReferenceBridge -> objCReferenceToKotlin(value, resultLifetime) is ValueTypeBridge -> objCToKotlin(value, typeBridge.objCValueType) - is HashCodeBridge -> { - assert(codegen.context.is64Bit()) - val low = trunc(value, int32Type) - val high = trunc(shr(value, 32, signed = false), int32Type) - xor(low, high) - } } fun FunctionGenerationContext.initRuntimeIfNeeded() { @@ -459,101 +450,167 @@ private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() { emitKotlinFunctionAdaptersToBlock() } -private fun ObjCExportCodeGenerator.generateObjCImp( - target: IrFunction?, +private inline fun ObjCExportCodeGenerator.generateObjCImpBy( methodBridge: MethodBridge, - isVirtual: Boolean = false + genBody: FunctionGenerationContext.() -> Unit ): LLVMValueRef { - // TODO: adapt exceptions. - - val returnType = methodBridge.returnBridge - val result = LLVMAddFunction(context.llvmModule, "", objCFunctionType(methodBridge))!! generateFunction(codegen, result) { - // TODO: call [NSObject init] if it is a constructor? - // TODO: check for abstract class if it is a constructor. + genBody() + } - if (methodBridge.isKotlinTopLevel) { - initRuntimeIfNeeded() // For instance methods it gets called when allocating. - } + LLVMSetLinkage(result, LLVMLinkage.LLVMPrivateLinkage) + return result +} - if (target == null) { - // IMP for abstract method. +private fun ObjCExportCodeGenerator.generateAbstractObjCImp(methodBridge: MethodBridge): LLVMValueRef = + generateObjCImpBy(methodBridge) { callFromBridge( context.llvm.Kotlin_ObjCExport_AbstractMethodCalled, listOf(param(0), param(1)) ) unreachable() - return@generateFunction - } - - val args = methodBridge.paramBridges.mapIndexedNotNull { index, typeBridge -> - val isReceiver = index == 0 - if (isReceiver && methodBridge.isKotlinTopLevel) { - null - } else { - val param = param(if (isReceiver) index else index + 1) - objCToKotlin(param, typeBridge, Lifetime.ARGUMENT) - } } +private fun ObjCExportCodeGenerator.generateObjCImp( + target: IrFunction?, + methodBridge: MethodBridge, + isVirtual: Boolean = false +) = if (target == null) { + generateAbstractObjCImp(methodBridge) +} else { + generateObjCImp(methodBridge) { args, resultLifetime, exceptionHandler -> val llvmTarget = if (!isVirtual) { codegen.llvmFunction(target) } else { lookupVirtualImpl(args.first(), target) } - val targetResult = callFromBridge(llvmTarget, args, Lifetime.ARGUMENT) + call(llvmTarget, args, resultLifetime, exceptionHandler) + } +} - if (target is IrConstructor) { - ret(param(0)) - } else when (returnType) { - VoidBridge -> ret(null) - is TypeBridge -> ret(kotlinToObjC(targetResult, returnType)) +private fun ObjCExportCodeGenerator.generateObjCImp( + methodBridge: MethodBridge, + callKotlin: FunctionGenerationContext.( + args: List, + resultLifetime: Lifetime, + exceptionHandler: ExceptionHandler + ) -> LLVMValueRef? +): LLVMValueRef = generateObjCImpBy(methodBridge) { + + val returnType = methodBridge.returnBridge + + // TODO: call [NSObject init] if it is a constructor? + // TODO: check for abstract class if it is a constructor. + + if (!methodBridge.isInstance) { + initRuntimeIfNeeded() // For instance methods it gets called when allocating. + } + + var errorOutPtr: LLVMValueRef? = null + var kotlinResultOutPtr: LLVMValueRef? = null + lateinit var kotlinResultOutBridge: TypeBridge + + val kotlinArgs = methodBridge.paramBridges.mapIndexedNotNull { index, paramBridge -> + val parameter = param(index) + when (paramBridge) { + is MethodBridgeValueParameter.Mapped -> + objCToKotlin(parameter, paramBridge.bridge, Lifetime.ARGUMENT) + + MethodBridgeReceiver.Static, MethodBridgeSelector -> null + MethodBridgeReceiver.Instance -> objCReferenceToKotlin(parameter, Lifetime.ARGUMENT) + + MethodBridgeReceiver.Factory -> null // actual value added by [callKotlin]. + + MethodBridgeValueParameter.ErrorOutParameter -> { + assert(errorOutPtr == null) + errorOutPtr = parameter + null + } + + is MethodBridgeValueParameter.KotlinResultOutParameter -> { + assert(kotlinResultOutPtr == null) + kotlinResultOutPtr = parameter + kotlinResultOutBridge = paramBridge.bridge + null + } } } - LLVMSetLinkage(result, LLVMLinkage.LLVMPrivateLinkage) + // TODO: consider merging this handler with function cleanup. + val exceptionHandler = if (errorOutPtr == null) { + kotlinExceptionHandler { exception -> + callFromBridge(context.ir.symbols.objCExportTrapOnUndeclaredException.owner.llvmFunction, listOf(exception)) + unreachable() + } + } else { + kotlinExceptionHandler { exception -> + callFromBridge( + context.llvm.Kotlin_ObjCExport_RethrowExceptionAsNSError, + listOf(exception, errorOutPtr!!) + ) + + val returnValue = when (returnType) { + !is MethodBridge.ReturnValue.WithError -> + error("bridge with error parameter has unexpected return type: $returnType") + + MethodBridge.ReturnValue.WithError.Success -> Int8(0).llvm // false + + is MethodBridge.ReturnValue.WithError.RefOrNull -> { + if (returnType.successBridge == MethodBridge.ReturnValue.Instance.InitResult) { + // Release init receiver, as required by convention. + callFromBridge(objcRelease, listOf(param(0))) + } + kNullInt8Ptr + } + } + + ret(returnValue) + } + } + + val targetResult = callKotlin(kotlinArgs, Lifetime.ARGUMENT, exceptionHandler) + + kotlinResultOutPtr?.let { + ifThen(icmpNe(it, LLVMConstNull(it.type)!!)) { + val objCResult = kotlinToObjC(targetResult!!, kotlinResultOutBridge) + store(objCResult, it) + } + } + + tailrec fun genReturnValueOnSuccess(returnBridge: MethodBridge.ReturnValue): LLVMValueRef? = when (returnBridge) { + MethodBridge.ReturnValue.Void -> null + MethodBridge.ReturnValue.HashCode -> { + assert(codegen.context.is64Bit()) + zext(targetResult!!, kInt64) + } + is MethodBridge.ReturnValue.Mapped -> kotlinToObjC(targetResult!!, returnBridge.bridge) + MethodBridge.ReturnValue.WithError.Success -> Int8(1).llvm // true + is MethodBridge.ReturnValue.WithError.RefOrNull -> genReturnValueOnSuccess(returnBridge.successBridge) + MethodBridge.ReturnValue.Instance.InitResult -> param(0) + MethodBridge.ReturnValue.Instance.FactoryResult -> kotlinReferenceToObjC(targetResult!!) // provided by [callKotlin] + } + + ret(genReturnValueOnSuccess(returnType)) - return result } private fun ObjCExportCodeGenerator.generateObjCImpForArrayConstructor( target: ConstructorDescriptor, methodBridge: MethodBridge -): LLVMValueRef { - // TODO: adapt exceptions. +): LLVMValueRef = generateObjCImp(methodBridge) { args, resultLifetime, exceptionHandler -> + val targetIr = context.ir.get(target) - val result = LLVMAddFunction(context.llvmModule, "", objCFunctionType(methodBridge))!! + val arrayInstance = callFromBridge( + context.llvm.allocArrayFunction, + listOf((targetIr as IrConstructor).constructedClass.llvmTypeInfoPtr, args.first()), + resultLifetime = Lifetime.ARGUMENT + ) - generateFunction(codegen, result) { - initRuntimeIfNeeded() // For instance methods it gets called when allocating. - - val kotlinValueArgs = methodBridge.paramBridges - .drop(1) // Drop class method receiver. - .mapIndexed { index, typeBridge -> - objCToKotlin(param(index + 2), typeBridge, Lifetime.ARGUMENT) - } - - val targetIr = context.ir.get(target) - - val arrayInstance = callFromBridge( - context.llvm.allocArrayFunction, - listOf((targetIr as IrConstructor).constructedClass.llvmTypeInfoPtr, kotlinValueArgs.first()), - resultLifetime = Lifetime.ARGUMENT - ) - callFromBridge( - targetIr.llvmFunction, - listOf(arrayInstance) + kotlinValueArgs - ) - - ret(kotlinToObjC(arrayInstance, ReferenceBridge)) - } - - LLVMSetLinkage(result, LLVMLinkage.LLVMPrivateLinkage) - - return result + call(targetIr.llvmFunction, listOf(arrayInstance) + args, resultLifetime, exceptionHandler) + arrayInstance } // TODO: cache bridges. @@ -563,62 +620,124 @@ private fun ObjCExportCodeGenerator.generateKotlinToObjCBridge( ): ConstPointer { val methodBridge = mapper.bridgeMethod(baseMethod) - val allBaseMethodParams = baseMethod.allParameters - val paramBridges = methodBridge.paramBridges - val returnBridge = methodBridge.returnBridge + val parameterToBase = descriptor.allParameters.zip(baseMethod.allParameters).toMap() val objcMsgSend = msgSender(objCFunctionType(methodBridge)) val functionType = codegen.getLlvmFunctionType(context.ir.get(descriptor)) val result = generateFunction(codegen, functionType, "") { - val args = mutableListOf() + var errorOutPtr: LLVMValueRef? = null + var kotlinResultOutPtr: LLVMValueRef? = null + lateinit var kotlinResultOutBridge: TypeBridge - descriptor.allParameters.forEachIndexed { index, parameter -> + val parameters = descriptor.allParameters.mapIndexed { index, parameterDescriptor -> + parameterDescriptor to param(index) + }.toMap() - val kotlinValue = convertKotlin( - { param(index) }, - actualType = parameter.type, - expectedType = allBaseMethodParams[index].type, - resultLifetime = Lifetime.ARGUMENT - ) + val objCArgs = methodBridge.parametersAssociated(descriptor).map { (bridge, parameter) -> + when (bridge) { + is MethodBridgeValueParameter.Mapped -> { + parameter!! + val kotlinValue = convertKotlin( + { parameters[parameter]!! }, + actualType = parameter.type, + expectedType = parameterToBase[parameter]!!.type, + resultLifetime = Lifetime.ARGUMENT + ) + kotlinToObjC(kotlinValue, bridge.bridge) + } - args += kotlinToObjC(kotlinValue, paramBridges[index]) + MethodBridgeReceiver.Instance -> kotlinReferenceToObjC(parameters[parameter]!!) + MethodBridgeSelector -> genSelector(namer.getSelector(baseMethod)) - // TODO: if `convertKotlin` boxes Kotlin value, then it gets converted by `kotlinToObjC` to `NSNumber`, - // and boxing directly to `NSNumber` would be much efficient. + MethodBridgeReceiver.Static, + MethodBridgeReceiver.Factory -> + error("Method is not instance and thus can't have bridge for overriding: $baseMethod") - if (index == 0) { - args += genSelector(namer.getSelector(baseMethod)) + MethodBridgeValueParameter.ErrorOutParameter -> + alloca(int8TypePtr).also { errorOutPtr = it } + + is MethodBridgeValueParameter.KotlinResultOutParameter -> + alloca(bridge.bridge.objCType).also { + kotlinResultOutPtr = it + kotlinResultOutBridge = bridge.bridge + } } } - val targetResult = callFromBridge(objcMsgSend, args) + val targetResult = callFromBridge(objcMsgSend, objCArgs) assert(baseMethod !is ConstructorDescriptor) - when (returnBridge) { - VoidBridge -> { - if (LLVMGetReturnType(functionType) == voidType) { - ret(null) - } else { - ret(staticData.theUnitInstanceRef.llvm) - } - } - is TypeBridge -> { - - val genConvertedTargetResult = { lifetime: Lifetime -> - objCToKotlin(targetResult, returnBridge, lifetime) - } - - ret(convertKotlin( - genConvertedTargetResult, - actualType = baseMethod.returnType!!, - expectedType = descriptor.returnType!!, - resultLifetime = Lifetime.RETURN_VALUE - )) - } + fun rethrow() { + val error = load(errorOutPtr!!) + callFromBridge(context.llvm.Kotlin_ObjCExport_RethrowNSErrorAsException, listOf(error)) + unreachable() } + + fun genKotlinBaseMethodResult( + lifetime: Lifetime, + returnBridge: MethodBridge.ReturnValue + ): LLVMValueRef? = when (returnBridge) { + MethodBridge.ReturnValue.Void -> null + + MethodBridge.ReturnValue.HashCode -> { + assert(codegen.context.is64Bit()) + val low = trunc(targetResult, int32Type) + val high = trunc(shr(targetResult, 32, signed = false), int32Type) + xor(low, high) + } + + is MethodBridge.ReturnValue.Mapped -> { + objCToKotlin(targetResult, returnBridge.bridge, lifetime) + } + + MethodBridge.ReturnValue.WithError.Success -> { + ifThen(icmpEq(targetResult, Int8(0).llvm)) { + rethrow() + } + + kotlinResultOutPtr?.let { + objCToKotlin(load(it), kotlinResultOutBridge, lifetime) + } + } + + is MethodBridge.ReturnValue.WithError.RefOrNull -> { + ifThen(icmpEq(targetResult, kNullInt8Ptr)) { + rethrow() + } + assert(kotlinResultOutPtr == null) + genKotlinBaseMethodResult(lifetime, returnBridge.successBridge) + } + + MethodBridge.ReturnValue.Instance.InitResult, + MethodBridge.ReturnValue.Instance.FactoryResult -> + error("init or factory method can't have bridge for overriding: $baseMethod") + } + + val baseReturnType = baseMethod.returnType!! + val actualReturnType = descriptor.returnType!! + + val retVal = when { + actualReturnType.isUnit() -> { + genKotlinBaseMethodResult(Lifetime.ARGUMENT, methodBridge.returnBridge) + null + } + baseReturnType.isUnit() -> { + genKotlinBaseMethodResult(Lifetime.ARGUMENT, methodBridge.returnBridge) + codegen.theUnitInstanceRef.llvm + } + else -> + convertKotlin( + { lifetime -> genKotlinBaseMethodResult(lifetime, methodBridge.returnBridge)!! }, + actualType = baseReturnType, + expectedType = actualReturnType, + resultLifetime = Lifetime.RETURN_VALUE + ) + } + + ret(retVal) } LLVMSetLinkage(result, LLVMLinkage.LLVMPrivateLinkage) @@ -887,11 +1006,16 @@ private fun ObjCExportCodeGenerator.createDirectAdapters( } } -private inline fun ObjCExportCodeGenerator.generateObjCToKotlinMethodAdapter( - methodBridge: MethodBridge, +private inline fun ObjCExportCodeGenerator.generateObjCToKotlinSyntheticGetter( selector: String, block: FunctionGenerationContext.() -> Unit ): ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter { + + val methodBridge = MethodBridge( + MethodBridge.ReturnValue.Mapped(ReferenceBridge), + MethodBridgeReceiver.Static, valueParameters = emptyList() + ) + val encoding = getEncoding(methodBridge) val imp = generateFunction(codegen, objCFunctionType(methodBridge), "") { block() @@ -903,8 +1027,7 @@ private inline fun ObjCExportCodeGenerator.generateObjCToKotlinMethodAdapter( } private fun ObjCExportCodeGenerator.createUnitInstanceAdapter() = - generateObjCToKotlinMethodAdapter( - MethodBridge(ReferenceBridge, listOf(ReferenceBridge, ReferenceBridge)), + generateObjCToKotlinSyntheticGetter( namer.getObjectInstanceSelector(context.builtIns.unit) ) { initRuntimeIfNeeded() // For instance methods it gets called when allocating. @@ -919,9 +1042,8 @@ private fun ObjCExportCodeGenerator.createObjectInstanceAdapter( assert(!descriptor.isUnit()) val selector = namer.getObjectInstanceSelector(descriptor) - val methodBridge = MethodBridge(ReferenceBridge, listOf(ReferenceBridge, ReferenceBridge)) - return generateObjCToKotlinMethodAdapter(methodBridge, selector) { + return generateObjCToKotlinSyntheticGetter(selector) { initRuntimeIfNeeded() // For instance methods it gets called when allocating. val value = getObjectValue(context.ir.get(descriptor), shared = false, locationInfo = null, exceptionHandler = ExceptionHandler.Caller) @@ -935,9 +1057,8 @@ private fun ObjCExportCodeGenerator.createEnumEntryAdapter( assert(descriptor.kind == ClassKind.ENUM_ENTRY) val selector = namer.getEnumEntrySelector(descriptor) - val methodBridge = MethodBridge(ReferenceBridge, listOf(ReferenceBridge, ReferenceBridge)) - return generateObjCToKotlinMethodAdapter(methodBridge, selector) { + return generateObjCToKotlinSyntheticGetter(selector) { initRuntimeIfNeeded() // For instance methods it gets called when allocating. val value = getEnumEntry(context.ir.getEnumEntry(descriptor), ExceptionHandler.Caller) @@ -954,12 +1075,7 @@ private fun List.toMethods(): List } private fun objCFunctionType(methodBridge: MethodBridge): LLVMTypeRef { - val paramTypes = mutableListOf() - - methodBridge.paramBridges.forEachIndexed { index, typeBridge -> - paramTypes += typeBridge.objCType - if (index == 0) paramTypes += int8TypePtr // Selector. - } + val paramTypes = methodBridge.paramBridges.map { it.objCType } val returnType = methodBridge.returnBridge.objCType @@ -977,30 +1093,38 @@ private val ObjCValueType.llvmType: LLVMTypeRef get() = when (this) { ObjCValueType.DOUBLE -> LLVMDoubleType()!! } -private val ReturnableTypeBridge.objCType: LLVMTypeRef get() = when (this) { - VoidBridge -> voidType +private val MethodBridgeParameter.objCType: LLVMTypeRef get() = when (this) { + is MethodBridgeValueParameter.Mapped -> this.bridge.objCType + is MethodBridgeReceiver -> ReferenceBridge.objCType + MethodBridgeSelector -> int8TypePtr + MethodBridgeValueParameter.ErrorOutParameter -> pointerType(ReferenceBridge.objCType) + is MethodBridgeValueParameter.KotlinResultOutParameter -> pointerType(this.bridge.objCType) +} + +private val MethodBridge.ReturnValue.objCType: LLVMTypeRef get() = when (this) { + MethodBridge.ReturnValue.Void -> voidType + MethodBridge.ReturnValue.HashCode -> kInt64 // TODO: only for 64-bit platforms + is MethodBridge.ReturnValue.Mapped -> this.bridge.objCType + MethodBridge.ReturnValue.WithError.Success -> ObjCValueType.BOOL.llvmType + + MethodBridge.ReturnValue.Instance.InitResult, + MethodBridge.ReturnValue.Instance.FactoryResult, + is MethodBridge.ReturnValue.WithError.RefOrNull -> ReferenceBridge.objCType +} + +private val TypeBridge.objCType: LLVMTypeRef get() = when (this) { is ReferenceBridge -> int8TypePtr is ValueTypeBridge -> this.objCValueType.llvmType - is HashCodeBridge -> kInt64 // TODO: only for 64-bit platforms } internal fun ObjCExportCodeGenerator.getEncoding(methodBridge: MethodBridge): String { var paramOffset = 0 - val pointerSize = runtime.pointerSize val params = buildString { - fun appendParam(encoding: String, size: Int) { - append(encoding) + methodBridge.paramBridges.forEach { + append(it.objCEncoding) append(paramOffset) - paramOffset += size - } - - methodBridge.paramBridges.forEachIndexed { index, typeBridge -> - appendParam( - typeBridge.objCEncoding, - LLVMStoreSizeOfType(runtime.targetData, typeBridge.objCType).toInt() - ) - if (index == 0) appendParam(":", pointerSize) + paramOffset += LLVMStoreSizeOfType(runtime.targetData, it.objCType).toInt() } } @@ -1010,11 +1134,28 @@ internal fun ObjCExportCodeGenerator.getEncoding(methodBridge: MethodBridge): St return "$returnTypeEncoding$paramSize$params" } -private val ReturnableTypeBridge.objCEncoding: String get() = when (this) { - VoidBridge -> "v" +private val MethodBridge.ReturnValue.objCEncoding: String get() = when (this) { + MethodBridge.ReturnValue.Void -> "v" + MethodBridge.ReturnValue.HashCode -> "L" // NSUInteger = unsigned long; // TODO: `unsigned int` on watchOS + is MethodBridge.ReturnValue.Mapped -> this.bridge.objCEncoding + MethodBridge.ReturnValue.WithError.Success -> ObjCValueType.BOOL.encoding + + MethodBridge.ReturnValue.Instance.InitResult, + MethodBridge.ReturnValue.Instance.FactoryResult, + is MethodBridge.ReturnValue.WithError.RefOrNull -> ReferenceBridge.objCEncoding +} + +private val MethodBridgeParameter.objCEncoding: String get() = when (this) { + is MethodBridgeValueParameter.Mapped -> this.bridge.objCEncoding + is MethodBridgeReceiver -> ReferenceBridge.objCEncoding + MethodBridgeSelector -> ":" + MethodBridgeValueParameter.ErrorOutParameter -> "^${ReferenceBridge.objCEncoding}" + is MethodBridgeValueParameter.KotlinResultOutParameter -> "^${this.bridge.objCEncoding}" +} + +private val TypeBridge.objCEncoding: String get() = when (this) { ReferenceBridge -> "@" is ValueTypeBridge -> this.objCValueType.encoding - HashCodeBridge -> "L" // NSUInteger = unsigned long; // TODO: `unsigned int` on watchOS } internal fun Context.is64Bit(): Boolean = this.config.target.architecture.bitness == 64 diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt index e4b63e1ebc3..cbea73dd973 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -16,19 +16,18 @@ package org.jetbrains.kotlin.backend.konan.objcexport -import org.jetbrains.kotlin.backend.konan.Context -import org.jetbrains.kotlin.backend.konan.KonanCompilationException -import org.jetbrains.kotlin.backend.konan.descriptors.enumEntries -import org.jetbrains.kotlin.backend.konan.descriptors.getPackageFragments -import org.jetbrains.kotlin.backend.konan.descriptors.isArray -import org.jetbrains.kotlin.backend.konan.descriptors.isInterface -import org.jetbrains.kotlin.backend.konan.reportCompilationError -import org.jetbrains.kotlin.backend.konan.reportCompilationWarning +import org.jetbrains.kotlin.backend.konan.* +import org.jetbrains.kotlin.backend.konan.descriptors.* import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.ir.util.report import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.constants.ArrayValue +import org.jetbrains.kotlin.resolve.constants.KClassValue import org.jetbrains.kotlin.resolve.descriptorUtil.* import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.types.KotlinType @@ -37,7 +36,7 @@ import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.utils.addIfNotNull internal class ObjCExportHeaderGenerator(val context: Context) { - val mapper: ObjCExportMapper = object : ObjCExportMapper { + val mapper: ObjCExportMapper = object : ObjCExportMapper() { override fun getCategoryMembersFor(descriptor: ClassDescriptor) = extensions[descriptor].orEmpty() @@ -376,12 +375,18 @@ internal class ObjCExportHeaderGenerator(val context: Context) { assert(mapper.isBaseProperty(baseProperty)) assert(mapper.isObjCProperty(baseProperty)) - val type = mapType(property.type, mapper.bridgeMethod(baseProperty.getter!!).returnBridge) + val getterBridge = mapper.bridgeMethod(baseProperty.getter!!) + val type = mapReturnType(getterBridge.returnBridge, property.getter!!) val name = namer.getName(baseProperty) append("@property ") val attributes = mutableListOf() + + if (!getterBridge.isInstance) { + attributes += "class" + } + val getterSelector = getSelector(baseProperty.getter!!) if (getterSelector != name) { attributes += "getter=$getterSelector" @@ -408,32 +413,36 @@ internal class ObjCExportHeaderGenerator(val context: Context) { assert(mapper.isBaseMethod(baseMethod)) val methodBridge = mapper.bridgeMethod(baseMethod) + exportThrownFromThisAndOverridden(method) + val selectorParts = getSelector(baseMethod).split(':') - val isArrayConstructor = method is ConstructorDescriptor && method.constructedClass.isArray - if (methodBridge.isKotlinTopLevel || isArrayConstructor) { - append("+") - } else { + if (methodBridge.isInstance) { append("-") + } else { + append("+") } append("(") - val returnType = if (method is ConstructorDescriptor) { - "instancetype" - } else { - mapType(method.returnType!!, methodBridge.returnBridge).render() - } - append(returnType) + append(mapReturnType(methodBridge.returnBridge, method).render()) append(")") - val valueParameters = mapper.objCValueParameters(method) + val valueParametersAssociated = methodBridge.valueParametersAssociated(method) + val valueParameterNames = mutableListOf() - valueParameters.forEach { p -> - // TODO: mangle only the extension receiver parameter. - var candidate = when { - p is ReceiverParameterDescriptor -> "receiver" - method is PropertySetterDescriptor -> "value" - else -> p.name.asString() + + valueParametersAssociated.forEach { (bridge, p) -> + var candidate = when (bridge) { + is MethodBridgeValueParameter.Mapped -> { + p!! + when { + p is ReceiverParameterDescriptor -> "receiver" + method is PropertySetterDescriptor -> "value" + else -> p.name.asString() + } + } + MethodBridgeValueParameter.ErrorOutParameter -> "error" + is MethodBridgeValueParameter.KotlinResultOutParameter -> "result" } while (candidate in valueParameterNames) { candidate += "_" @@ -443,8 +452,17 @@ internal class ObjCExportHeaderGenerator(val context: Context) { append(selectorParts[0]) - valueParameters.forEachIndexed { index, p -> + valueParametersAssociated.forEachIndexed { index, (bridge, p) -> val name = valueParameterNames[index] + val type = when (bridge) { + is MethodBridgeValueParameter.Mapped -> mapType(p!!.type, bridge.bridge) + MethodBridgeValueParameter.ErrorOutParameter -> + ObjCPointerType(ObjCNullableReferenceType(ObjCClassType("NSError")), nullable = true) + + is MethodBridgeValueParameter.KotlinResultOutParameter -> + ObjCPointerType(mapType(method.returnType!!, bridge.bridge), nullable = true) + } + if (index != 0) { append(' ') append(selectorParts[index]) @@ -452,7 +470,7 @@ internal class ObjCExportHeaderGenerator(val context: Context) { append(":") append("(") - append(mapType(p.type, methodBridge.paramBridges[index + 1]).render()) + append(type.render()) append(")") append(name) } @@ -461,9 +479,76 @@ internal class ObjCExportHeaderGenerator(val context: Context) { append(" NS_SWIFT_NAME($swiftName)") - if (method is ConstructorDescriptor && !isArrayConstructor) { + if (method is ConstructorDescriptor && !method.constructedClass.isArray) { // TODO: check methodBridge instead. append(" NS_DESIGNATED_INITIALIZER") } + + // TODO: consider adding swift_error attribute. + } + + private val methodsWithThrowAnnotationConsidered = mutableSetOf() + + private val uncheckedExceptionClasses = listOf("Error", "RuntimeException").map { + context.builtIns.builtInsPackageScope + .getContributedClassifier(Name.identifier(it), NoLookupLocation.FROM_BACKEND) as ClassDescriptor + } + + private fun exportThrown(method: FunctionDescriptor) { + if (!method.kind.isReal) return + val throwsAnnotation = method.annotations.findAnnotation(KonanBuiltIns.FqNames.throws) ?: return + + if (!mapper.doesThrow(method)) { + context.report( + context.ir.get(method), + "@${KonanBuiltIns.FqNames.throws.shortName()} annotation should also be added to a base method", + isError = false + ) + } + + if (method in methodsWithThrowAnnotationConsidered) return + methodsWithThrowAnnotationConsidered += method + + val arguments = (throwsAnnotation.allValueArguments.values.single() as ArrayValue).value + for (argument in arguments) { + val classDescriptor = TypeUtils.getClassDescriptor((argument as KClassValue).value) ?: continue + + uncheckedExceptionClasses.firstOrNull { classDescriptor.isSubclassOf(it) }?.let { + context.report( + context.ir.get(method), + "Method is declared to throw ${classDescriptor.fqNameSafe}, " + + "but instances of ${it.fqNameSafe} and its subclasses aren't propagated " + + "from Kotlin to Objective-C/Swift", + isError = false + ) + } + + scheduleClassToBeGenerated(classDescriptor) + } + } + + private fun exportThrownFromThisAndOverridden(method: FunctionDescriptor) { + method.allOverriddenDescriptors.forEach { exportThrown(it) } + } + + private fun mapReturnType( + returnBridge: MethodBridge.ReturnValue, + method: FunctionDescriptor + ): ObjCType = when (returnBridge) { + MethodBridge.ReturnValue.Void -> ObjCVoidType + MethodBridge.ReturnValue.HashCode -> ObjCPrimitiveType("NSUInteger") + is MethodBridge.ReturnValue.Mapped -> mapType(method.returnType!!, returnBridge.bridge) + MethodBridge.ReturnValue.WithError.Success -> ObjCPrimitiveType("BOOL") + is MethodBridge.ReturnValue.WithError.RefOrNull -> { + val successReturnType = mapReturnType(returnBridge.successBridge, method) + if (successReturnType !is ObjCNonNullReferenceType) { + error("Function is expected to have non-null return type: $method") + } + + ObjCNullableReferenceType(successReturnType) + } + + MethodBridge.ReturnValue.Instance.InitResult, + MethodBridge.ReturnValue.Instance.FactoryResult -> ObjCInstanceType } fun build(): List = mutableListOf().apply { @@ -505,6 +590,11 @@ internal class ObjCExportHeaderGenerator(val context: Context) { add("@end;") add("") + add("@interface NSError (NSErrorKotlinException)") + add("@property (readonly) id _Nullable kotlinException;") + add("@end;") + add("") + stubs.forEach { addAll(it.lines) add("") @@ -517,52 +607,60 @@ internal class ObjCExportHeaderGenerator(val context: Context) { internal sealed class ObjCType { final override fun toString(): String = this.render() - open fun render(varName: String): String = "${this.render()} $varName" - abstract fun render(): String + abstract fun render(attrsAndName: String): String + + fun render() = render("") + + protected fun String.withAttrsAndName(attrsAndName: String) = + if (attrsAndName.isEmpty()) this else "$this ${attrsAndName.trimStart()}" } -internal sealed class ObjCReferenceType(kotlinType: KotlinType) : ObjCType() { - val attributes = if (TypeUtils.isNullableType(kotlinType)) " _Nullable" else "" +internal sealed class ObjCReferenceType : ObjCType() + +internal sealed class ObjCNonNullReferenceType : ObjCReferenceType() + +internal data class ObjCNullableReferenceType(val nonNullType: ObjCNonNullReferenceType) : ObjCReferenceType() { + override fun render(attrsAndName: String) = nonNullType.render(" _Nullable".withAttrsAndName(attrsAndName)) } private class ObjCClassType( - kotlinType: KotlinType, val className: String, - val typeArguments: List = emptyList() -) : ObjCReferenceType(kotlinType) { + val typeArguments: List = emptyList() +) : ObjCNonNullReferenceType() { - override fun render() = buildString { + override fun render(attrsAndName: String) = buildString { append(className) if (typeArguments.isNotEmpty()) { append("<") typeArguments.joinTo(this) { it.render() } append(">") } - append('*') - - append(attributes) + append(" *") + append(attrsAndName) } } -private class ObjCProtocolType(kotlinType: KotlinType, val protocolName: String) : ObjCReferenceType(kotlinType) { - override fun render() = "id<$protocolName>$attributes" // TODO: check +private class ObjCProtocolType(val protocolName: String) : ObjCNonNullReferenceType() { + + override fun render(attrsAndName: String) = "id<$protocolName>".withAttrsAndName(attrsAndName) } -private class ObjCIdType(kotlinType: KotlinType) : ObjCReferenceType(kotlinType) { - override fun render() = "id$attributes" +private object ObjCIdType : ObjCNonNullReferenceType() { + override fun render(attrsAndName: String) = "id".withAttrsAndName(attrsAndName) +} + +private object ObjCInstanceType : ObjCNonNullReferenceType() { + override fun render(attrsAndName: String): String = "instancetype".withAttrsAndName(attrsAndName) } private class ObjCBlockPointerType( - kotlinType: KotlinType, val returnType: ObjCReferenceType, val parameterTypes: List -) : ObjCReferenceType(kotlinType) { + val returnType: ObjCReferenceType, val parameterTypes: List +) : ObjCNonNullReferenceType() { - override fun render() = render("") - - override fun render(varName: String) = buildString { + override fun render(attrsAndName: String) = buildString { append(returnType.render()) - append("(^") - append(attributes) - append(varName) + append(" (^") + append(attrsAndName) append(")(") if (parameterTypes.isEmpty()) append("void") parameterTypes.joinTo(this) { it.render() } @@ -571,25 +669,33 @@ private class ObjCBlockPointerType( } private class ObjCPrimitiveType(val cName: String) : ObjCType() { - override fun render() = cName + override fun render(attrsAndName: String) = cName.withAttrsAndName(attrsAndName) +} + +private class ObjCPointerType(val pointee: ObjCType, val nullable: Boolean = false) : ObjCType() { + override fun render(attrsAndName: String) = + pointee.render("*${if (nullable) { + " _Nullable".withAttrsAndName(attrsAndName) + } else { + attrsAndName + }}") } private object ObjCVoidType : ObjCType() { - override fun render() = "void" - override fun render(varName: String) = error("variables can't have `void` type") + override fun render(attrsAndName: String) = "void".withAttrsAndName(attrsAndName) } internal interface CustomTypeMapper { val mappedClassDescriptor: ClassDescriptor - fun mapType(type: KotlinType, mappedSuperType: KotlinType): ObjCReferenceType + fun mapType(mappedSuperType: KotlinType): ObjCNonNullReferenceType class Simple( override val mappedClassDescriptor: ClassDescriptor, private val objCClassName: String ) : CustomTypeMapper { - override fun mapType(type: KotlinType, mappedSuperType: KotlinType): ObjCReferenceType = - ObjCClassType(type, objCClassName) + override fun mapType(mappedSuperType: KotlinType): ObjCNonNullReferenceType = + ObjCClassType(objCClassName) } class Collection( @@ -597,18 +703,18 @@ internal interface CustomTypeMapper { override val mappedClassDescriptor: ClassDescriptor, private val objCClassName: String ) : CustomTypeMapper { - override fun mapType(type: KotlinType, mappedSuperType: KotlinType): ObjCReferenceType { + override fun mapType(mappedSuperType: KotlinType): ObjCNonNullReferenceType { val typeArguments = mappedSuperType.arguments.map { val argument = it.type if (TypeUtils.isNullableType(argument)) { // Kotlin `null` keys and values are represented as `NSNull` singleton. - ObjCIdType(generator.context.builtIns.anyType) + ObjCIdType } else { - generator.mapReferenceType(argument) + generator.mapReferenceTypeIgnoringNullability(argument) } } - return ObjCClassType(type, objCClassName, typeArguments) + return ObjCClassType(objCClassName, typeArguments) } } @@ -618,7 +724,7 @@ internal interface CustomTypeMapper { ) : CustomTypeMapper { override val mappedClassDescriptor = generator.context.builtIns.getFunction(parameterCount) - override fun mapType(type: KotlinType, mappedSuperType: KotlinType): ObjCReferenceType { + override fun mapType(mappedSuperType: KotlinType): ObjCNonNullReferenceType { val functionType = mappedSuperType val returnType = functionType.getReturnTypeFromFunctionType() @@ -626,7 +732,6 @@ internal interface CustomTypeMapper { functionType.getValueParameterTypesFromFunctionType().map { it.type } return ObjCBlockPointerType( - type, generator.mapReferenceType(returnType), parameterTypes.map { generator.mapReferenceType(it) } ) @@ -634,8 +739,18 @@ internal interface CustomTypeMapper { } } -private fun ObjCExportHeaderGenerator.mapReferenceType(kotlinType: KotlinType): ObjCReferenceType { +private fun ObjCExportHeaderGenerator.mapReferenceType(kotlinType: KotlinType): ObjCReferenceType = + mapReferenceTypeIgnoringNullability(kotlinType).let { + if (TypeUtils.isNullableType(kotlinType)) { + ObjCNullableReferenceType(it) + } else { + it + } + } +private fun ObjCExportHeaderGenerator.mapReferenceTypeIgnoringNullability( + kotlinType: KotlinType +): ObjCNonNullReferenceType { val typeToMapper = (listOf(kotlinType) + kotlinType.supertypes()).mapNotNull { type -> val mapper = customTypeMappers[type.constructor.declarationDescriptor] if (mapper != null) { @@ -665,7 +780,7 @@ private fun ObjCExportHeaderGenerator.mapReferenceType(kotlinType: KotlinType): } mostSpecificTypeToMapper.entries.firstOrNull()?.let { (type, mapper) -> - return mapper.mapType(kotlinType, type) + return mapper.mapType(type) } val classDescriptor = kotlinType.getErasedTypeClass() @@ -673,25 +788,28 @@ private fun ObjCExportHeaderGenerator.mapReferenceType(kotlinType: KotlinType): // TODO: translate `where T : BaseClass, T : SomeInterface` to `BaseClass* ` if (classDescriptor == context.builtIns.any || classDescriptor in hiddenTypes) { - return ObjCIdType(kotlinType) + return ObjCIdType } - if (classDescriptor !in generatedClasses) { - extraClassesToTranslate += classDescriptor - } + scheduleClassToBeGenerated(classDescriptor) return if (classDescriptor.isInterface) { - ObjCProtocolType(kotlinType, translateClassName(classDescriptor)) + ObjCProtocolType(translateClassName(classDescriptor)) } else { - ObjCClassType(kotlinType, translateClassName(classDescriptor)) + ObjCClassType(translateClassName(classDescriptor)) + } +} + +private fun ObjCExportHeaderGenerator.scheduleClassToBeGenerated(classDescriptor: ClassDescriptor) { + if (classDescriptor !in generatedClasses) { + extraClassesToTranslate += classDescriptor } } private fun ObjCExportHeaderGenerator.mapType( kotlinType: KotlinType, - typeBridge: ReturnableTypeBridge + typeBridge: TypeBridge ): ObjCType = when (typeBridge) { - VoidBridge -> ObjCVoidType ReferenceBridge -> mapReferenceType(kotlinType) is ValueTypeBridge -> { val cName = when (typeBridge.objCValueType) { @@ -707,7 +825,6 @@ private fun ObjCExportHeaderGenerator.mapType( // TODO: consider other namings. ObjCPrimitiveType(cName) } - HashCodeBridge -> ObjCPrimitiveType("NSUInteger") } private data class Stub(val lines: List) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt index 6ad856dea71..eb0e048bcf9 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt @@ -16,26 +16,29 @@ package org.jetbrains.kotlin.backend.konan.objcexport -import org.jetbrains.kotlin.backend.common.descriptors.explicitParameters +import org.jetbrains.kotlin.backend.common.descriptors.allParameters import org.jetbrains.kotlin.backend.common.descriptors.isSuspend -import org.jetbrains.kotlin.backend.konan.KONAN_FUNCTION_INTERFACES_MAX_PARAMETERS -import org.jetbrains.kotlin.backend.konan.ValueType -import org.jetbrains.kotlin.backend.konan.correspondingValueType +import org.jetbrains.kotlin.backend.konan.* +import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenDescriptors import org.jetbrains.kotlin.backend.konan.descriptors.isArray import org.jetbrains.kotlin.backend.konan.descriptors.isInterface -import org.jetbrains.kotlin.backend.konan.isObjCObjectType import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns -import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.typeUtil.isUnit -internal interface ObjCExportMapper { - fun getCategoryMembersFor(descriptor: ClassDescriptor): List +internal abstract class ObjCExportMapper { + abstract fun getCategoryMembersFor(descriptor: ClassDescriptor): List val maxFunctionTypeParameterCount get() = KONAN_FUNCTION_INTERFACES_MAX_PARAMETERS - fun isSpecialMapped(descriptor: ClassDescriptor): Boolean + abstract fun isSpecialMapped(descriptor: ClassDescriptor): Boolean + + private val methodBridgeCache = mutableMapOf() + + fun bridgeMethod(descriptor: FunctionDescriptor) = methodBridgeCache.getOrPut(descriptor) { + bridgeMethodImpl(descriptor) + } } private fun ObjCExportMapper.isRepresentedAsObjCInterface(descriptor: ClassDescriptor): Boolean = @@ -98,35 +101,111 @@ internal tailrec fun KotlinType.getErasedTypeClass(): ClassDescriptor = internal fun ObjCExportMapper.isTopLevel(descriptor: CallableMemberDescriptor): Boolean = descriptor.containingDeclaration !is ClassDescriptor && this.getClassIfCategory(descriptor) == null -internal fun ObjCExportMapper.objCValueParameters(method: FunctionDescriptor): List = - when { - method is ConstructorDescriptor -> - listOfNotNull(method.dispatchReceiverParameter) + method.valueParameters - - getClassIfCategory(method) == null -> - listOfNotNull(method.extensionReceiverParameter) + method.valueParameters - - else -> method.valueParameters - } - internal fun ObjCExportMapper.isObjCProperty(property: PropertyDescriptor): Boolean = - this.objCValueParameters(property.getter!!).isEmpty() && // Which is false e.g. if it has two receivers. - !this.isTopLevel(property) // Because Objective-C has no class (e.g. static) properties. + property.extensionReceiverParameter == null || getClassIfCategory(property) != null + +internal fun ObjCExportMapper.doesThrow(method: FunctionDescriptor): Boolean = method.allOverriddenDescriptors.any { + it.overriddenDescriptors.isEmpty() && it.annotations.hasAnnotation(KonanBuiltIns.FqNames.throws) +} // TODO: generalize type bridges to support such things as selectors, ignored class method receivers etc. -internal sealed class ReturnableTypeBridge -internal object VoidBridge : ReturnableTypeBridge() -internal sealed class TypeBridge : ReturnableTypeBridge() +internal sealed class TypeBridge internal object ReferenceBridge : TypeBridge() internal data class ValueTypeBridge(val objCValueType: ObjCValueType) : TypeBridge() -internal object HashCodeBridge : TypeBridge() + +internal sealed class MethodBridgeParameter + +internal sealed class MethodBridgeReceiver : MethodBridgeParameter() { + object Static : MethodBridgeReceiver() + object Factory : MethodBridgeReceiver() + object Instance : MethodBridgeReceiver() +} + +internal object MethodBridgeSelector : MethodBridgeParameter() + +internal sealed class MethodBridgeValueParameter : MethodBridgeParameter() { + data class Mapped(val bridge: TypeBridge) : MethodBridgeValueParameter() + object ErrorOutParameter : MethodBridgeValueParameter() + data class KotlinResultOutParameter(val bridge: TypeBridge) : MethodBridgeValueParameter() +} internal data class MethodBridge( - val returnBridge: ReturnableTypeBridge, - val paramBridges: List, - val isKotlinTopLevel: Boolean = false -) + val returnBridge: ReturnValue, + val receiver: MethodBridgeReceiver, + val valueParameters: List +) { + + sealed class ReturnValue { + object Void : ReturnValue() + object HashCode : ReturnValue() + data class Mapped(val bridge: TypeBridge) : ReturnValue() + sealed class Instance : ReturnValue() { + object InitResult : Instance() + object FactoryResult : Instance() + } + + sealed class WithError : ReturnValue() { + object Success : WithError() + data class RefOrNull(val successBridge: ReturnValue) : WithError() + } + } + + val paramBridges: List = + listOf(receiver) + MethodBridgeSelector + valueParameters + + // TODO: it is not exactly true in potential future cases. + val isInstance: Boolean get() = when (receiver) { + MethodBridgeReceiver.Static, + MethodBridgeReceiver.Factory -> false + + MethodBridgeReceiver.Instance -> true + } +} + +internal fun MethodBridge.valueParametersAssociated( + descriptor: FunctionDescriptor +): List> { + val kotlinParameters = descriptor.allParameters.iterator() + val skipFirstKotlinParameter = when (this.receiver) { + MethodBridgeReceiver.Static -> false + MethodBridgeReceiver.Factory, MethodBridgeReceiver.Instance -> true + } + if (skipFirstKotlinParameter) { + kotlinParameters.next() + } + + return this.valueParameters.map { + when (it) { + is MethodBridgeValueParameter.Mapped -> it to kotlinParameters.next() + + is MethodBridgeValueParameter.ErrorOutParameter, + is MethodBridgeValueParameter.KotlinResultOutParameter -> it to null + } + }.also { assert(!kotlinParameters.hasNext()) } +} + +internal fun MethodBridge.parametersAssociated( + descriptor: FunctionDescriptor +): List> { + val kotlinParameters = descriptor.allParameters.iterator() + + return this.paramBridges.map { + when (it) { + is MethodBridgeValueParameter.Mapped, MethodBridgeReceiver.Instance -> + it to kotlinParameters.next() + + MethodBridgeReceiver.Static, MethodBridgeSelector, MethodBridgeValueParameter.ErrorOutParameter, + is MethodBridgeValueParameter.KotlinResultOutParameter -> + it to null + + MethodBridgeReceiver.Factory -> { + kotlinParameters.next() + it to null + } + } + }.also { assert(!kotlinParameters.hasNext()) } +} private fun ObjCExportMapper.bridgeType(kotlinType: KotlinType): TypeBridge { val valueType = kotlinType.correspondingValueType @@ -138,49 +217,86 @@ private fun ObjCExportMapper.bridgeType(kotlinType: KotlinType): TypeBridge { return ValueTypeBridge(objCValueType) } -private fun ObjCExportMapper.bridgeReturnType(kotlinType: KotlinType): ReturnableTypeBridge = if (kotlinType.isUnit()) { - VoidBridge -} else { - bridgeType(kotlinType) -} +private fun ObjCExportMapper.bridgeParameter(parameter: ParameterDescriptor): MethodBridgeValueParameter = + MethodBridgeValueParameter.Mapped(bridgeType(parameter.type)) -internal fun ObjCExportMapper.bridgeReturnType(descriptor: FunctionDescriptor): ReturnableTypeBridge { +private fun ObjCExportMapper.bridgeReturnType( + descriptor: FunctionDescriptor, + valueParameters: MutableList, + convertExceptionsToErrors: Boolean +): MethodBridge.ReturnValue { val returnType = descriptor.returnType!! return when { - descriptor.containingDeclaration == descriptor.builtIns.any && descriptor.name.asString() == "hashCode" -> - HashCodeBridge + descriptor is ConstructorDescriptor -> if (descriptor.constructedClass.isArray) { + MethodBridge.ReturnValue.Instance.FactoryResult + } else { + MethodBridge.ReturnValue.Instance.InitResult + }.let { + if (convertExceptionsToErrors) MethodBridge.ReturnValue.WithError.RefOrNull(it) else it + } - descriptor is PropertyGetterDescriptor -> bridgePropertyType(descriptor.correspondingProperty) + descriptor.containingDeclaration == descriptor.builtIns.any && descriptor.name.asString() == "hashCode" -> { + assert(!convertExceptionsToErrors) + MethodBridge.ReturnValue.HashCode + } - else -> bridgeReturnType(returnType) + descriptor is PropertyGetterDescriptor -> { + assert(!convertExceptionsToErrors) + MethodBridge.ReturnValue.Mapped(bridgePropertyType(descriptor.correspondingProperty)) + } + + returnType.isUnit() -> if (convertExceptionsToErrors) { + MethodBridge.ReturnValue.WithError.Success + } else { + MethodBridge.ReturnValue.Void + } + + else -> { + val returnTypeBridge = bridgeType(returnType) + if (convertExceptionsToErrors) { + if (returnTypeBridge is ReferenceBridge && !TypeUtils.isNullableType(returnType)) { + MethodBridge.ReturnValue.WithError.RefOrNull(MethodBridge.ReturnValue.Mapped(returnTypeBridge)) + } else { + valueParameters += MethodBridgeValueParameter.KotlinResultOutParameter(returnTypeBridge) + MethodBridge.ReturnValue.WithError.Success + } + } else { + MethodBridge.ReturnValue.Mapped(returnTypeBridge) + } + } } } -internal fun ObjCExportMapper.bridgeMethod(descriptor: FunctionDescriptor): MethodBridge { +private fun ObjCExportMapper.bridgeMethodImpl(descriptor: FunctionDescriptor): MethodBridge { assert(isBaseMethod(descriptor)) - val returnBridge = bridgeReturnType(descriptor) + val convertExceptionsToErrors = this.doesThrow(descriptor) - val paramBridges = mutableListOf() - - if (descriptor is ConstructorDescriptor) { - if (descriptor.constructedClass.isArray) { - // Generated as class factory method. - paramBridges += ReferenceBridge // Receiver of class method. - } else { - // Generated as Objective-C instance init method. - paramBridges += ReferenceBridge // Receiver of init method. - } - } + val kotlinParameters = descriptor.allParameters.iterator() val isTopLevel = isTopLevel(descriptor) - if (isTopLevel) { - paramBridges += ReferenceBridge + + val receiver = if (descriptor is ConstructorDescriptor && descriptor.constructedClass.isArray) { + kotlinParameters.next() + MethodBridgeReceiver.Factory + } else if (isTopLevel) { + MethodBridgeReceiver.Static + } else { + kotlinParameters.next() + MethodBridgeReceiver.Instance } - descriptor.explicitParameters.mapTo(paramBridges) { bridgeType(it.type) } + val valueParameters = mutableListOf() + kotlinParameters.forEach { + valueParameters += bridgeParameter(it) + } - return MethodBridge(returnBridge, paramBridges, isKotlinTopLevel = isTopLevel) + val returnBridge = bridgeReturnType(descriptor, valueParameters, convertExceptionsToErrors) + if (convertExceptionsToErrors) { + valueParameters += MethodBridgeValueParameter.ErrorOutParameter + } + + return MethodBridge(returnBridge, receiver, valueParameters) } internal fun ObjCExportMapper.bridgePropertyType(descriptor: PropertyDescriptor): TypeBridge { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt index 3d5a3683acc..fa298a30cad 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt @@ -130,23 +130,33 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe fun getSelector(method: FunctionDescriptor): String = methodSelectors.getOrPut(method) { assert(mapper.isBaseMethod(method)) - val parameters = mapper.objCValueParameters(method) + val parameters = mapper.bridgeMethod(method).valueParametersAssociated(method) StringBuilder().apply { append(method.getMangledName(forSwift = false)) - parameters.forEachIndexed { index, it -> - val name = when { - it is ReceiverParameterDescriptor -> "" - method is PropertySetterDescriptor -> when (parameters.size) { - 1 -> "" - else -> "value" + parameters.forEachIndexed { index, (bridge, it) -> + val name = when (bridge) { + is MethodBridgeValueParameter.Mapped -> when { + it is ReceiverParameterDescriptor -> "" + method is PropertySetterDescriptor -> when (parameters.size) { + 1 -> "" + else -> "value" + } + else -> it!!.name.asString() } - else -> it.name.asString() + MethodBridgeValueParameter.ErrorOutParameter -> "error" + is MethodBridgeValueParameter.KotlinResultOutParameter -> "result" } if (index == 0) { - if (method is ConstructorDescriptor) append("With") + append(when { + bridge is MethodBridgeValueParameter.ErrorOutParameter || + bridge is MethodBridgeValueParameter.KotlinResultOutParameter -> "AndReturn" + + method is ConstructorDescriptor -> "With" + else -> "" + }) append(name.capitalize()) } else { append(name) @@ -168,21 +178,26 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe fun getSwiftName(method: FunctionDescriptor): String = methodSwiftNames.getOrPut(method) { assert(mapper.isBaseMethod(method)) - val parameters = mapper.objCValueParameters(method) + val parameters = mapper.bridgeMethod(method).valueParametersAssociated(method) StringBuilder().apply { append(method.getMangledName(forSwift = true)) append("(") - parameters.forEach { - val label = when { - it is ReceiverParameterDescriptor -> "_" - method is PropertySetterDescriptor -> when (parameters.size) { - 1 -> "_" - else -> "value" + parameters@ for ((bridge, it) in parameters) { + val label = when (bridge) { + is MethodBridgeValueParameter.Mapped -> when { + it is ReceiverParameterDescriptor -> "_" + method is PropertySetterDescriptor -> when (parameters.size) { + 1 -> "_" + else -> "value" + } + else -> it!!.name.asString() } - else -> it.name.asString() + MethodBridgeValueParameter.ErrorOutParameter -> continue@parameters + is MethodBridgeValueParameter.KotlinResultOutParameter -> "result" } + append(label) append(":") } @@ -402,8 +417,8 @@ private fun ObjCExportMapper.canHaveSameSelector(first: FunctionDescriptor, seco // Otherwise both are Kotlin member methods should merge in any common subclass. - // Taking into account the conditions above, check if methods have the same ABI: - return bridgeReturnType(first) == bridgeReturnType(second) + // Check if methods have the same bridge (and thus the same ABI): + return bridgeMethod(first) == bridgeMethod(second) } private fun ObjCExportMapper.canHaveSameName(first: PropertyDescriptor, second: PropertyDescriptor): Boolean { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt index e26ef5942ba..74316933f59 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.ir.util +import org.jetbrains.kotlin.backend.common.CommonBackendContext import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations @@ -346,3 +347,28 @@ object CheckDeclarationParentsVisitor : IrElementVisitor parent + is IrDeclaration -> parent.getContainingFile() + else -> null + } +} + +fun CommonBackendContext.report(declaration: IrDeclaration, message: String, isError: Boolean) { + val irFile = declaration.getContainingFile() + this.report( + declaration, + irFile, + if (irFile != null) { + message + } else { + val renderer = org.jetbrains.kotlin.renderer.DescriptorRenderer.COMPACT_WITH_SHORT_TYPES + "$message\n${renderer.render(declaration.descriptor)}" + }, + isError + ) +} diff --git a/runtime/src/main/cpp/ObjCExport.h b/runtime/src/main/cpp/ObjCExport.h index 614fcab268f..9adc6bd0aeb 100644 --- a/runtime/src/main/cpp/ObjCExport.h +++ b/runtime/src/main/cpp/ObjCExport.h @@ -31,6 +31,9 @@ extern "C" OBJ_GETTER(Kotlin_ObjCExport_refFromObjC, id obj); -(KRef)toKotlin:(KRef*)OBJ_RESULT; @end; +extern "C" id Kotlin_Interop_CreateNSStringFromKString(KRef str); +extern "C" OBJ_GETTER(Kotlin_Interop_CreateKStringFromNSString, NSString* str); + #endif // KONAN_OBJC_INTEROP #endif // RUNTIME_OBJCEXPORT_H \ No newline at end of file diff --git a/runtime/src/main/cpp/ObjCExport.mm b/runtime/src/main/cpp/ObjCExport.mm index 1073a78492e..cb236b242b0 100644 --- a/runtime/src/main/cpp/ObjCExport.mm +++ b/runtime/src/main/cpp/ObjCExport.mm @@ -23,14 +23,18 @@ #import #import #import +#import #import +#import #import +#import #import #import "ObjCExport.h" #import "MemoryPrivate.hpp" #import "Runtime.h" #import "Utils.h" +#import "Exceptions.h" struct ObjCToKotlinMethodAdapter { const char* selector; @@ -329,8 +333,6 @@ extern "C" id objc_retainAutoreleaseReturnValue(id self); @interface NSString (NSStringToKotlin) @end; -extern "C" OBJ_GETTER(Kotlin_Interop_CreateKStringFromNSString, NSString* str); - @implementation NSString (NSStringToKotlin) -(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { RETURN_RESULT_OF(Kotlin_Interop_CreateKStringFromNSString, self); @@ -523,8 +525,6 @@ static id convertKotlinObject(ObjHeader* obj) { return [clazz createWrapper:obj]; } -extern "C" id Kotlin_Interop_CreateNSStringFromKString(KRef str); - static convertReferenceToObjC findConverterFromInterfaces(const TypeInfo* typeInfo) { const TypeInfo* foundTypeInfo = nullptr; diff --git a/runtime/src/main/cpp/ObjCExportCollections.mm b/runtime/src/main/cpp/ObjCExportCollections.mm index 9ef0514bd5f..344a1513947 100644 --- a/runtime/src/main/cpp/ObjCExportCollections.mm +++ b/runtime/src/main/cpp/ObjCExportCollections.mm @@ -1,3 +1,19 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #import "Memory.h" #import "Types.h" diff --git a/runtime/src/main/cpp/ObjCExportErrors.mm b/runtime/src/main/cpp/ObjCExportErrors.mm new file mode 100644 index 00000000000..94759ed8ac1 --- /dev/null +++ b/runtime/src/main/cpp/ObjCExportErrors.mm @@ -0,0 +1,104 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if KONAN_OBJC_INTEROP + +#import +#import +#import + +#import "Exceptions.h" +#import "ObjCExport.h" +#import "Runtime.h" +#import "Utils.h" + +extern "C" OBJ_GETTER(Kotlin_Throwable_getMessage, KRef throwable); +extern "C" OBJ_GETTER(Kotlin_ObjCExport_getWrappedError, KRef throwable); +extern "C" void Kotlin_ObjCExport_abortIfUnchecked(KRef exception); + +static char kotlinExceptionOriginChar; + +extern "C" void Kotlin_ObjCExport_RethrowExceptionAsNSError(KRef exception, id* outError) { + Kotlin_ObjCExport_abortIfUnchecked(exception); + + if (outError == nullptr) { + return; + } + + ObjHolder errorHolder, messageHolder; + + KRef error = Kotlin_ObjCExport_getWrappedError(exception, errorHolder.slot()); + if (error != nullptr) { + *outError = Kotlin_ObjCExport_refToObjC(error); + return; + } + + NSMutableDictionary* userInfo = [[NSMutableDictionary new] autorelease]; + userInfo[@"KotlinException"] = Kotlin_ObjCExport_refToObjC(exception); + userInfo[@"KotlinExceptionOrigin"] = @(&kotlinExceptionOriginChar); // Support for different Kotlin runtimes loaded. + + KRef message = Kotlin_Throwable_getMessage(exception, messageHolder.slot()); + NSString* description = Kotlin_Interop_CreateNSStringFromKString(message); + if (description != nullptr) { + userInfo[NSLocalizedDescriptionKey] = description; + } + + *outError = [NSError errorWithDomain:@"KotlinException" code:0 userInfo:userInfo]; + return; +} + +extern "C" void Kotlin_ObjCExport_RethrowNSErrorAsExceptionImpl(KRef message, KRef error); + +extern "C" void Kotlin_ObjCExport_RethrowNSErrorAsException(id error) { + NSString* description; + + NSError* e = (NSError*) error; + if (e != nullptr) { + auto userInfo = e.userInfo; + if (userInfo != nullptr) { + id kotlinException = userInfo[@"KotlinException"]; + id kotlinExceptionOrigin = userInfo[@"KotlinExceptionOrigin"]; + if (kotlinException != nullptr && + kotlinExceptionOrigin != nullptr && [kotlinExceptionOrigin isEqual:@(&kotlinExceptionOriginChar)] + ) { + ObjHolder kotlinExceptionHolder; + ThrowException(Kotlin_ObjCExport_refFromObjC(kotlinException, kotlinExceptionHolder.slot())); + return; + } + } + description = e.localizedDescription; + } else { + description = nullptr; + } + + ObjHolder messageHolder, errorHolder; + 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); +} + +@interface NSError (NSErrorKotlinException) +@end; + +@implementation NSError (NSErrorKotlinException) +-(id)kotlinException { + auto userInfo = self.userInfo; + return userInfo == nullptr ? nullptr : userInfo[@"KotlinException"]; +} +@end; + +#endif diff --git a/runtime/src/main/kotlin/konan/Annotations.kt b/runtime/src/main/kotlin/konan/Annotations.kt index d142ccea2c6..99167412390 100644 --- a/runtime/src/main/kotlin/konan/Annotations.kt +++ b/runtime/src/main/kotlin/konan/Annotations.kt @@ -16,6 +16,8 @@ package konan +import kotlin.reflect.KClass + /** * Forces the compiler to use specified symbol name for the target `external` function. * @@ -43,6 +45,22 @@ annotation class VolatileLambda */ public annotation class Used +// TODO: merge with [kotlin.jvm.Throws] +/** + * This annotation indicates what exceptions should be declared by a function when compiled to a platform method. + * + * When compiling to Objective-C/Swift framework, methods having or inheriting this annotation are represented as + * `NSError*`-producing methods in Objective-C and as `throws` methods in Swift. + * When such a method called through framework API throws an exception, it is either propagated as + * `NSError` or considered unhandled (if exception `is` [kotlin.Error] or [kotlin.RuntimeException]). + * In any case exception is not checked to be instance of one of the [exceptionClasses]. + * + * @property exceptionClasses the list of checked exception classes that may be thrown by the function. + */ +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.SOURCE) +public annotation class Throws(vararg val exceptionClasses: KClass) + /** * Need to be fixed because of reification support. */ diff --git a/runtime/src/main/kotlin/konan/internal/ObjCExportUtils.kt b/runtime/src/main/kotlin/konan/internal/ObjCExportUtils.kt index 81f9ca999ac..ee5fc28b58b 100644 --- a/runtime/src/main/kotlin/konan/internal/ObjCExportUtils.kt +++ b/runtime/src/main/kotlin/konan/internal/ObjCExportUtils.kt @@ -268,3 +268,54 @@ internal class NSEnumeratorAsKIterator : AbstractIterator() { @ExportForCppRuntime private fun Kotlin_NSEnumeratorAsKIterator_create() = NSEnumeratorAsKIterator() @ExportForCppRuntime private fun Kotlin_NSSetAsKSet_create() = NSSetAsKSet() @ExportForCppRuntime private fun Kotlin_NSDictionaryAsKMap_create() = NSDictionaryAsKMap() + +@ExportForCppRuntime private fun Kotlin_ObjCExport_RethrowNSErrorAsExceptionImpl( + message: String?, + error: Any +) { + throw ObjCErrorException(message, error) +} + +class ObjCErrorException( + message: String?, + internal val error: Any +) : Exception(message) { + override fun toString(): String = "NSError-based exception: $message" +} + +private val uncheckedExceptionMessage: String + get() = "Instances of kotlin.Error, kotlin.RuntimeException and subclasses " + + "aren't propagated from Kotlin to Objective-C/Swift." + +private fun Throwable.isUncheckedException(): Boolean = this is kotlin.Error || this is kotlin.RuntimeException + +@ExportForCppRuntime +private fun Kotlin_ObjCExport_abortIfUnchecked(exception: Throwable) { + if (exception.isUncheckedException()) { + println(uncheckedExceptionMessage) + konan.internal.ReportUnhandledException(exception) + kotlin.system.exitProcess(1) + } +} + +@ExportForCompiler +private fun trapOnUndeclaredException(exception: Throwable) { + if (exception.isUncheckedException()) { + println(uncheckedExceptionMessage) + println("Other exceptions can be propagated as NSError if method has or inherits @Throws annotation.") + } else { + println("Exceptions are propagated from Kotlin to Objective-C/Swift as NSError " + + "only if method has or inherits @Throws annotation") + } + + konan.internal.ReportUnhandledException(exception) + kotlin.system.exitProcess(1) + +} + +@ExportForCppRuntime +private fun Kotlin_Throwable_getMessage(throwable: Throwable): String? = throwable.message + +@ExportForCppRuntime +private fun Kotlin_ObjCExport_getWrappedError(throwable: Throwable): Any? = + (throwable as? ObjCErrorException)?.error