Refactor ObjCExportHeaderGenerator to allow per declaration translation

This commit is contained in:
Svyatoslav Scherbina
2019-03-04 10:46:29 +03:00
committed by SvyatoslavScherbina
parent cead418d50
commit 2b8dcfc533
7 changed files with 639 additions and 536 deletions
@@ -33,7 +33,7 @@ internal fun TypeBridge.makeNothing() = when (this) {
internal class ObjCExportCodeGenerator( internal class ObjCExportCodeGenerator(
codegen: CodeGenerator, codegen: CodeGenerator,
val namer: ObjCExportNamerImpl, val namer: ObjCExportNamer,
val mapper: ObjCExportMapper val mapper: ObjCExportMapper
) : ObjCCodeGenerator(codegen) { ) : ObjCCodeGenerator(codegen) {
@@ -155,14 +155,15 @@ internal class ObjCExportCodeGenerator(
return callFromBridge(conversion.owner.llvmFunction, listOf(value), resultLifetime) return callFromBridge(conversion.owner.llvmFunction, listOf(value), resultLifetime)
} }
internal fun emitRtti( private val objCTypeAdapters = mutableListOf<ObjCTypeAdapter>()
internal fun generate(
generatedClasses: Collection<ClassDescriptor>, generatedClasses: Collection<ClassDescriptor>,
categoryMembers: Map<ClassDescriptor, List<CallableMemberDescriptor>>,
topLevel: Map<SourceFile, List<CallableMemberDescriptor>> topLevel: Map<SourceFile, List<CallableMemberDescriptor>>
) { ) {
val objCTypeAdapters = mutableListOf<ObjCTypeAdapter>()
generatedClasses.forEach { generatedClasses.forEach {
objCTypeAdapters += createTypeAdapter(it) objCTypeAdapters += createTypeAdapter(it, categoryMembers[it].orEmpty())
if (!it.isInterface) { if (!it.isInterface) {
val className = namer.getClassOrProtocolName(it).binaryName val className = namer.getClassOrProtocolName(it).binaryName
@@ -180,7 +181,9 @@ internal class ObjCExportCodeGenerator(
val name = namer.getFileClassName(sourceFile).binaryName val name = namer.getFileClassName(sourceFile).binaryName
dataGenerator.emitEmptyClass(name, namer.kotlinAnyName.binaryName) dataGenerator.emitEmptyClass(name, namer.kotlinAnyName.binaryName)
} }
}
internal fun emitRtti() {
NSNumberKind.values().mapNotNull { it.mappedKotlinClassId }.forEach { NSNumberKind.values().mapNotNull { it.mappedKotlinClassId }.forEach {
dataGenerator.exportClass("Kotlin${it.shortClassName}") dataGenerator.exportClass("Kotlin${it.shortClassName}")
} }
@@ -189,7 +192,7 @@ internal class ObjCExportCodeGenerator(
emitSpecialClassesConvertions() emitSpecialClassesConvertions()
objCTypeAdapters += createTypeAdapter(context.builtIns.any) objCTypeAdapters += createTypeAdapter(context.builtIns.any, categoryMembers = emptyList())
val placedClassAdapters = mutableMapOf<String, ConstPointer>() val placedClassAdapters = mutableMapOf<String, ConstPointer>()
val placedInterfaceAdapters = mutableMapOf<String, ConstPointer>() val placedInterfaceAdapters = mutableMapOf<String, ConstPointer>()
@@ -397,7 +400,7 @@ private fun ObjCExportCodeGenerator.emitBoxConverter(
private fun ObjCExportCodeGenerator.emitFunctionConverters() { private fun ObjCExportCodeGenerator.emitFunctionConverters() {
val generator = BlockAdapterToFunctionGenerator(this) val generator = BlockAdapterToFunctionGenerator(this)
(0 .. mapper.maxFunctionTypeParameterCount).forEach { numberOfParameters -> (0 .. ObjCExportMapper.maxFunctionTypeParameterCount).forEach { numberOfParameters ->
val converter = generator.run { generateConvertFunctionToBlock(numberOfParameters) } val converter = generator.run { generateConvertFunctionToBlock(numberOfParameters) }
setObjCExportTypeInfo(context.builtIns.getFunction(numberOfParameters), constPointer(converter)) setObjCExportTypeInfo(context.builtIns.getFunction(numberOfParameters), constPointer(converter))
} }
@@ -421,7 +424,7 @@ private fun ObjCExportCodeGenerator.emitKotlinFunctionAdaptersToBlock() {
val ptr = staticData.placeGlobalArray( val ptr = staticData.placeGlobalArray(
"", "",
pointerType(runtime.typeInfoType), pointerType(runtime.typeInfoType),
(0 .. mapper.maxFunctionTypeParameterCount).map { (0 .. ObjCExportMapper.maxFunctionTypeParameterCount).map {
generateKotlinFunctionAdapterToBlock(it) generateKotlinFunctionAdapterToBlock(it)
} }
).pointer.getElementPtr(0) ).pointer.getElementPtr(0)
@@ -866,7 +869,8 @@ private fun ObjCExportCodeGenerator.createTypeAdapterForFileClass(
} }
private fun ObjCExportCodeGenerator.createTypeAdapter( private fun ObjCExportCodeGenerator.createTypeAdapter(
descriptor: ClassDescriptor descriptor: ClassDescriptor,
categoryMembers: List<CallableMemberDescriptor>
): ObjCExportCodeGenerator.ObjCTypeAdapter { ): ObjCExportCodeGenerator.ObjCTypeAdapter {
val adapters = mutableListOf<ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter>() val adapters = mutableListOf<ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter>()
val classAdapters = mutableListOf<ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter>() val classAdapters = mutableListOf<ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter>()
@@ -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 val exposedMethods = descriptor.contributedMethods.filter { mapper.shouldBeExposed(it) } + categoryMethods
@@ -5,58 +5,117 @@
package org.jetbrains.kotlin.backend.konan.objcexport package org.jetbrains.kotlin.backend.konan.objcexport
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType
import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.name.ClassId 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.KotlinType
import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.TypeUtils
internal interface CustomTypeMapper { internal interface CustomTypeMapper {
val mappedClassId: ClassId 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 {
override fun mapType(mappedSuperType: KotlinType): ObjCNonNullReferenceType =
ObjCClassType(objCClassName)
} }
class Collection( internal object CustomTypeMappers {
private val generator: ObjCExportHeaderGenerator, /**
mappedClassDescriptor: ClassDescriptor, * Custom type mappers.
private val objCClassName: String *
* Don't forget to update [hiddenTypes] after adding new one.
*/
val byClassId: Map<ClassId, CustomTypeMapper> = with(KotlinBuiltIns.FQ_NAMES) {
val result = mutableListOf<CustomTypeMapper>()
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 }
}
/**
* 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<ClassId> = 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 { ) : 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 typeArguments = mappedSuperType.arguments.map {
val argument = it.type val argument = it.type
if (TypeUtils.isNullableType(argument)) { if (TypeUtils.isNullableType(argument)) {
// Kotlin `null` keys and values are represented as `NSNull` singleton. // Kotlin `null` keys and values are represented as `NSNull` singleton.
ObjCIdType ObjCIdType
} else { } else {
generator.mapReferenceTypeIgnoringNullability(argument) translator.mapReferenceTypeIgnoringNullability(argument)
} }
} }
return ObjCClassType(objCClassName, typeArguments) return ObjCClassType(translator.getObjCClassName(), typeArguments)
} }
} }
class Function( private class Function(parameterCount: Int) : CustomTypeMapper {
private val generator: ObjCExportHeaderGenerator, override val mappedClassId: ClassId = KotlinBuiltIns.getFunctionClassId(parameterCount)
parameterCount: Int
) : CustomTypeMapper {
override val mappedClassId: ClassId = generator.builtIns.getFunction(parameterCount).classId!!
override fun mapType(mappedSuperType: KotlinType): ObjCNonNullReferenceType { override fun mapType(mappedSuperType: KotlinType, translator: ObjCExportTranslatorImpl): ObjCNonNullReferenceType {
val functionType = mappedSuperType val functionType = mappedSuperType
val returnType = functionType.getReturnTypeFromFunctionType() val returnType = functionType.getReturnTypeFromFunctionType()
@@ -64,8 +123,8 @@ internal interface CustomTypeMapper {
functionType.getValueParameterTypesFromFunctionType().map { it.type } functionType.getValueParameterTypesFromFunctionType().map { it.type }
return ObjCBlockPointerType( return ObjCBlockPointerType(
generator.mapReferenceType(returnType), translator.mapReferenceType(returnType),
parameterTypes.map { generator.mapReferenceType(it) } parameterTypes.map { translator.mapReferenceType(it) }
) )
} }
} }
@@ -6,13 +6,11 @@
package org.jetbrains.kotlin.backend.konan.objcexport package org.jetbrains.kotlin.backend.konan.objcexport
import org.jetbrains.kotlin.backend.konan.descriptors.getPackageFragments 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.isNativeBinary
import org.jetbrains.kotlin.backend.konan.llvm.CodeGenerator import org.jetbrains.kotlin.backend.konan.llvm.CodeGenerator
import org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGenerator 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.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.SourceFile
import org.jetbrains.kotlin.konan.file.File import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.target.CompilerOutputKind import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.KonanTarget 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. if (!context.config.produce.isNativeBinary) return // TODO: emit RTTI to the same modules as classes belong to.
val objCCodeGenerator: ObjCExportCodeGenerator val produceFramework = context.config.produce == CompilerOutputKind.FRAMEWORK
val generatedClasses: Set<ClassDescriptor>
val topLevelDeclarations: Map<SourceFile, List<CallableMemberDescriptor>>
if (context.config.produce == CompilerOutputKind.FRAMEWORK) { val mapper = ObjCExportMapper()
val headerGenerator = ObjCExportHeaderGeneratorImpl(context) 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) produceFrameworkSpecific(headerGenerator)
generatedClasses = headerGenerator.generatedClasses objCCodeGenerator.generate(
topLevelDeclarations = headerGenerator.topLevel generatedClasses = headerGenerator.generatedClasses,
objCCodeGenerator = ObjCExportCodeGenerator(codegen, headerGenerator.namer, headerGenerator.mapper) categoryMembers = headerGenerator.extensions,
} else { topLevel = headerGenerator.topLevel
// TODO: refactor ObjCExport* to handle this case on a general basis. )
val mapper = object : ObjCExportMapper() {
override fun getCategoryMembersFor(descriptor: ClassDescriptor): List<CallableMemberDescriptor> =
emptyList()
override fun isSpecialMapped(descriptor: ClassDescriptor): Boolean =
error("shouldn't reach here")
} }
val namer = ObjCExportNamerImpl(emptySet(), context.builtIns, mapper, context.moduleDescriptor.namePrefix) objCCodeGenerator.emitRtti()
objCCodeGenerator = ObjCExportCodeGenerator(codegen, namer, mapper)
generatedClasses = emptySet()
topLevelDeclarations = emptyMap()
}
objCCodeGenerator.emitRtti(generatedClasses = generatedClasses, topLevel = topLevelDeclarations)
} }
private fun produceFrameworkSpecific(headerGenerator: ObjCExportHeaderGenerator) { private fun produceFrameworkSpecific(headerGenerator: ObjCExportHeaderGenerator) {
@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.constants.ArrayValue import org.jetbrains.kotlin.resolve.constants.ArrayValue
import org.jetbrains.kotlin.resolve.constants.KClassValue 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.resolve.scopes.MemberScope
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.isNullable
import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.types.typeUtil.supertypes
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
abstract class ObjCExportHeaderGenerator( interface ObjCExportTranslator {
val moduleDescriptors: List<ModuleDescriptor>, fun translateFile(file: SourceFile, declarations: List<CallableMemberDescriptor>): ObjCInterface
fun translateClass(descriptor: ClassDescriptor): ObjCInterface
fun translateInterface(descriptor: ClassDescriptor): ObjCProtocol
fun translateExtensions(classDescriptor: ClassDescriptor, declarations: List<CallableMemberDescriptor>): 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, val builtIns: KotlinBuiltIns,
topLevelNamePrefix: String val mapper: ObjCExportMapper,
) { val namer: ObjCExportNamer,
val warningCollector: ObjCExportWarningCollector
constructor( ) : ObjCExportTranslator {
moduleDescriptor: ModuleDescriptor,
builtIns: KotlinBuiltIns,
topLevelNamePrefix: String = moduleDescriptor.namePrefix
) : this(moduleDescriptor, emptyList(), builtIns, topLevelNamePrefix)
constructor(
moduleDescriptor: ModuleDescriptor,
exportedDependencies: List<ModuleDescriptor>,
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<ClassDescriptor>()
internal val topLevel = mutableMapOf<SourceFile, MutableList<CallableMemberDescriptor>>()
/**
* Custom type mappers.
*
* Don't forget to update [hiddenTypes] after adding new one.
*/
private val customTypeMappers: Map<ClassId, CustomTypeMapper> = with(builtIns) {
val result = mutableListOf<CustomTypeMapper>()
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<ClassId> = 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 val kotlinAnyName = namer.kotlinAnyName private val kotlinAnyName = namer.kotlinAnyName
private val stubs = mutableListOf<Stub<*>>() internal fun translateKotlinObjCClassAsUnavailableStub(descriptor: ClassDescriptor): ObjCInterface = objCInterface(
private val classOrInterfaceToName = mutableMapOf<ClassDescriptor, ObjCExportNamer.ClassOrProtocolName>()
private val classForwardDeclarations = mutableSetOf<String>()
private val protocolForwardDeclarations = mutableSetOf<String>()
private val extensions = mutableMapOf<ClassDescriptor, MutableList<CallableMemberDescriptor>>()
private val extraClassesToTranslate = mutableSetOf<ClassDescriptor>()
private fun objCInterface(
name: ObjCExportNamer.ClassOrProtocolName,
generics: List<String> = emptyList(),
descriptor: ClassDescriptor? = null,
superClass: String? = null,
superProtocols: List<String> = emptyList(),
members: List<Stub<*>> = emptyList(),
attributes: List<String> = emptyList()
): ObjCInterface = ObjCInterface(
name.objCName,
generics,
descriptor,
superClass,
superProtocols,
null,
members,
attributes + name.toNameAttributes()
)
private fun objCProtocol(
name: ObjCExportNamer.ClassOrProtocolName,
descriptor: ClassDescriptor,
superProtocols: List<String>,
members: List<Stub<*>>,
attributes: List<String> = emptyList()
): ObjCProtocol = ObjCProtocol(
name.objCName,
descriptor,
superProtocols,
members,
attributes + name.toNameAttributes()
)
private fun ObjCExportNamer.ClassOrProtocolName.toNameAttributes(): List<String> = listOfNotNull(
binaryName.takeIf { it != objCName }?.let { objcRuntimeNameAttribute(it) },
swiftName.takeIf { it != objCName }?.let { swiftNameAttribute(it) }
)
fun translateModule(): List<Stub<*>> {
// 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<ObjectType>"
))
// TODO: only if appears
stubs.add(objCInterface(
namer.mutableMapName,
generics = listOf("KeyType", "ObjectType"),
superClass = "NSMutableDictionary<KeyType, ObjectType>"
))
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<CallableMemberDescriptor>()
.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<ClassDescriptor>()
.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), namer.getClassOrProtocolName(descriptor),
descriptor = descriptor, descriptor = descriptor,
superClass = "NSObject", superClass = "NSObject",
attributes = listOf("unavailable(\"Kotlin subclass of Objective-C class can't be imported\")") 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<String> = emptyList()): ObjCMethod { private fun referenceClass(descriptor: ClassDescriptor): ObjCExportNamer.ClassOrProtocolName {
return ObjCMethod(
null,
false,
ObjCInstanceType,
listOf(kind.factorySelector),
listOf(ObjCParameter("value", null, kind.objCType)),
attributes
)
}
private fun nsNumberFactory(kind: NSNumberKind, attributes: List<String> = 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) {
assert(mapper.shouldBeExposed(descriptor)) 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) { private fun referenceProtocol(descriptor: ClassDescriptor): ObjCExportNamer.ClassOrProtocolName {
if (!generatedClasses.add(descriptor)) return 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<Stub<*>> = buildMembers { translateInterfaceMembers(descriptor) } val members: List<Stub<*>> = buildMembers { translateInterfaceMembers(descriptor) }
val superProtocols: List<String> = descriptor.superProtocols val superProtocols: List<String> = descriptor.superProtocols
val protocolStub = objCProtocol(name, descriptor, superProtocols, members) return objCProtocol(name, descriptor, superProtocols, members)
stubs.add(protocolStub)
} }
private val ClassDescriptor.superProtocols: List<String> private val ClassDescriptor.superProtocols: List<String>
@@ -353,47 +95,48 @@ abstract class ObjCExportHeaderGenerator(
.asSequence() .asSequence()
.filter { mapper.shouldBeExposed(it) } .filter { mapper.shouldBeExposed(it) }
.map { .map {
translateInterface(it) generator?.generateInterface(it)
translateClassName(it).objCName referenceProtocol(it).objCName
} }
.toList() .toList()
private fun translateExtensions(classDescriptor: ClassDescriptor, declarations: List<CallableMemberDescriptor>) { override fun translateExtensions(
translateClass(classDescriptor) classDescriptor: ClassDescriptor,
declarations: List<CallableMemberDescriptor>
): ObjCInterface {
generator?.generateClass(classDescriptor)
val name = translateClassName(classDescriptor).objCName val name = referenceClass(classDescriptor).objCName
val members = buildMembers { val members = buildMembers {
translatePlainMembers(declarations) translatePlainMembers(declarations)
} }
stubs.add(ObjCInterface(name, categoryName = "Extensions", members = members)) return ObjCInterface(name, categoryName = "Extensions", members = members)
} }
private fun translateTopLevel(sourceFile: SourceFile, declarations: List<CallableMemberDescriptor>) { override fun translateFile(file: SourceFile, declarations: List<CallableMemberDescriptor>): ObjCInterface {
val name = namer.getFileClassName(sourceFile) val name = namer.getFileClassName(file)
// TODO: stop inheriting KotlinBase. // TODO: stop inheriting KotlinBase.
val members = buildMembers { val members = buildMembers {
translatePlainMembers(declarations) translatePlainMembers(declarations)
} }
stubs.add(objCInterface( return objCInterface(
name, name,
superClass = namer.kotlinAnyName.objCName, superClass = namer.kotlinAnyName.objCName,
members = members, members = members,
attributes = listOf("objc_subclassing_restricted") attributes = listOf("objc_subclassing_restricted")
)) )
} }
private fun translateClass(descriptor: ClassDescriptor) { override fun translateClass(descriptor: ClassDescriptor): ObjCInterface {
if (!generatedClasses.add(descriptor)) return val name = translateClassOrInterfaceName(descriptor)
val name = translateClassName(descriptor)
val superClass = descriptor.getSuperClassNotAny() val superClass = descriptor.getSuperClassNotAny()
val superName = if (superClass == null) { val superName = if (superClass == null) {
kotlinAnyName kotlinAnyName
} else { } else {
translateClass(superClass) generator?.generateClass(superClass)
translateClassName(superClass) referenceClass(superClass)
} }
val superProtocols: List<String> = descriptor.superProtocols val superProtocols: List<String> = descriptor.superProtocols
@@ -467,7 +210,7 @@ abstract class ObjCExportHeaderGenerator(
val attributes = if (descriptor.isFinalOrEnum) listOf("objc_subclassing_restricted") else emptyList() val attributes = if (descriptor.isFinalOrEnum) listOf("objc_subclassing_restricted") else emptyList()
val interfaceStub = objCInterface( return objCInterface(
name, name,
descriptor = descriptor, descriptor = descriptor,
superClass = superName.objCName, superClass = superName.objCName,
@@ -475,7 +218,6 @@ abstract class ObjCExportHeaderGenerator(
members = members, members = members,
attributes = attributes attributes = attributes
) )
stubs.add(interfaceStub)
} }
private fun ClassDescriptor.getExposedMembers(): List<CallableMemberDescriptor> = private fun ClassDescriptor.getExposedMembers(): List<CallableMemberDescriptor> =
@@ -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<FunctionDescriptor>() private val methodsWithThrowAnnotationConsidered = mutableSetOf<FunctionDescriptor>()
private val uncheckedExceptionClasses = listOf("Error", "RuntimeException").map { private val uncheckedExceptionClasses = listOf("Error", "RuntimeException").map {
@@ -759,7 +498,8 @@ abstract class ObjCExportHeaderGenerator(
val throwsAnnotation = method.annotations.findAnnotation(KonanFqNames.throws) ?: return val throwsAnnotation = method.annotations.findAnnotation(KonanFqNames.throws) ?: return
if (!mapper.doesThrow(method)) { 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 if (method in methodsWithThrowAnnotationConsidered) return
@@ -770,13 +510,13 @@ abstract class ObjCExportHeaderGenerator(
val classDescriptor = TypeUtils.getClassDescriptor((argument as KClassValue).getArgumentType(method.module)) ?: continue val classDescriptor = TypeUtils.getClassDescriptor((argument as KClassValue).getArgumentType(method.module)) ?: continue
uncheckedExceptionClasses.firstOrNull { classDescriptor.isSubclassOf(it) }?.let { uncheckedExceptionClasses.firstOrNull { classDescriptor.isSubclassOf(it) }?.let {
reportWarning(method, warningCollector.reportWarning(method,
"Method is declared to throw ${classDescriptor.fqNameSafe}, " + "Method is declared to throw ${classDescriptor.fqNameSafe}, " +
"but instances of ${it.fqNameSafe} and its subclasses aren't propagated " + "but instances of ${it.fqNameSafe} and its subclasses aren't propagated " +
"from Kotlin to Objective-C/Swift") "from Kotlin to Objective-C/Swift")
} }
scheduleClassToBeGenerated(classDescriptor) generator?.requireClassOrInterface(classDescriptor)
} }
} }
@@ -800,6 +540,173 @@ abstract class ObjCExportHeaderGenerator(
MethodBridge.ReturnValue.Instance.FactoryResult -> ObjCInstanceType 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* <SomeInterface>`
// 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<ModuleDescriptor>,
val builtIns: KotlinBuiltIns,
internal val mapper: ObjCExportMapper,
val namer: ObjCExportNamer
) {
constructor(
moduleDescriptors: List<ModuleDescriptor>,
builtIns: KotlinBuiltIns,
topLevelNamePrefix: String
) : this(moduleDescriptors, builtIns, topLevelNamePrefix, ObjCExportMapper())
private constructor(
moduleDescriptors: List<ModuleDescriptor>,
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<ModuleDescriptor>,
builtIns: KotlinBuiltIns,
topLevelNamePrefix: String = moduleDescriptor.namePrefix
) : this(listOf(moduleDescriptor) + exportedDependencies, builtIns, topLevelNamePrefix)
private val stubs = mutableListOf<Stub<*>>()
private val classForwardDeclarations = linkedSetOf<String>()
private val protocolForwardDeclarations = linkedSetOf<String>()
private val extraClassesToTranslate = mutableSetOf<ClassDescriptor>()
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<ClassDescriptor>()
internal val extensions = mutableMapOf<ClassDescriptor, MutableList<CallableMemberDescriptor>>()
internal val topLevel = mutableMapOf<SourceFile, MutableList<CallableMemberDescriptor>>()
fun build(): List<String> = mutableListOf<String>().apply { fun build(): List<String> = mutableListOf<String>().apply {
add("#import <Foundation/Foundation.h>") add("#import <Foundation/Foundation.h>")
add("") add("")
@@ -829,119 +736,235 @@ abstract class ObjCExportHeaderGenerator(
protected abstract fun reportWarning(method: FunctionDescriptor, text: String) protected abstract fun reportWarning(method: FunctionDescriptor, text: String)
internal fun mapReferenceType(kotlinType: KotlinType): ObjCReferenceType =
mapReferenceTypeIgnoringNullability(kotlinType).let { fun translateModule(): List<Stub<*>> {
if (kotlinType.binaryRepresentationIsNullable()) { // TODO: make the translation order stable
ObjCNullableReferenceType(it) // 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<ObjectType>"
))
// TODO: only if appears
stubs.add(objCInterface(
namer.mutableMapName,
generics = listOf("KeyType", "ObjectType"),
superClass = "NSMutableDictionary<KeyType, ObjectType>"
))
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<CallableMemberDescriptor>()
.filter { mapper.shouldBeExposed(it) }
.forEach {
val classDescriptor = mapper.getClassIfCategory(it)
if (classDescriptor != null) {
extensions.getOrPut(classDescriptor, { mutableListOf() }) += it
} else { } else {
it topLevel.getOrPut(it.findSourceFile(), { mutableListOf() }) += 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 ->
customTypeMappers[descriptor.classId]?.let { mapper ->
TypeMappingMatch(type, descriptor, mapper)
}
}
} }
val mostSpecificMatches = typeMappingMatches.filter { match -> fun MemberScope.translateClasses() {
typeMappingMatches.all { otherMatch -> getContributedDescriptors()
otherMatch.descriptor == match.descriptor || .asSequence()
!otherMatch.descriptor.isSubclassOf(match.descriptor) .filterIsInstance<ClassDescriptor>()
} .forEach {
} if (mapper.shouldBeExposed(it)) {
if (it.isInterface) {
if (mostSpecificMatches.size > 1) { generateInterface(it)
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.
}
mostSpecificMatches.firstOrNull()?.let {
return it.mapper.mapType(it.type)
}
val classDescriptor = kotlinType.getErasedTypeClass()
// TODO: translate `where T : BaseClass, T : SomeInterface` to `BaseClass* <SomeInterface>`
// TODO: expose custom inline class boxes properly.
if (classDescriptor == builtIns.any || classDescriptor.classId in hiddenTypes || classDescriptor.isInlined()) {
return ObjCIdType
}
if (classDescriptor.defaultType.isObjCObjectType()) {
return mapObjCObjectReferenceTypeIgnoringNullability(classDescriptor)
}
scheduleClassToBeGenerated(classDescriptor)
return if (classDescriptor.isInterface) {
ObjCProtocolType(translateClassName(classDescriptor).objCName)
} else { } else {
ObjCClassType(translateClassName(classDescriptor).objCName) generateClass(it)
}
it.unsubstitutedMemberScope.translateClasses()
} else if (it.isKotlinObjCClass() && mapper.shouldBeVisible(it)) {
assert(!it.isInterface)
stubs += translator.translateKotlinObjCClassAsUnavailableStub(it)
}
} }
} }
private tailrec fun mapObjCObjectReferenceTypeIgnoringNullability(descriptor: ClassDescriptor): ObjCNonNullReferenceType { packageFragments.forEach { packageFragment ->
// TODO: more precise types can be used. packageFragment.getMemberScope().translateClasses()
}
if (descriptor.isObjCMetaClass()) return ObjCIdType extensions.forEach { classDescriptor, declarations ->
generateExtensions(classDescriptor, declarations)
}
if (descriptor.isExternalObjCClass()) { topLevel.forEach { sourceFile, declarations ->
return if (descriptor.isInterface) { generateFile(sourceFile, declarations)
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 { } else {
val name = descriptor.name.asString() generateClass(descriptor)
classForwardDeclarations += name
ObjCClassType(name)
} }
} }
if (descriptor.isKotlinObjCClass()) { return stubs
return mapObjCObjectReferenceTypeIgnoringNullability(descriptor.getSuperClassOrAny())
} }
return ObjCIdType 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
))
private fun scheduleClassToBeGenerated(classDescriptor: ClassDescriptor) { NSNumberKind.values().forEach {
if (classDescriptor !in generatedClasses) { if (it.mappedKotlinClassId != null) {
extraClassesToTranslate += classDescriptor stubs += genKotlinNumber(it.mappedKotlinClassId, it)
}
} }
} }
private fun mapType(kotlinType: KotlinType, typeBridge: TypeBridge): ObjCType = when (typeBridge) { private fun genKotlinNumber(kotlinClassId: ClassId, kind: NSNumberKind): ObjCInterface {
ReferenceBridge -> mapReferenceType(kotlinType) val name = namer.numberBoxName(kotlinClassId)
is ValueTypeBridge -> {
when (typeBridge.objCValueType) { val members = buildMembers {
ObjCValueType.BOOL -> ObjCPrimitiveType("BOOL") +nsNumberFactory(kind)
ObjCValueType.UNICHAR -> ObjCPrimitiveType("unichar") +nsNumberInit(kind)
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. return objCInterface(
name,
superClass = namer.kotlinNumberName.objCName,
members = members
)
}
private fun nsNumberInit(kind: NSNumberKind, attributes: List<String> = emptyList()): ObjCMethod {
return ObjCMethod(
null,
false,
ObjCInstanceType,
listOf(kind.factorySelector),
listOf(ObjCParameter("value", null, kind.objCType)),
attributes
)
}
private fun nsNumberFactory(kind: NSNumberKind, attributes: List<String> = emptyList()): ObjCMethod {
return ObjCMethod(
null,
true,
ObjCInstanceType,
listOf(kind.initSelector),
listOf(ObjCParameter("value", null, kind.objCType)),
attributes
)
}
private fun generateFile(sourceFile: SourceFile, declarations: List<CallableMemberDescriptor>) {
stubs.add(translator.translateFile(sourceFile, declarations))
}
private fun generateExtensions(classDescriptor: ClassDescriptor, declarations: List<CallableMemberDescriptor>) {
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<String> = emptyList(),
descriptor: ClassDescriptor? = null,
superClass: String? = null,
superProtocols: List<String> = emptyList(),
members: List<Stub<*>> = emptyList(),
attributes: List<String> = emptyList()
): ObjCInterface = ObjCInterface(
name.objCName,
generics,
descriptor,
superClass,
superProtocols,
null,
members,
attributes + name.toNameAttributes()
)
private fun objCProtocol(
name: ObjCExportNamer.ClassOrProtocolName,
descriptor: ClassDescriptor,
superProtocols: List<String>,
members: List<Stub<*>>,
attributes: List<String> = emptyList()
): ObjCProtocol = ObjCProtocol(
name.objCName,
descriptor,
superProtocols,
members,
attributes + name.toNameAttributes()
)
private fun ObjCExportNamer.ClassOrProtocolName.toNameAttributes(): List<String> = 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\")"
@@ -6,16 +6,17 @@
package org.jetbrains.kotlin.backend.konan.objcexport package org.jetbrains.kotlin.backend.konan.objcexport
import org.jetbrains.kotlin.backend.konan.Context 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.backend.konan.reportCompilationWarning
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.ir.util.report import org.jetbrains.kotlin.ir.util.report
internal class ObjCExportHeaderGeneratorImpl(val context: Context) : ObjCExportHeaderGenerator( internal class ObjCExportHeaderGeneratorImpl(
context.moduleDescriptor, val context: Context,
context.getExportedDependencies(), moduleDescriptors: List<ModuleDescriptor>,
context.builtIns mapper: ObjCExportMapper,
) { namer: ObjCExportNamer
) : ObjCExportHeaderGenerator(moduleDescriptors, context.builtIns, mapper, namer) {
override fun reportWarning(text: String) { override fun reportWarning(text: String) {
context.reportCompilationWarning(text) context.reportCompilationWarning(text)
@@ -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.allOverriddenDescriptors
import org.jetbrains.kotlin.backend.konan.descriptors.isArray import org.jetbrains.kotlin.backend.konan.descriptors.isArray
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface 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.PrimitiveType
import org.jetbrains.kotlin.builtins.UnsignedType import org.jetbrains.kotlin.builtins.UnsignedType
import org.jetbrains.kotlin.descriptors.* 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.isNothing
import org.jetbrains.kotlin.types.typeUtil.isUnit import org.jetbrains.kotlin.types.typeUtil.isUnit
internal abstract class ObjCExportMapper { internal class ObjCExportMapper {
abstract fun getCategoryMembersFor(descriptor: ClassDescriptor): List<CallableMemberDescriptor> companion object {
val maxFunctionTypeParameterCount get() = KONAN_FUNCTION_INTERFACES_MAX_PARAMETERS val maxFunctionTypeParameterCount get() = KONAN_FUNCTION_INTERFACES_MAX_PARAMETERS
abstract fun isSpecialMapped(descriptor: ClassDescriptor): Boolean }
val customTypeMappers: Map<ClassId, CustomTypeMapper> get() = CustomTypeMappers.byClassId
val hiddenTypes: Set<ClassId> 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<FunctionDescriptor, MethodBridge>() private val methodBridgeCache = mutableMapOf<FunctionDescriptor, MethodBridge>()
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.backend.konan.objcexport 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.isArray
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
import org.jetbrains.kotlin.builtins.KotlinBuiltIns 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.load.kotlin.PackagePartClassUtils
import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.descriptorUtil.* import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.source.PsiSourceFile import org.jetbrains.kotlin.resolve.source.PsiSourceFile
interface ObjCExportNamer { interface ObjCExportNamer {
data class ClassOrProtocolName(val swiftName: String, val objCName: String, val binaryName: String = objCName) 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 getPropertyName(property: PropertyDescriptor): String
fun getObjectInstanceSelector(descriptor: ClassDescriptor): String fun getObjectInstanceSelector(descriptor: ClassDescriptor): String
fun getEnumEntrySelector(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, fun createNamer(moduleDescriptor: ModuleDescriptor,
topLevelNamePrefix: String = moduleDescriptor.namePrefix): ObjCExportNamer = topLevelNamePrefix: String = moduleDescriptor.namePrefix): ObjCExportNamer =
createNamer(moduleDescriptor, emptyList(), topLevelNamePrefix) createNamer(moduleDescriptor, emptyList(), topLevelNamePrefix)
fun createNamer(moduleDescriptor: ModuleDescriptor, fun createNamer(
moduleDescriptor: ModuleDescriptor,
exportedDependencies: List<ModuleDescriptor>, exportedDependencies: List<ModuleDescriptor>,
topLevelNamePrefix: String = moduleDescriptor.namePrefix): ObjCExportNamer { topLevelNamePrefix: String = moduleDescriptor.namePrefix
val generator = object : ObjCExportHeaderGenerator( ): ObjCExportNamer = ObjCExportNamerImpl(
moduleDescriptor, (exportedDependencies + moduleDescriptor).toSet(),
exportedDependencies,
moduleDescriptor.builtIns, moduleDescriptor.builtIns,
topLevelNamePrefix ObjCExportMapper(),
) { topLevelNamePrefix,
override fun reportWarning(text: String) {} local = true
override fun reportWarning(method: FunctionDescriptor, text: String) {} )
}
return generator.namer
}
internal class ObjCExportNamerImpl( internal class ObjCExportNamerImpl(
val moduleDescriptors: Set<ModuleDescriptor>, private val moduleDescriptors: Set<ModuleDescriptor>,
builtIns: KotlinBuiltIns, builtIns: KotlinBuiltIns,
val mapper: ObjCExportMapper, private val mapper: ObjCExportMapper,
private val topLevelNamePrefix: String private val topLevelNamePrefix: String,
private val local: Boolean
) : ObjCExportNamer { ) : ObjCExportNamer {
private fun String.toUnmangledClassOrProtocolName(): ObjCExportNamer.ClassOrProtocolName = private fun String.toUnmangledClassOrProtocolName(): ObjCExportNamer.ClassOrProtocolName =
@@ -66,15 +72,15 @@ internal class ObjCExportNamerImpl(
binaryName = "Kotlin$this" binaryName = "Kotlin$this"
) )
val kotlinAnyName = "KotlinBase".toUnmangledClassOrProtocolName() override val kotlinAnyName = "KotlinBase".toUnmangledClassOrProtocolName()
val mutableSetName = "MutableSet".toSpecialStandardClassOrProtocolName() override val mutableSetName = "MutableSet".toSpecialStandardClassOrProtocolName()
val mutableMapName = "MutableDictionary".toSpecialStandardClassOrProtocolName() override val mutableMapName = "MutableDictionary".toSpecialStandardClassOrProtocolName()
fun numberBoxName(classId: ClassId): ObjCExportNamer.ClassOrProtocolName = override fun numberBoxName(classId: ClassId): ObjCExportNamer.ClassOrProtocolName =
classId.shortClassName.asString().toSpecialStandardClassOrProtocolName() classId.shortClassName.asString().toSpecialStandardClassOrProtocolName()
val kotlinNumberName = "Number".toSpecialStandardClassOrProtocolName() override val kotlinNumberName = "Number".toSpecialStandardClassOrProtocolName()
private val methodSelectors = object : Mapping<FunctionDescriptor, String>() { private val methodSelectors = object : Mapping<FunctionDescriptor, String>() {
@@ -204,8 +210,10 @@ internal class ObjCExportNamerImpl(
} else { } else {
append(descriptor.name.asString().capitalize()) append(descriptor.name.asString().capitalize())
} }
} else { } else if (containingDeclaration is PackageFragmentDescriptor) {
appendTopLevelClassBaseName(descriptor) appendTopLevelClassBaseName(descriptor)
} else {
error("unexpected class parent: $containingDeclaration")
} }
}.mangledBySuffixUnderscores() }.mangledBySuffixUnderscores()
} }
@@ -219,8 +227,10 @@ internal class ObjCExportNamerImpl(
append(getClassOrProtocolObjCName(containingDeclaration)) append(getClassOrProtocolObjCName(containingDeclaration))
.append(descriptor.name.asString().capitalize()) .append(descriptor.name.asString().capitalize())
} else { } else if (containingDeclaration is PackageFragmentDescriptor) {
append(topLevelNamePrefix).appendTopLevelClassBaseName(descriptor) append(topLevelNamePrefix).appendTopLevelClassBaseName(descriptor)
} else {
error("unexpected class parent: $containingDeclaration")
} }
}.mangledBySuffixUnderscores() }.mangledBySuffixUnderscores()
} }
@@ -430,21 +440,22 @@ internal class ObjCExportNamerImpl(
error("name candidates run out") 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 (element in elementToName) error(element)
if (reserved(name)) return false if (reserved(name)) return false
val elements = nameToElements.getOrPut(name) { mutableListOf() } if (nameToElements[name].orEmpty().any { conflict(element, it) }) {
if (elements.any { conflict(element, it) }) {
return false return false
} }
elements += element if (!local) {
nameToElements.getOrPut(name) { mutableListOf() } += element
elementToName[element] = name elementToName[element] = name
}
return true return true
} }