From 2b8dcfc53354e48c574e0d3dccae87fca28b0b0b Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Mon, 4 Mar 2019 10:46:29 +0300 Subject: [PATCH] Refactor ObjCExportHeaderGenerator to allow per declaration translation --- .../objcexport/ObjCExportCodeGenerator.kt | 24 +- .../konan/objcexport/CustomTypeMapper.kt | 109 ++- .../backend/konan/objcexport/ObjCExport.kt | 51 +- .../objcexport/ObjCExportHeaderGenerator.kt | 883 +++++++++--------- .../ObjCExportHeaderGeneratorImpl.kt | 13 +- .../konan/objcexport/ObjCExportMapper.kt | 18 +- .../konan/objcexport/ObjCExportNamer.kt | 77 +- 7 files changed, 639 insertions(+), 536 deletions(-) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt index 6af5e1b2c21..ebcba3f3285 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -33,7 +33,7 @@ internal fun TypeBridge.makeNothing() = when (this) { internal class ObjCExportCodeGenerator( codegen: CodeGenerator, - val namer: ObjCExportNamerImpl, + val namer: ObjCExportNamer, val mapper: ObjCExportMapper ) : ObjCCodeGenerator(codegen) { @@ -155,14 +155,15 @@ internal class ObjCExportCodeGenerator( return callFromBridge(conversion.owner.llvmFunction, listOf(value), resultLifetime) } - internal fun emitRtti( + private val objCTypeAdapters = mutableListOf() + + internal fun generate( generatedClasses: Collection, + categoryMembers: Map>, topLevel: Map> ) { - val objCTypeAdapters = mutableListOf() - generatedClasses.forEach { - objCTypeAdapters += createTypeAdapter(it) + objCTypeAdapters += createTypeAdapter(it, categoryMembers[it].orEmpty()) if (!it.isInterface) { val className = namer.getClassOrProtocolName(it).binaryName @@ -180,7 +181,9 @@ internal class ObjCExportCodeGenerator( val name = namer.getFileClassName(sourceFile).binaryName dataGenerator.emitEmptyClass(name, namer.kotlinAnyName.binaryName) } + } + internal fun emitRtti() { NSNumberKind.values().mapNotNull { it.mappedKotlinClassId }.forEach { dataGenerator.exportClass("Kotlin${it.shortClassName}") } @@ -189,7 +192,7 @@ internal class ObjCExportCodeGenerator( emitSpecialClassesConvertions() - objCTypeAdapters += createTypeAdapter(context.builtIns.any) + objCTypeAdapters += createTypeAdapter(context.builtIns.any, categoryMembers = emptyList()) val placedClassAdapters = mutableMapOf() val placedInterfaceAdapters = mutableMapOf() @@ -397,7 +400,7 @@ private fun ObjCExportCodeGenerator.emitBoxConverter( private fun ObjCExportCodeGenerator.emitFunctionConverters() { val generator = BlockAdapterToFunctionGenerator(this) - (0 .. mapper.maxFunctionTypeParameterCount).forEach { numberOfParameters -> + (0 .. ObjCExportMapper.maxFunctionTypeParameterCount).forEach { numberOfParameters -> val converter = generator.run { generateConvertFunctionToBlock(numberOfParameters) } setObjCExportTypeInfo(context.builtIns.getFunction(numberOfParameters), constPointer(converter)) } @@ -421,7 +424,7 @@ private fun ObjCExportCodeGenerator.emitKotlinFunctionAdaptersToBlock() { val ptr = staticData.placeGlobalArray( "", pointerType(runtime.typeInfoType), - (0 .. mapper.maxFunctionTypeParameterCount).map { + (0 .. ObjCExportMapper.maxFunctionTypeParameterCount).map { generateKotlinFunctionAdapterToBlock(it) } ).pointer.getElementPtr(0) @@ -866,7 +869,8 @@ private fun ObjCExportCodeGenerator.createTypeAdapterForFileClass( } private fun ObjCExportCodeGenerator.createTypeAdapter( - descriptor: ClassDescriptor + descriptor: ClassDescriptor, + categoryMembers: List ): ObjCExportCodeGenerator.ObjCTypeAdapter { val adapters = mutableListOf() val classAdapters = mutableListOf() @@ -883,7 +887,7 @@ private fun ObjCExportCodeGenerator.createTypeAdapter( } } - val categoryMethods = mapper.getCategoryMembersFor(descriptor).toMethods() + val categoryMethods = categoryMembers.toMethods() val exposedMethods = descriptor.contributedMethods.filter { mapper.shouldBeExposed(it) } + categoryMethods diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/CustomTypeMapper.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/CustomTypeMapper.kt index 6695d1e4cab..7c62ea48e0d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/CustomTypeMapper.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/CustomTypeMapper.kt @@ -5,58 +5,117 @@ package org.jetbrains.kotlin.backend.konan.objcexport +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType -import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.name.ClassId -import org.jetbrains.kotlin.resolve.descriptorUtil.classId +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils internal interface CustomTypeMapper { val mappedClassId: ClassId - fun mapType(mappedSuperType: KotlinType): ObjCNonNullReferenceType + fun mapType(mappedSuperType: KotlinType, translator: ObjCExportTranslatorImpl): ObjCNonNullReferenceType +} - class Simple( - override val mappedClassId: ClassId, - private val objCClassName: String - ) : CustomTypeMapper { +internal object CustomTypeMappers { + /** + * Custom type mappers. + * + * Don't forget to update [hiddenTypes] after adding new one. + */ + val byClassId: Map = with(KotlinBuiltIns.FQ_NAMES) { + val result = mutableListOf() - override fun mapType(mappedSuperType: KotlinType): ObjCNonNullReferenceType = - ObjCClassType(objCClassName) + result += Collection(list, "NSArray") + result += Collection(mutableList, "NSMutableArray") + result += Collection(set, "NSSet") + result += Collection(mutableSet, { namer.mutableSetName.objCName }) + result += Collection(map, "NSDictionary") + result += Collection(mutableMap, { namer.mutableMapName.objCName }) + + NSNumberKind.values().forEach { + // TODO: NSNumber seem to have different equality semantics. + val classId = it.mappedKotlinClassId + if (classId != null) { + result += Simple(classId, { namer.numberBoxName(classId).objCName }) + } + + } + + result += Simple(ClassId.topLevel(string.toSafe()), "NSString") + + (0..ObjCExportMapper.maxFunctionTypeParameterCount).forEach { + result += Function(it) + } + + result.associateBy { it.mappedClassId } } - class Collection( - private val generator: ObjCExportHeaderGenerator, - mappedClassDescriptor: ClassDescriptor, - private val objCClassName: String + /** + * Types to be "hidden" during mapping, i.e. represented as `id`. + * + * Currently contains super types of classes handled by custom type mappers. + * Note: can be generated programmatically, but requires stdlib in this case. + */ + val hiddenTypes: Set = listOf( + "kotlin.Any", + "kotlin.CharSequence", + "kotlin.Comparable", + "kotlin.Function", + "kotlin.Number", + "kotlin.collections.Collection", + "kotlin.collections.Iterable", + "kotlin.collections.MutableCollection", + "kotlin.collections.MutableIterable" + ).map { ClassId.topLevel(FqName(it)) }.toSet() + + private class Simple( + override val mappedClassId: ClassId, + private val getObjCClassName: ObjCExportTranslatorImpl.() -> String ) : CustomTypeMapper { - override val mappedClassId = mappedClassDescriptor.classId!! + constructor( + mappedClassId: ClassId, + objCClassName: String + ) : this(mappedClassId, { objCClassName }) - override fun mapType(mappedSuperType: KotlinType): ObjCNonNullReferenceType { + override fun mapType(mappedSuperType: KotlinType, translator: ObjCExportTranslatorImpl): ObjCNonNullReferenceType = + ObjCClassType(translator.getObjCClassName()) + } + + private class Collection( + mappedClassFqName: FqName, + private val getObjCClassName: ObjCExportTranslatorImpl.() -> String + ) : CustomTypeMapper { + + constructor( + mappedClassFqName: FqName, + objCClassName: String + ) : this(mappedClassFqName, { objCClassName }) + + override val mappedClassId = ClassId.topLevel(mappedClassFqName) + + override fun mapType(mappedSuperType: KotlinType, translator: ObjCExportTranslatorImpl): 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 } else { - generator.mapReferenceTypeIgnoringNullability(argument) + translator.mapReferenceTypeIgnoringNullability(argument) } } - return ObjCClassType(objCClassName, typeArguments) + return ObjCClassType(translator.getObjCClassName(), typeArguments) } } - class Function( - private val generator: ObjCExportHeaderGenerator, - parameterCount: Int - ) : CustomTypeMapper { - override val mappedClassId: ClassId = generator.builtIns.getFunction(parameterCount).classId!! + private class Function(parameterCount: Int) : CustomTypeMapper { + override val mappedClassId: ClassId = KotlinBuiltIns.getFunctionClassId(parameterCount) - override fun mapType(mappedSuperType: KotlinType): ObjCNonNullReferenceType { + override fun mapType(mappedSuperType: KotlinType, translator: ObjCExportTranslatorImpl): ObjCNonNullReferenceType { val functionType = mappedSuperType val returnType = functionType.getReturnTypeFromFunctionType() @@ -64,8 +123,8 @@ internal interface CustomTypeMapper { functionType.getValueParameterTypesFromFunctionType().map { it.type } return ObjCBlockPointerType( - generator.mapReferenceType(returnType), - parameterTypes.map { generator.mapReferenceType(it) } + translator.mapReferenceType(returnType), + parameterTypes.map { translator.mapReferenceType(it) } ) } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt index 79d254dd873..73e66535f65 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt @@ -6,13 +6,11 @@ package org.jetbrains.kotlin.backend.konan.objcexport import org.jetbrains.kotlin.backend.konan.descriptors.getPackageFragments +import org.jetbrains.kotlin.backend.konan.getExportedDependencies import org.jetbrains.kotlin.backend.konan.isNativeBinary import org.jetbrains.kotlin.backend.konan.llvm.CodeGenerator import org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGenerator -import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor -import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor -import org.jetbrains.kotlin.descriptors.SourceFile import org.jetbrains.kotlin.konan.file.File import org.jetbrains.kotlin.konan.target.CompilerOutputKind import org.jetbrains.kotlin.konan.target.KonanTarget @@ -32,36 +30,33 @@ internal class ObjCExport(val codegen: CodeGenerator) { if (!context.config.produce.isNativeBinary) return // TODO: emit RTTI to the same modules as classes belong to. - val objCCodeGenerator: ObjCExportCodeGenerator - val generatedClasses: Set - val topLevelDeclarations: Map> + val produceFramework = context.config.produce == CompilerOutputKind.FRAMEWORK - if (context.config.produce == CompilerOutputKind.FRAMEWORK) { - val headerGenerator = ObjCExportHeaderGeneratorImpl(context) + val mapper = ObjCExportMapper() + val exportedDependencies = if (produceFramework) context.getExportedDependencies() else emptyList() + val moduleDescriptors = listOf(context.moduleDescriptor) + exportedDependencies + val namer = ObjCExportNamerImpl( + moduleDescriptors.toSet(), + context.moduleDescriptor.builtIns, + mapper, + context.moduleDescriptor.namePrefix, + local = false + ) + + val objCCodeGenerator = ObjCExportCodeGenerator(codegen, namer, mapper) + + if (produceFramework) { + val headerGenerator = ObjCExportHeaderGeneratorImpl(context, moduleDescriptors, mapper, namer) produceFrameworkSpecific(headerGenerator) - generatedClasses = headerGenerator.generatedClasses - topLevelDeclarations = headerGenerator.topLevel - objCCodeGenerator = ObjCExportCodeGenerator(codegen, headerGenerator.namer, headerGenerator.mapper) - } else { - // TODO: refactor ObjCExport* to handle this case on a general basis. - val mapper = object : ObjCExportMapper() { - override fun getCategoryMembersFor(descriptor: ClassDescriptor): List = - emptyList() - - override fun isSpecialMapped(descriptor: ClassDescriptor): Boolean = - error("shouldn't reach here") - - } - - val namer = ObjCExportNamerImpl(emptySet(), context.builtIns, mapper, context.moduleDescriptor.namePrefix) - objCCodeGenerator = ObjCExportCodeGenerator(codegen, namer, mapper) - - generatedClasses = emptySet() - topLevelDeclarations = emptyMap() + objCCodeGenerator.generate( + generatedClasses = headerGenerator.generatedClasses, + categoryMembers = headerGenerator.extensions, + topLevel = headerGenerator.topLevel + ) } - objCCodeGenerator.emitRtti(generatedClasses = generatedClasses, topLevel = topLevelDeclarations) + objCCodeGenerator.emitRtti() } private fun produceFrameworkSpecific(headerGenerator: ObjCExportHeaderGenerator) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt index 58eddb4f725..67a3a5485b5 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -11,7 +11,6 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.ClassId -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 @@ -19,332 +18,75 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.* import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils -import org.jetbrains.kotlin.types.isNullable import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.utils.addIfNotNull -abstract class ObjCExportHeaderGenerator( - val moduleDescriptors: List, +interface ObjCExportTranslator { + fun translateFile(file: SourceFile, declarations: List): ObjCInterface + fun translateClass(descriptor: ClassDescriptor): ObjCInterface + fun translateInterface(descriptor: ClassDescriptor): ObjCProtocol + fun translateExtensions(classDescriptor: ClassDescriptor, declarations: List): ObjCInterface +} + +interface ObjCExportWarningCollector { + fun reportWarning(text: String) + fun reportWarning(method: FunctionDescriptor, text: String) + + object SILENT : ObjCExportWarningCollector { + override fun reportWarning(text: String) {} + override fun reportWarning(method: FunctionDescriptor, text: String) {} + } +} + +internal class ObjCExportTranslatorImpl( + private val generator: ObjCExportHeaderGenerator?, val builtIns: KotlinBuiltIns, - topLevelNamePrefix: String -) { - - constructor( - moduleDescriptor: ModuleDescriptor, - builtIns: KotlinBuiltIns, - topLevelNamePrefix: String = moduleDescriptor.namePrefix - ) : this(moduleDescriptor, emptyList(), builtIns, topLevelNamePrefix) - - constructor( - moduleDescriptor: ModuleDescriptor, - exportedDependencies: List, - builtIns: KotlinBuiltIns, - topLevelNamePrefix: String = moduleDescriptor.namePrefix - ) : this(listOf(moduleDescriptor) + exportedDependencies, builtIns, topLevelNamePrefix) - - internal val mapper: ObjCExportMapper = object : ObjCExportMapper() { - override fun getCategoryMembersFor(descriptor: ClassDescriptor) = - extensions[descriptor].orEmpty() - - override fun isSpecialMapped(descriptor: ClassDescriptor): Boolean { - // TODO: this method duplicates some of the [mapReferenceType] logic. - return descriptor == builtIns.any || - descriptor.getAllSuperClassifiers().any { it.classId in customTypeMappers } - } - } - - internal val namer = ObjCExportNamerImpl(moduleDescriptors.toSet(), builtIns, mapper, topLevelNamePrefix) - - internal val generatedClasses = mutableSetOf() - internal val topLevel = mutableMapOf>() - - /** - * Custom type mappers. - * - * Don't forget to update [hiddenTypes] after adding new one. - */ - private val customTypeMappers: Map = with(builtIns) { - val result = mutableListOf() - - val generator = this@ObjCExportHeaderGenerator - - result += CustomTypeMapper.Collection(generator, list, "NSArray") - result += CustomTypeMapper.Collection(generator, mutableList, "NSMutableArray") - result += CustomTypeMapper.Collection(generator, set, "NSSet") - result += CustomTypeMapper.Collection(generator, mutableSet, namer.mutableSetName.objCName) - result += CustomTypeMapper.Collection(generator, map, "NSDictionary") - result += CustomTypeMapper.Collection(generator, mutableMap, namer.mutableMapName.objCName) - - NSNumberKind.values().forEach { - // TODO: NSNumber seem to have different equality semantics. - val classId = it.mappedKotlinClassId - if (classId != null) { - result += CustomTypeMapper.Simple(classId, namer.numberBoxName(classId).objCName) - } - - } - - result += CustomTypeMapper.Simple(string.classId!!, "NSString") - - (0..mapper.maxFunctionTypeParameterCount).forEach { - result += CustomTypeMapper.Function(generator, it) - } - - result.associateBy { it.mappedClassId } - } - - /** - * Types to be "hidden" during mapping, i.e. represented as `id`. - * - * Currently contains super types of classes handled by [customTypeMappers]. - * Note: can be generated programmatically, but requires stdlib in this case. - */ - private val hiddenTypes: Set = listOf( - "kotlin.Any", - "kotlin.CharSequence", - "kotlin.Comparable", - "kotlin.Function", - "kotlin.Number", - "kotlin.collections.Collection", - "kotlin.collections.Iterable", - "kotlin.collections.MutableCollection", - "kotlin.collections.MutableIterable" - ).map { ClassId.topLevel(FqName(it)) }.toSet() + val mapper: ObjCExportMapper, + val namer: ObjCExportNamer, + val warningCollector: ObjCExportWarningCollector +) : ObjCExportTranslator { private val kotlinAnyName = namer.kotlinAnyName - private val stubs = mutableListOf>() - private val classOrInterfaceToName = mutableMapOf() - - private val classForwardDeclarations = mutableSetOf() - private val protocolForwardDeclarations = mutableSetOf() - - private val extensions = mutableMapOf>() - private val extraClassesToTranslate = mutableSetOf() - - private fun objCInterface( - name: ObjCExportNamer.ClassOrProtocolName, - generics: List = emptyList(), - descriptor: ClassDescriptor? = null, - superClass: String? = null, - superProtocols: List = emptyList(), - members: List> = emptyList(), - attributes: List = emptyList() - ): ObjCInterface = ObjCInterface( - name.objCName, - generics, - descriptor, - superClass, - superProtocols, - null, - members, - attributes + name.toNameAttributes() + internal fun translateKotlinObjCClassAsUnavailableStub(descriptor: ClassDescriptor): ObjCInterface = objCInterface( + namer.getClassOrProtocolName(descriptor), + descriptor = descriptor, + superClass = "NSObject", + attributes = listOf("unavailable(\"Kotlin subclass of Objective-C class can't be imported\")") ) - private fun objCProtocol( - name: ObjCExportNamer.ClassOrProtocolName, - descriptor: ClassDescriptor, - superProtocols: List, - members: List>, - attributes: List = emptyList() - ): ObjCProtocol = ObjCProtocol( - name.objCName, - descriptor, - superProtocols, - members, - attributes + name.toNameAttributes() - ) - - private fun ObjCExportNamer.ClassOrProtocolName.toNameAttributes(): List = listOfNotNull( - binaryName.takeIf { it != objCName }?.let { objcRuntimeNameAttribute(it) }, - swiftName.takeIf { it != objCName }?.let { swiftNameAttribute(it) } - ) - - fun translateModule(): List> { - // TODO: make the translation order stable - // to stabilize name mangling. - - stubs.add(objCInterface(kotlinAnyName, superClass = "NSObject", members = buildMembers { - +ObjCMethod(null, true, ObjCInstanceType, listOf("init"), emptyList(), listOf("unavailable")) - +ObjCMethod(null, false, ObjCInstanceType, listOf("new"), emptyList(), listOf("unavailable")) - +ObjCMethod(null, false, ObjCVoidType, listOf("initialize"), emptyList(), listOf("objc_requires_super")) - })) - - // TODO: add comment to the header. - stubs.add(ObjCInterface( - kotlinAnyName.objCName, - superProtocols = listOf("NSCopying"), - categoryName = "${kotlinAnyName.objCName}Copying" - )) - - // TODO: only if appears - stubs.add(objCInterface( - namer.mutableSetName, - generics = listOf("ObjectType"), - superClass = "NSMutableSet" - )) - - // TODO: only if appears - stubs.add(objCInterface( - namer.mutableMapName, - generics = listOf("KeyType", "ObjectType"), - superClass = "NSMutableDictionary" - )) - - stubs.add(ObjCInterface("NSError", categoryName = "NSErrorKotlinException", members = buildMembers { - +ObjCProperty("kotlinException", null, ObjCNullableReferenceType(ObjCIdType), listOf("readonly")) - })) - - genKotlinNumbers() - - val packageFragments = moduleDescriptors.flatMap { it.getPackageFragments() } - - packageFragments.forEach { packageFragment -> - packageFragment.getMemberScope().getContributedDescriptors() - .asSequence() - .filterIsInstance() - .filter { mapper.shouldBeExposed(it) } - .forEach { - val classDescriptor = mapper.getClassIfCategory(it) - if (classDescriptor != null) { - extensions.getOrPut(classDescriptor, { mutableListOf() }) += it - } else { - topLevel.getOrPut(it.findSourceFile(), { mutableListOf() }) += it - } - } - - } - - fun MemberScope.translateClasses() { - getContributedDescriptors() - .asSequence() - .filterIsInstance() - .forEach { - if (mapper.shouldBeExposed(it)) { - if (it.isInterface) { - translateInterface(it) - } else { - translateClass(it) - } - - it.unsubstitutedMemberScope.translateClasses() - } else if (it.isKotlinObjCClass() && mapper.shouldBeVisible(it)) { - assert(!it.isInterface) - translateKotlinObjCClassAsUnavailableStub(it) - } - } - } - - packageFragments.forEach { packageFragment -> - packageFragment.getMemberScope().translateClasses() - } - - extensions.forEach { classDescriptor, declarations -> - translateExtensions(classDescriptor, declarations) - } - - topLevel.forEach { sourceFile, declarations -> - translateTopLevel(sourceFile, declarations) - } - - while (extraClassesToTranslate.isNotEmpty()) { - val descriptor = extraClassesToTranslate.first() - extraClassesToTranslate -= descriptor - if (descriptor.isInterface) { - translateInterface(descriptor) - } else { - translateClass(descriptor) - } - } - - return stubs - } - - private fun translateKotlinObjCClassAsUnavailableStub(descriptor: ClassDescriptor) { - stubs.add(objCInterface( - namer.getClassOrProtocolName(descriptor), - descriptor = descriptor, - superClass = "NSObject", - attributes = listOf("unavailable(\"Kotlin subclass of Objective-C class can't be imported\")") - - )) - } - - private fun genKotlinNumbers() { - val members = buildMembers { - NSNumberKind.values().forEach { - +nsNumberFactory(it, listOf("unavailable")) - } - NSNumberKind.values().forEach { - +nsNumberInit(it, listOf("unavailable")) - } - } - stubs.add(objCInterface( - namer.kotlinNumberName, - superClass = "NSNumber", - members = members - )) - - NSNumberKind.values().forEach { - if (it.mappedKotlinClassId != null) { - stubs += genKotlinNumber(it.mappedKotlinClassId, it) - } - } - } - - private fun genKotlinNumber(kotlinClassId: ClassId, kind: NSNumberKind): ObjCInterface { - val name = namer.numberBoxName(kotlinClassId) - - val members = buildMembers { - +nsNumberFactory(kind) - +nsNumberInit(kind) - } - return objCInterface( - name, - superClass = namer.kotlinNumberName.objCName, - members = members - ) - } - - private fun nsNumberInit(kind: NSNumberKind, attributes: List = emptyList()): ObjCMethod { - return ObjCMethod( - null, - false, - ObjCInstanceType, - listOf(kind.factorySelector), - listOf(ObjCParameter("value", null, kind.objCType)), - attributes - ) - } - - private fun nsNumberFactory(kind: NSNumberKind, attributes: List = emptyList()): ObjCMethod { - return ObjCMethod( - null, - true, - ObjCInstanceType, - listOf(kind.initSelector), - listOf(ObjCParameter("value", null, kind.objCType)), - attributes - ) - } - - private fun translateClassName(descriptor: ClassDescriptor) = classOrInterfaceToName.getOrPut(descriptor) { + private fun referenceClass(descriptor: ClassDescriptor): ObjCExportNamer.ClassOrProtocolName { assert(mapper.shouldBeExposed(descriptor)) - val forwardDeclarations = if (descriptor.isInterface) protocolForwardDeclarations else classForwardDeclarations + assert(!descriptor.isInterface) + generator?.requireClassOrInterface(descriptor) - namer.getClassOrProtocolName(descriptor).also { forwardDeclarations += it.objCName } + return translateClassOrInterfaceName(descriptor).also { + generator?.referenceClass(it.objCName, descriptor) + } } - private fun translateInterface(descriptor: ClassDescriptor) { - if (!generatedClasses.add(descriptor)) return + private fun referenceProtocol(descriptor: ClassDescriptor): ObjCExportNamer.ClassOrProtocolName { + assert(mapper.shouldBeExposed(descriptor)) + assert(descriptor.isInterface) + generator?.requireClassOrInterface(descriptor) - val name = translateClassName(descriptor) + return translateClassOrInterfaceName(descriptor).also { + generator?.referenceProtocol(it.objCName, descriptor) + } + } + + private fun translateClassOrInterfaceName(descriptor: ClassDescriptor): ObjCExportNamer.ClassOrProtocolName { + assert(mapper.shouldBeExposed(descriptor)) + + return namer.getClassOrProtocolName(descriptor) + } + + override fun translateInterface(descriptor: ClassDescriptor): ObjCProtocol { + val name = translateClassOrInterfaceName(descriptor) val members: List> = buildMembers { translateInterfaceMembers(descriptor) } val superProtocols: List = descriptor.superProtocols - val protocolStub = objCProtocol(name, descriptor, superProtocols, members) - - stubs.add(protocolStub) + return objCProtocol(name, descriptor, superProtocols, members) } private val ClassDescriptor.superProtocols: List @@ -353,47 +95,48 @@ abstract class ObjCExportHeaderGenerator( .asSequence() .filter { mapper.shouldBeExposed(it) } .map { - translateInterface(it) - translateClassName(it).objCName + generator?.generateInterface(it) + referenceProtocol(it).objCName } .toList() - private fun translateExtensions(classDescriptor: ClassDescriptor, declarations: List) { - translateClass(classDescriptor) + override fun translateExtensions( + classDescriptor: ClassDescriptor, + declarations: List + ): ObjCInterface { + generator?.generateClass(classDescriptor) - val name = translateClassName(classDescriptor).objCName + val name = referenceClass(classDescriptor).objCName val members = buildMembers { translatePlainMembers(declarations) } - stubs.add(ObjCInterface(name, categoryName = "Extensions", members = members)) + return ObjCInterface(name, categoryName = "Extensions", members = members) } - private fun translateTopLevel(sourceFile: SourceFile, declarations: List) { - val name = namer.getFileClassName(sourceFile) + override fun translateFile(file: SourceFile, declarations: List): ObjCInterface { + val name = namer.getFileClassName(file) // TODO: stop inheriting KotlinBase. val members = buildMembers { translatePlainMembers(declarations) } - stubs.add(objCInterface( + return objCInterface( name, superClass = namer.kotlinAnyName.objCName, members = members, attributes = listOf("objc_subclassing_restricted") - )) + ) } - private fun translateClass(descriptor: ClassDescriptor) { - if (!generatedClasses.add(descriptor)) return - - val name = translateClassName(descriptor) + override fun translateClass(descriptor: ClassDescriptor): ObjCInterface { + val name = translateClassOrInterfaceName(descriptor) val superClass = descriptor.getSuperClassNotAny() val superName = if (superClass == null) { kotlinAnyName } else { - translateClass(superClass) - translateClassName(superClass) + generator?.generateClass(superClass) + referenceClass(superClass) } val superProtocols: List = descriptor.superProtocols @@ -410,7 +153,7 @@ abstract class ObjCExportHeaderGenerator( +buildMethod(it, it) if (selector == "init") { +ObjCMethod(it, false, ObjCInstanceType, listOf("new"), emptyList(), - listOf("availability(swift, unavailable, message=\"use object initializers instead\")")) + listOf("availability(swift, unavailable, message=\"use object initializers instead\")")) } } @@ -467,7 +210,7 @@ abstract class ObjCExportHeaderGenerator( val attributes = if (descriptor.isFinalOrEnum) listOf("objc_subclassing_restricted") else emptyList() - val interfaceStub = objCInterface( + return objCInterface( name, descriptor = descriptor, superClass = superName.objCName, @@ -475,7 +218,6 @@ abstract class ObjCExportHeaderGenerator( members = members, attributes = attributes ) - stubs.add(interfaceStub) } private fun ClassDescriptor.getExposedMembers(): List = @@ -744,9 +486,6 @@ abstract class ObjCExportHeaderGenerator( } } - private fun swiftNameAttribute(swiftName: String) = "swift_name(\"$swiftName\")" - private fun objcRuntimeNameAttribute(name: String) = "objc_runtime_name(\"$name\")" - private val methodsWithThrowAnnotationConsidered = mutableSetOf() private val uncheckedExceptionClasses = listOf("Error", "RuntimeException").map { @@ -759,7 +498,8 @@ abstract class ObjCExportHeaderGenerator( val throwsAnnotation = method.annotations.findAnnotation(KonanFqNames.throws) ?: return if (!mapper.doesThrow(method)) { - reportWarning(method, "@${KonanFqNames.throws.shortName()} annotation should also be added to a base method") + warningCollector.reportWarning(method, + "@${KonanFqNames.throws.shortName()} annotation should also be added to a base method") } if (method in methodsWithThrowAnnotationConsidered) return @@ -770,13 +510,13 @@ abstract class ObjCExportHeaderGenerator( val classDescriptor = TypeUtils.getClassDescriptor((argument as KClassValue).getArgumentType(method.module)) ?: continue uncheckedExceptionClasses.firstOrNull { classDescriptor.isSubclassOf(it) }?.let { - reportWarning(method, + warningCollector.reportWarning(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") } - scheduleClassToBeGenerated(classDescriptor) + generator?.requireClassOrInterface(classDescriptor) } } @@ -800,6 +540,173 @@ abstract class ObjCExportHeaderGenerator( MethodBridge.ReturnValue.Instance.FactoryResult -> ObjCInstanceType } + internal fun mapReferenceType(kotlinType: KotlinType): ObjCReferenceType = + mapReferenceTypeIgnoringNullability(kotlinType).let { + if (kotlinType.binaryRepresentationIsNullable()) { + ObjCNullableReferenceType(it) + } else { + it + } + } + + internal fun mapReferenceTypeIgnoringNullability(kotlinType: KotlinType): ObjCNonNullReferenceType { + class TypeMappingMatch(val type: KotlinType, val descriptor: ClassDescriptor, val mapper: CustomTypeMapper) + + val typeMappingMatches = (listOf(kotlinType) + kotlinType.supertypes()).mapNotNull { type -> + (type.constructor.declarationDescriptor as? ClassDescriptor)?.let { descriptor -> + mapper.customTypeMappers[descriptor.classId]?.let { mapper -> + TypeMappingMatch(type, descriptor, mapper) + } + } + } + + val mostSpecificMatches = typeMappingMatches.filter { match -> + typeMappingMatches.all { otherMatch -> + otherMatch.descriptor == match.descriptor || + !otherMatch.descriptor.isSubclassOf(match.descriptor) + } + } + + if (mostSpecificMatches.size > 1) { + val types = mostSpecificMatches.map { it.type } + val firstType = types[0] + val secondType = types[1] + + warningCollector.reportWarning( + "Exposed type '$kotlinType' is '$firstType' and '$secondType' at the same time. " + + "This most likely wouldn't work as expected.") + + // TODO: the same warning for such classes. + } + + mostSpecificMatches.firstOrNull()?.let { + return it.mapper.mapType(it.type, this) + } + + val classDescriptor = kotlinType.getErasedTypeClass() + + // TODO: translate `where T : BaseClass, T : SomeInterface` to `BaseClass* ` + + // TODO: expose custom inline class boxes properly. + if (classDescriptor == builtIns.any || classDescriptor.classId in mapper.hiddenTypes || classDescriptor.isInlined()) { + return ObjCIdType + } + + if (classDescriptor.defaultType.isObjCObjectType()) { + return mapObjCObjectReferenceTypeIgnoringNullability(classDescriptor) + } + + return if (classDescriptor.isInterface) { + ObjCProtocolType(referenceProtocol(classDescriptor).objCName) + } else { + ObjCClassType(referenceClass(classDescriptor).objCName) + } + } + + private tailrec fun mapObjCObjectReferenceTypeIgnoringNullability(descriptor: ClassDescriptor): ObjCNonNullReferenceType { + // TODO: more precise types can be used. + + if (descriptor.isObjCMetaClass()) return ObjCIdType + + if (descriptor.isExternalObjCClass()) { + return if (descriptor.isInterface) { + val name = descriptor.name.asString().removeSuffix("Protocol") + generator?.referenceProtocol(name) + ObjCProtocolType(name) + } else { + val name = descriptor.name.asString() + generator?.referenceClass(name) + ObjCClassType(name) + } + } + + if (descriptor.isKotlinObjCClass()) { + return mapObjCObjectReferenceTypeIgnoringNullability(descriptor.getSuperClassOrAny()) + } + + return ObjCIdType + } + + private fun mapType(kotlinType: KotlinType, typeBridge: TypeBridge): ObjCType = when (typeBridge) { + ReferenceBridge -> mapReferenceType(kotlinType) + is ValueTypeBridge -> { + when (typeBridge.objCValueType) { + ObjCValueType.BOOL -> ObjCPrimitiveType("BOOL") + ObjCValueType.UNICHAR -> ObjCPrimitiveType("unichar") + ObjCValueType.CHAR -> ObjCPrimitiveType("int8_t") + ObjCValueType.SHORT -> ObjCPrimitiveType("int16_t") + ObjCValueType.INT -> ObjCPrimitiveType("int32_t") + ObjCValueType.LONG_LONG -> ObjCPrimitiveType("int64_t") + ObjCValueType.UNSIGNED_CHAR -> ObjCPrimitiveType("uint8_t") + ObjCValueType.UNSIGNED_SHORT -> ObjCPrimitiveType("uint16_t") + ObjCValueType.UNSIGNED_INT -> ObjCPrimitiveType("uint32_t") + ObjCValueType.UNSIGNED_LONG_LONG -> ObjCPrimitiveType("uint64_t") + ObjCValueType.FLOAT -> ObjCPrimitiveType("float") + ObjCValueType.DOUBLE -> ObjCPrimitiveType("double") + ObjCValueType.POINTER -> ObjCPointerType(ObjCVoidType) + } + // TODO: consider other namings. + } + } +} + +abstract class ObjCExportHeaderGenerator internal constructor( + val moduleDescriptors: List, + val builtIns: KotlinBuiltIns, + internal val mapper: ObjCExportMapper, + val namer: ObjCExportNamer +) { + + constructor( + moduleDescriptors: List, + builtIns: KotlinBuiltIns, + topLevelNamePrefix: String + ) : this(moduleDescriptors, builtIns, topLevelNamePrefix, ObjCExportMapper()) + + private constructor( + moduleDescriptors: List, + builtIns: KotlinBuiltIns, + topLevelNamePrefix: String, + mapper: ObjCExportMapper + ) : this( + moduleDescriptors, + builtIns, + mapper, + ObjCExportNamerImpl(moduleDescriptors.toSet(), builtIns, mapper, topLevelNamePrefix, local = false) + ) + + constructor( + moduleDescriptor: ModuleDescriptor, + builtIns: KotlinBuiltIns, + topLevelNamePrefix: String = moduleDescriptor.namePrefix + ) : this(moduleDescriptor, emptyList(), builtIns, topLevelNamePrefix) + + constructor( + moduleDescriptor: ModuleDescriptor, + exportedDependencies: List, + builtIns: KotlinBuiltIns, + topLevelNamePrefix: String = moduleDescriptor.namePrefix + ) : this(listOf(moduleDescriptor) + exportedDependencies, builtIns, topLevelNamePrefix) + + private val stubs = mutableListOf>() + + private val classForwardDeclarations = linkedSetOf() + private val protocolForwardDeclarations = linkedSetOf() + private val extraClassesToTranslate = mutableSetOf() + + private val translator = ObjCExportTranslatorImpl(this, builtIns, mapper, namer, + object : ObjCExportWarningCollector { + override fun reportWarning(text: String) = + this@ObjCExportHeaderGenerator.reportWarning(text) + + override fun reportWarning(method: FunctionDescriptor, text: String) = + this@ObjCExportHeaderGenerator.reportWarning(method, text) + }) + + internal val generatedClasses = mutableSetOf() + internal val extensions = mutableMapOf>() + internal val topLevel = mutableMapOf>() + fun build(): List = mutableListOf().apply { add("#import ") add("") @@ -829,119 +736,235 @@ abstract class ObjCExportHeaderGenerator( protected abstract fun reportWarning(method: FunctionDescriptor, text: String) - internal fun mapReferenceType(kotlinType: KotlinType): ObjCReferenceType = - mapReferenceTypeIgnoringNullability(kotlinType).let { - if (kotlinType.binaryRepresentationIsNullable()) { - ObjCNullableReferenceType(it) - } else { - it - } - } - internal fun mapReferenceTypeIgnoringNullability(kotlinType: KotlinType): ObjCNonNullReferenceType { - class TypeMappingMatch(val type: KotlinType, val descriptor: ClassDescriptor, val mapper: CustomTypeMapper) + fun translateModule(): List> { + // TODO: make the translation order stable + // to stabilize name mangling. + + stubs.add(objCInterface(namer.kotlinAnyName, superClass = "NSObject", members = buildMembers { + +ObjCMethod(null, true, ObjCInstanceType, listOf("init"), emptyList(), listOf("unavailable")) + +ObjCMethod(null, false, ObjCInstanceType, listOf("new"), emptyList(), listOf("unavailable")) + +ObjCMethod(null, false, ObjCVoidType, listOf("initialize"), emptyList(), listOf("objc_requires_super")) + })) + + // TODO: add comment to the header. + stubs.add(ObjCInterface( + namer.kotlinAnyName.objCName, + superProtocols = listOf("NSCopying"), + categoryName = "${namer.kotlinAnyName.objCName}Copying" + )) + + // TODO: only if appears + stubs.add(objCInterface( + namer.mutableSetName, + generics = listOf("ObjectType"), + superClass = "NSMutableSet" + )) + + // TODO: only if appears + stubs.add(objCInterface( + namer.mutableMapName, + generics = listOf("KeyType", "ObjectType"), + superClass = "NSMutableDictionary" + )) + + stubs.add(ObjCInterface("NSError", categoryName = "NSErrorKotlinException", members = buildMembers { + +ObjCProperty("kotlinException", null, ObjCNullableReferenceType(ObjCIdType), listOf("readonly")) + })) + + genKotlinNumbers() + + val packageFragments = moduleDescriptors.flatMap { it.getPackageFragments() } + + packageFragments.forEach { packageFragment -> + packageFragment.getMemberScope().getContributedDescriptors() + .asSequence() + .filterIsInstance() + .filter { mapper.shouldBeExposed(it) } + .forEach { + val classDescriptor = mapper.getClassIfCategory(it) + if (classDescriptor != null) { + extensions.getOrPut(classDescriptor, { mutableListOf() }) += it + } else { + topLevel.getOrPut(it.findSourceFile(), { mutableListOf() }) += it + } + } - val typeMappingMatches = (listOf(kotlinType) + kotlinType.supertypes()).mapNotNull { type -> - (type.constructor.declarationDescriptor as? ClassDescriptor)?.let { descriptor -> - customTypeMappers[descriptor.classId]?.let { mapper -> - TypeMappingMatch(type, descriptor, mapper) - } - } } - val mostSpecificMatches = typeMappingMatches.filter { match -> - typeMappingMatches.all { otherMatch -> - otherMatch.descriptor == match.descriptor || - !otherMatch.descriptor.isSubclassOf(match.descriptor) - } + fun MemberScope.translateClasses() { + getContributedDescriptors() + .asSequence() + .filterIsInstance() + .forEach { + if (mapper.shouldBeExposed(it)) { + if (it.isInterface) { + generateInterface(it) + } else { + generateClass(it) + } + + it.unsubstitutedMemberScope.translateClasses() + } else if (it.isKotlinObjCClass() && mapper.shouldBeVisible(it)) { + assert(!it.isInterface) + stubs += translator.translateKotlinObjCClassAsUnavailableStub(it) + } + } } - if (mostSpecificMatches.size > 1) { - val types = mostSpecificMatches.map { it.type } - val firstType = types[0] - val secondType = types[1] - - reportWarning("Exposed type '$kotlinType' is '$firstType' and '$secondType' at the same time. " + - "This most likely wouldn't work as expected.") - - // TODO: the same warning for such classes. + packageFragments.forEach { packageFragment -> + packageFragment.getMemberScope().translateClasses() } - mostSpecificMatches.firstOrNull()?.let { - return it.mapper.mapType(it.type) + extensions.forEach { classDescriptor, declarations -> + generateExtensions(classDescriptor, declarations) } - val classDescriptor = kotlinType.getErasedTypeClass() - - // TODO: translate `where T : BaseClass, T : SomeInterface` to `BaseClass* ` - - // TODO: expose custom inline class boxes properly. - if (classDescriptor == builtIns.any || classDescriptor.classId in hiddenTypes || classDescriptor.isInlined()) { - return ObjCIdType + topLevel.forEach { sourceFile, declarations -> + generateFile(sourceFile, declarations) } - if (classDescriptor.defaultType.isObjCObjectType()) { - return mapObjCObjectReferenceTypeIgnoringNullability(classDescriptor) - } - - scheduleClassToBeGenerated(classDescriptor) - - return if (classDescriptor.isInterface) { - ObjCProtocolType(translateClassName(classDescriptor).objCName) - } else { - ObjCClassType(translateClassName(classDescriptor).objCName) - } - } - - private tailrec fun mapObjCObjectReferenceTypeIgnoringNullability(descriptor: ClassDescriptor): ObjCNonNullReferenceType { - // TODO: more precise types can be used. - - if (descriptor.isObjCMetaClass()) return ObjCIdType - - if (descriptor.isExternalObjCClass()) { - return if (descriptor.isInterface) { - val name = descriptor.name.asString().removeSuffix("Protocol") - protocolForwardDeclarations += name - ObjCProtocolType(name) + while (extraClassesToTranslate.isNotEmpty()) { + val descriptor = extraClassesToTranslate.first() + extraClassesToTranslate -= descriptor + if (descriptor.isInterface) { + generateInterface(descriptor) } else { - val name = descriptor.name.asString() - classForwardDeclarations += name - ObjCClassType(name) + generateClass(descriptor) } } - if (descriptor.isKotlinObjCClass()) { - return mapObjCObjectReferenceTypeIgnoringNullability(descriptor.getSuperClassOrAny()) - } - - return ObjCIdType + return stubs } - private fun scheduleClassToBeGenerated(classDescriptor: ClassDescriptor) { - if (classDescriptor !in generatedClasses) { - extraClassesToTranslate += classDescriptor - } - } - - private fun mapType(kotlinType: KotlinType, typeBridge: TypeBridge): ObjCType = when (typeBridge) { - ReferenceBridge -> mapReferenceType(kotlinType) - is ValueTypeBridge -> { - when (typeBridge.objCValueType) { - ObjCValueType.BOOL -> ObjCPrimitiveType("BOOL") - ObjCValueType.UNICHAR -> ObjCPrimitiveType("unichar") - ObjCValueType.CHAR -> ObjCPrimitiveType("int8_t") - ObjCValueType.SHORT -> ObjCPrimitiveType("int16_t") - ObjCValueType.INT -> ObjCPrimitiveType("int32_t") - ObjCValueType.LONG_LONG -> ObjCPrimitiveType("int64_t") - ObjCValueType.UNSIGNED_CHAR -> ObjCPrimitiveType("uint8_t") - ObjCValueType.UNSIGNED_SHORT -> ObjCPrimitiveType("uint16_t") - ObjCValueType.UNSIGNED_INT -> ObjCPrimitiveType("uint32_t") - ObjCValueType.UNSIGNED_LONG_LONG -> ObjCPrimitiveType("uint64_t") - ObjCValueType.FLOAT -> ObjCPrimitiveType("float") - ObjCValueType.DOUBLE -> ObjCPrimitiveType("double") - ObjCValueType.POINTER -> ObjCPointerType(ObjCVoidType) + private fun genKotlinNumbers() { + val members = buildMembers { + NSNumberKind.values().forEach { + +nsNumberFactory(it, listOf("unavailable")) + } + NSNumberKind.values().forEach { + +nsNumberInit(it, listOf("unavailable")) + } + } + stubs.add(objCInterface( + namer.kotlinNumberName, + superClass = "NSNumber", + members = members + )) + + NSNumberKind.values().forEach { + if (it.mappedKotlinClassId != null) { + stubs += genKotlinNumber(it.mappedKotlinClassId, it) } - // TODO: consider other namings. } } -} \ No newline at end of file + + private fun genKotlinNumber(kotlinClassId: ClassId, kind: NSNumberKind): ObjCInterface { + val name = namer.numberBoxName(kotlinClassId) + + val members = buildMembers { + +nsNumberFactory(kind) + +nsNumberInit(kind) + } + return objCInterface( + name, + superClass = namer.kotlinNumberName.objCName, + members = members + ) + } + + private fun nsNumberInit(kind: NSNumberKind, attributes: List = emptyList()): ObjCMethod { + return ObjCMethod( + null, + false, + ObjCInstanceType, + listOf(kind.factorySelector), + listOf(ObjCParameter("value", null, kind.objCType)), + attributes + ) + } + + private fun nsNumberFactory(kind: NSNumberKind, attributes: List = emptyList()): ObjCMethod { + return ObjCMethod( + null, + true, + ObjCInstanceType, + listOf(kind.initSelector), + listOf(ObjCParameter("value", null, kind.objCType)), + attributes + ) + } + + private fun generateFile(sourceFile: SourceFile, declarations: List) { + stubs.add(translator.translateFile(sourceFile, declarations)) + } + + private fun generateExtensions(classDescriptor: ClassDescriptor, declarations: List) { + stubs.add(translator.translateExtensions(classDescriptor, declarations)) + } + + internal fun generateClass(descriptor: ClassDescriptor) { + if (!generatedClasses.add(descriptor)) return + stubs.add(translator.translateClass(descriptor)) + } + + internal fun generateInterface(descriptor: ClassDescriptor) { + if (!generatedClasses.add(descriptor)) return + stubs.add(translator.translateInterface(descriptor)) + } + + internal fun requireClassOrInterface(descriptor: ClassDescriptor) { + if (descriptor !in generatedClasses) { + extraClassesToTranslate += descriptor + } + } + + internal fun referenceClass(objCName: String, descriptor: ClassDescriptor? = null) { + if (descriptor !in generatedClasses) classForwardDeclarations += objCName + } + + internal fun referenceProtocol(objCName: String, descriptor: ClassDescriptor? = null) { + if (descriptor !in generatedClasses) protocolForwardDeclarations += objCName + } +} + +private fun objCInterface( + name: ObjCExportNamer.ClassOrProtocolName, + generics: List = emptyList(), + descriptor: ClassDescriptor? = null, + superClass: String? = null, + superProtocols: List = emptyList(), + members: List> = emptyList(), + attributes: List = emptyList() +): ObjCInterface = ObjCInterface( + name.objCName, + generics, + descriptor, + superClass, + superProtocols, + null, + members, + attributes + name.toNameAttributes() +) + +private fun objCProtocol( + name: ObjCExportNamer.ClassOrProtocolName, + descriptor: ClassDescriptor, + superProtocols: List, + members: List>, + attributes: List = emptyList() +): ObjCProtocol = ObjCProtocol( + name.objCName, + descriptor, + superProtocols, + members, + attributes + name.toNameAttributes() +) + +private fun ObjCExportNamer.ClassOrProtocolName.toNameAttributes(): List = listOfNotNull( + binaryName.takeIf { it != objCName }?.let { objcRuntimeNameAttribute(it) }, + swiftName.takeIf { it != objCName }?.let { swiftNameAttribute(it) } +) + +private fun swiftNameAttribute(swiftName: String) = "swift_name(\"$swiftName\")" +private fun objcRuntimeNameAttribute(name: String) = "objc_runtime_name(\"$name\")" diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGeneratorImpl.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGeneratorImpl.kt index 28df7c0b223..e0f1df6b616 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGeneratorImpl.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGeneratorImpl.kt @@ -6,16 +6,17 @@ package org.jetbrains.kotlin.backend.konan.objcexport import org.jetbrains.kotlin.backend.konan.Context -import org.jetbrains.kotlin.backend.konan.getExportedDependencies import org.jetbrains.kotlin.backend.konan.reportCompilationWarning import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.ir.util.report -internal class ObjCExportHeaderGeneratorImpl(val context: Context) : ObjCExportHeaderGenerator( - context.moduleDescriptor, - context.getExportedDependencies(), - context.builtIns -) { +internal class ObjCExportHeaderGeneratorImpl( + val context: Context, + moduleDescriptors: List, + mapper: ObjCExportMapper, + namer: ObjCExportNamer +) : ObjCExportHeaderGenerator(moduleDescriptors, context.builtIns, mapper, namer) { override fun reportWarning(text: String) { context.reportCompilationWarning(text) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt index 77bb821f620..c5e018be146 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt @@ -11,6 +11,7 @@ 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.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.PrimitiveType import org.jetbrains.kotlin.builtins.UnsignedType import org.jetbrains.kotlin.descriptors.* @@ -21,10 +22,19 @@ import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.typeUtil.isNothing import org.jetbrains.kotlin.types.typeUtil.isUnit -internal abstract class ObjCExportMapper { - abstract fun getCategoryMembersFor(descriptor: ClassDescriptor): List - val maxFunctionTypeParameterCount get() = KONAN_FUNCTION_INTERFACES_MAX_PARAMETERS - abstract fun isSpecialMapped(descriptor: ClassDescriptor): Boolean +internal class ObjCExportMapper { + companion object { + val maxFunctionTypeParameterCount get() = KONAN_FUNCTION_INTERFACES_MAX_PARAMETERS + } + + val customTypeMappers: Map get() = CustomTypeMappers.byClassId + val hiddenTypes: Set get() = CustomTypeMappers.hiddenTypes + + fun isSpecialMapped(descriptor: ClassDescriptor): Boolean { + // TODO: this method duplicates some of the [ObjCExportTranslatorImpl.mapReferenceType] logic. + return KotlinBuiltIns.isAny(descriptor) || + descriptor.getAllSuperClassifiers().any { it.classId in customTypeMappers } + } private val methodBridgeCache = mutableMapOf() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt index d8858612602..ac9d7fe259a 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.backend.konan.objcexport +import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.backend.konan.descriptors.isArray import org.jetbrains.kotlin.backend.konan.descriptors.isInterface import org.jetbrains.kotlin.builtins.KotlinBuiltIns @@ -14,11 +15,11 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.resolve.descriptorUtil.* +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf +import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.resolve.source.PsiSourceFile - interface ObjCExportNamer { data class ClassOrProtocolName(val swiftName: String, val objCName: String, val binaryName: String = objCName) @@ -29,32 +30,37 @@ interface ObjCExportNamer { fun getPropertyName(property: PropertyDescriptor): String fun getObjectInstanceSelector(descriptor: ClassDescriptor): String fun getEnumEntrySelector(descriptor: ClassDescriptor): String + + fun numberBoxName(classId: ClassId): ClassOrProtocolName + + val kotlinAnyName: ClassOrProtocolName + val mutableSetName: ClassOrProtocolName + val mutableMapName: ClassOrProtocolName + val kotlinNumberName: ClassOrProtocolName } fun createNamer(moduleDescriptor: ModuleDescriptor, topLevelNamePrefix: String = moduleDescriptor.namePrefix): ObjCExportNamer = createNamer(moduleDescriptor, emptyList(), topLevelNamePrefix) -fun createNamer(moduleDescriptor: ModuleDescriptor, - exportedDependencies: List, - topLevelNamePrefix: String = moduleDescriptor.namePrefix): ObjCExportNamer { - val generator = object : ObjCExportHeaderGenerator( - moduleDescriptor, - exportedDependencies, - moduleDescriptor.builtIns, - topLevelNamePrefix - ) { - override fun reportWarning(text: String) {} - override fun reportWarning(method: FunctionDescriptor, text: String) {} - } - return generator.namer -} +fun createNamer( + moduleDescriptor: ModuleDescriptor, + exportedDependencies: List, + topLevelNamePrefix: String = moduleDescriptor.namePrefix +): ObjCExportNamer = ObjCExportNamerImpl( + (exportedDependencies + moduleDescriptor).toSet(), + moduleDescriptor.builtIns, + ObjCExportMapper(), + topLevelNamePrefix, + local = true +) internal class ObjCExportNamerImpl( - val moduleDescriptors: Set, + private val moduleDescriptors: Set, builtIns: KotlinBuiltIns, - val mapper: ObjCExportMapper, - private val topLevelNamePrefix: String + private val mapper: ObjCExportMapper, + private val topLevelNamePrefix: String, + private val local: Boolean ) : ObjCExportNamer { private fun String.toUnmangledClassOrProtocolName(): ObjCExportNamer.ClassOrProtocolName = @@ -66,15 +72,15 @@ internal class ObjCExportNamerImpl( binaryName = "Kotlin$this" ) - val kotlinAnyName = "KotlinBase".toUnmangledClassOrProtocolName() + override val kotlinAnyName = "KotlinBase".toUnmangledClassOrProtocolName() - val mutableSetName = "MutableSet".toSpecialStandardClassOrProtocolName() - val mutableMapName = "MutableDictionary".toSpecialStandardClassOrProtocolName() + override val mutableSetName = "MutableSet".toSpecialStandardClassOrProtocolName() + override val mutableMapName = "MutableDictionary".toSpecialStandardClassOrProtocolName() - fun numberBoxName(classId: ClassId): ObjCExportNamer.ClassOrProtocolName = + override fun numberBoxName(classId: ClassId): ObjCExportNamer.ClassOrProtocolName = classId.shortClassName.asString().toSpecialStandardClassOrProtocolName() - val kotlinNumberName = "Number".toSpecialStandardClassOrProtocolName() + override val kotlinNumberName = "Number".toSpecialStandardClassOrProtocolName() private val methodSelectors = object : Mapping() { @@ -204,8 +210,10 @@ internal class ObjCExportNamerImpl( } else { append(descriptor.name.asString().capitalize()) } - } else { + } else if (containingDeclaration is PackageFragmentDescriptor) { appendTopLevelClassBaseName(descriptor) + } else { + error("unexpected class parent: $containingDeclaration") } }.mangledBySuffixUnderscores() } @@ -219,8 +227,10 @@ internal class ObjCExportNamerImpl( append(getClassOrProtocolObjCName(containingDeclaration)) .append(descriptor.name.asString().capitalize()) - } else { + } else if (containingDeclaration is PackageFragmentDescriptor) { append(topLevelNamePrefix).appendTopLevelClassBaseName(descriptor) + } else { + error("unexpected class parent: $containingDeclaration") } }.mangledBySuffixUnderscores() } @@ -430,21 +440,22 @@ internal class ObjCExportNamerImpl( error("name candidates run out") } - fun getIfAssigned(element: T): N? = elementToName[element] + private fun getIfAssigned(element: T): N? = elementToName[element] - fun tryAssign(element: T, name: N): Boolean { + private fun tryAssign(element: T, name: N): Boolean { if (element in elementToName) error(element) if (reserved(name)) return false - val elements = nameToElements.getOrPut(name) { mutableListOf() } - if (elements.any { conflict(element, it) }) { + if (nameToElements[name].orEmpty().any { conflict(element, it) }) { return false } - elements += element + if (!local) { + nameToElements.getOrPut(name) { mutableListOf() } += element - elementToName[element] = name + elementToName[element] = name + } return true }