Translate Kotlin @Throws to Swift 'throws' when producing framework

Methods having or inheriting `@Throws` annotation are represented as
`NSError*`-producing methods in Objective-C and as `throws` methods in Swift.
This commit is contained in:
Svyatoslav Scherbina
2018-03-19 15:35:39 +03:00
committed by SvyatoslavScherbina
parent db4a1f937b
commit bbefb4bab3
17 changed files with 981 additions and 323 deletions
@@ -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 }
@@ -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 {
@@ -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)
@@ -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).
@@ -952,30 +952,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
*/
private fun genLandingpad() {
with(functionGenerationContext) {
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 = functionGenerationContext.loadSlot(exceptionPtrPtr, true, "exception")
// __cxa_end_catch performs some C++ cleanup, including calling `KotlinException` class destructor.
val endCatch = context.llvm.cxaEndCatchFunction
call(endCatch, listOf())
val exceptionPtr = catchKotlinException()
jumpToHandler(exceptionPtr)
}
}
@@ -45,6 +45,12 @@ internal open class ObjCCodeGenerator(val codegen: CodeGenerator) {
)
)
val objcRelease = context.llvm.externalFunction(
"objc_release",
functionType(voidType, false, int8TypePtr),
context.stdlibModule.llvmSymbolOrigin
)
// TODO: this doesn't support stret.
fun msgSender(functionType: LLVMTypeRef): LLVMValueRef =
objcMsgSend.bitcast(pointerType(functionType)).llvm
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isUnit
import org.jetbrains.kotlin.util.OperatorNameConventions
internal class ObjCExportCodeGenerator(
@@ -119,10 +120,6 @@ internal class ObjCExportCodeGenerator(
): LLVMValueRef = when (typeBridge) {
is ReferenceBridge -> 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<LLVMValueRef>,
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<LLVMValueRef>()
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<CallableMemberDescriptor>.toMethods(): List<FunctionDescriptor>
}
private fun objCFunctionType(methodBridge: MethodBridge): LLVMTypeRef {
val paramTypes = mutableListOf<LLVMTypeRef>()
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
@@ -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<String>()
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<String>()
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<FunctionDescriptor>()
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<String> = mutableListOf<String>().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<ObjCReferenceType> = emptyList()
) : ObjCReferenceType(kotlinType) {
val typeArguments: List<ObjCNonNullReferenceType> = 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>
) : ObjCReferenceType(kotlinType) {
val returnType: ObjCReferenceType, val parameterTypes: List<ObjCReferenceType>
) : 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* <SomeInterface>`
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<String>)
@@ -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<CallableMemberDescriptor>
internal abstract class ObjCExportMapper {
abstract fun getCategoryMembersFor(descriptor: ClassDescriptor): List<CallableMemberDescriptor>
val maxFunctionTypeParameterCount get() = KONAN_FUNCTION_INTERFACES_MAX_PARAMETERS
fun isSpecialMapped(descriptor: ClassDescriptor): Boolean
abstract fun isSpecialMapped(descriptor: ClassDescriptor): Boolean
private val methodBridgeCache = mutableMapOf<FunctionDescriptor, MethodBridge>()
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<ParameterDescriptor> =
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<TypeBridge>,
val isKotlinTopLevel: Boolean = false
)
val returnBridge: ReturnValue,
val receiver: MethodBridgeReceiver,
val valueParameters: List<MethodBridgeValueParameter>
) {
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<MethodBridgeParameter> =
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<Pair<MethodBridgeValueParameter, ParameterDescriptor?>> {
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<Pair<MethodBridgeParameter, ParameterDescriptor?>> {
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<MethodBridgeValueParameter>,
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<TypeBridge>()
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<MethodBridgeValueParameter>()
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 {
@@ -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 {
@@ -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<Unit, IrDeclarationPare
}
}
}
tailrec fun IrDeclaration.getContainingFile(): IrFile? {
val parent = this.parent
return when (parent) {
is IrFile -> 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
)
}
+3
View File
@@ -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
+4 -4
View File
@@ -23,14 +23,18 @@
#import <Foundation/NSValue.h>
#import <Foundation/NSString.h>
#import <Foundation/NSMethodSignature.h>
#import <Foundation/NSError.h>
#import <Foundation/NSException.h>
#import <Foundation/NSDictionary.h>
#import <objc/runtime.h>
#import <objc/objc-exception.h>
#import <dispatch/dispatch.h>
#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) <ConvertibleToKotlin>
@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;
@@ -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"
+104
View File
@@ -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 <Foundation/NSDictionary.h>
#import <Foundation/NSError.h>
#import <Foundation/NSString.h>
#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<NSErrorUserInfoKey, id>* 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
@@ -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<out Throwable>)
/**
* Need to be fixed because of reification support.
*/
@@ -268,3 +268,54 @@ internal class NSEnumeratorAsKIterator : AbstractIterator<Any?>() {
@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