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:
committed by
SvyatoslavScherbina
parent
db4a1f937b
commit
bbefb4bab3
+2
@@ -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 }
|
||||
|
||||
+7
@@ -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 {
|
||||
|
||||
+57
@@ -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)
|
||||
|
||||
+2
@@ -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).
|
||||
|
||||
+1
-24
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -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
|
||||
|
||||
+284
-143
@@ -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
|
||||
|
||||
+193
-76
@@ -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>)
|
||||
|
||||
+173
-57
@@ -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 {
|
||||
|
||||
+34
-19
@@ -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 {
|
||||
|
||||
+26
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user