Improve Kotlin collections support when producing framework
* Represent MutableList, Set, Map as standard Objective-C collections * Represent MutableSet and MutableMap as Obj-C collection subclasses * Use Objective-C generics to represent collection element type * Make type mapping more correct
This commit is contained in:
committed by
SvyatoslavScherbina
parent
d228e77c0c
commit
b15c94855e
+5
@@ -398,6 +398,11 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
|
||||
val Kotlin_ObjCExport_refFromObjC by lazyRtFunction
|
||||
val Kotlin_Interop_CreateNSStringFromKString by lazyRtFunction
|
||||
val Kotlin_Interop_CreateNSArrayFromKList by lazyRtFunction
|
||||
val Kotlin_Interop_CreateNSMutableArrayFromKList by lazyRtFunction
|
||||
val Kotlin_Interop_CreateNSSetFromKSet by lazyRtFunction
|
||||
val Kotlin_Interop_CreateKotlinMutableSetFromKSet by lazyRtFunction
|
||||
val Kotlin_Interop_CreateNSDictionaryFromKMap by lazyRtFunction
|
||||
val Kotlin_Interop_CreateKotlinMutableDictonaryFromKMap by lazyRtFunction
|
||||
val Kotlin_ObjCExport_convertUnit by lazyRtFunction
|
||||
val Kotlin_ObjCExport_GetAssociatedObject by lazyRtFunction
|
||||
val Kotlin_ObjCExport_AbstractMethodCalled by lazyRtFunction
|
||||
|
||||
+5
@@ -70,6 +70,11 @@ internal class ObjCDataGenerator(val codegen: CodeGenerator) {
|
||||
|
||||
val classObjectType = codegen.runtime.getStructType("_class_t")
|
||||
|
||||
fun exportClass(name: String) {
|
||||
context.llvm.usedGlobals += getClassGlobal(name, isMetaclass = false).llvm
|
||||
context.llvm.usedGlobals += getClassGlobal(name, isMetaclass = true).llvm
|
||||
}
|
||||
|
||||
private fun getClassGlobal(name: String, isMetaclass: Boolean): ConstPointer {
|
||||
val prefix = if (isMetaclass) {
|
||||
"OBJC_METACLASS_\$_"
|
||||
|
||||
+30
-2
@@ -178,6 +178,9 @@ internal class ObjCExportCodeGenerator(
|
||||
dataGenerator.emitEmptyClass(namer.getPackageName(fqName), namer.kotlinAnyName)
|
||||
}
|
||||
|
||||
dataGenerator.exportClass("KotlinMutableSet")
|
||||
dataGenerator.exportClass("KotlinMutableDictionary")
|
||||
|
||||
emitSpecialClassesConvertions()
|
||||
|
||||
objCTypeAdapters += createTypeAdapter(context.builtIns.any)
|
||||
@@ -375,7 +378,7 @@ private fun ObjCExportCodeGenerator.emitBoxConverter(objCValueType: ObjCValueTyp
|
||||
private fun ObjCExportCodeGenerator.emitFunctionConverters() {
|
||||
val generator = BlockAdapterToFunctionGenerator(this)
|
||||
|
||||
(0 .. 22).forEach { numberOfParameters ->
|
||||
(0 .. mapper.maxFunctionTypeParameterCount).forEach { numberOfParameters ->
|
||||
val converter = generator.run { generateConvertFunctionToBlock(numberOfParameters) }
|
||||
setObjCExportTypeInfo(context.builtIns.getFunction(numberOfParameters), constPointer(converter))
|
||||
}
|
||||
@@ -399,7 +402,7 @@ private fun ObjCExportCodeGenerator.emitKotlinFunctionAdaptersToBlock() {
|
||||
val ptr = staticData.placeGlobalArray(
|
||||
"",
|
||||
pointerType(runtime.typeInfoType),
|
||||
(0 .. 22).map {
|
||||
(0 .. mapper.maxFunctionTypeParameterCount).map {
|
||||
generateKotlinFunctionAdapterToBlock(it)
|
||||
}
|
||||
).pointer.getElementPtr(0)
|
||||
@@ -419,6 +422,31 @@ private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() {
|
||||
constPointer(context.llvm.Kotlin_Interop_CreateNSArrayFromKList)
|
||||
)
|
||||
|
||||
setObjCExportTypeInfo(
|
||||
context.builtIns.mutableList,
|
||||
constPointer(context.llvm.Kotlin_Interop_CreateNSMutableArrayFromKList)
|
||||
)
|
||||
|
||||
setObjCExportTypeInfo(
|
||||
context.builtIns.set,
|
||||
constPointer(context.llvm.Kotlin_Interop_CreateNSSetFromKSet)
|
||||
)
|
||||
|
||||
setObjCExportTypeInfo(
|
||||
context.builtIns.mutableSet,
|
||||
constPointer(context.llvm.Kotlin_Interop_CreateKotlinMutableSetFromKSet)
|
||||
)
|
||||
|
||||
setObjCExportTypeInfo(
|
||||
context.builtIns.map,
|
||||
constPointer(context.llvm.Kotlin_Interop_CreateNSDictionaryFromKMap)
|
||||
)
|
||||
|
||||
setObjCExportTypeInfo(
|
||||
context.builtIns.mutableMap,
|
||||
constPointer(context.llvm.Kotlin_Interop_CreateKotlinMutableDictonaryFromKMap)
|
||||
)
|
||||
|
||||
ObjCValueType.values().forEach {
|
||||
emitBoxConverter(it)
|
||||
}
|
||||
|
||||
+166
-44
@@ -23,10 +23,10 @@ import org.jetbrains.kotlin.backend.konan.descriptors.getPackageFragments
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isArray
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
|
||||
import org.jetbrains.kotlin.backend.konan.reportCompilationError
|
||||
import org.jetbrains.kotlin.backend.konan.reportCompilationWarning
|
||||
import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType
|
||||
import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
|
||||
import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType
|
||||
import org.jetbrains.kotlin.builtins.isFunctionType
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.*
|
||||
@@ -38,13 +38,10 @@ import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
|
||||
internal class ObjCExportHeaderGenerator(val context: Context) {
|
||||
val mapper: ObjCExportMapper = object : ObjCExportMapper {
|
||||
override fun isRepresentedAsObjCInterface(descriptor: ClassDescriptor): Boolean {
|
||||
val objCType = mapReferenceType(descriptor.defaultType)
|
||||
return objCType is ObjCClassType && objCType.className == translateClassName(descriptor)
|
||||
}
|
||||
|
||||
override fun getCategoryMembersFor(descriptor: ClassDescriptor) =
|
||||
extensions[descriptor].orEmpty()
|
||||
|
||||
override val specialMappedTypes get() = customTypeMappers.keys
|
||||
}
|
||||
|
||||
val namer = ObjCExportNamer(context, mapper)
|
||||
@@ -52,6 +49,43 @@ internal class ObjCExportHeaderGenerator(val context: Context) {
|
||||
val generatedClasses = mutableSetOf<ClassDescriptor>()
|
||||
val topLevel = mutableMapOf<FqName, MutableList<CallableMemberDescriptor>>()
|
||||
|
||||
val customTypeMappers: Map<ClassDescriptor, CustomTypeMapper> = with (context.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)
|
||||
result += CustomTypeMapper.Collection(generator, map, "NSDictionary")
|
||||
result += CustomTypeMapper.Collection(generator, mutableMap, namer.mutableMapName)
|
||||
|
||||
for (descriptor in listOf(boolean, char, byte, short, int, long, float, double)) {
|
||||
// TODO: Kotlin code doesn't have any checkcasts on unboxing,
|
||||
// so it is possible that it expects boxed number of other type and unboxes it incorrectly.
|
||||
// TODO: NSNumber seem to have different equality semantics.
|
||||
result += CustomTypeMapper.Simple(descriptor, "NSNumber")
|
||||
}
|
||||
|
||||
result += CustomTypeMapper.Simple(string, "NSString")
|
||||
|
||||
(0 .. mapper.maxFunctionTypeParameterCount).forEach {
|
||||
result += CustomTypeMapper.Function(generator, it)
|
||||
}
|
||||
|
||||
result.associateBy { it.mappedClassDescriptor }
|
||||
}
|
||||
|
||||
val hiddenTypes: Set<ClassDescriptor> = run {
|
||||
val customMappedTypes = customTypeMappers.keys
|
||||
|
||||
customMappedTypes
|
||||
.flatMap { it.getAllSuperClassifiers().toList() }
|
||||
.map { it as ClassDescriptor }
|
||||
.toSet() - customMappedTypes
|
||||
}
|
||||
|
||||
private val kotlinAnyName = namer.kotlinAnyName
|
||||
|
||||
private val stubs = mutableListOf<Stub>()
|
||||
@@ -175,7 +209,7 @@ internal class ObjCExportHeaderGenerator(val context: Context) {
|
||||
val name = namer.getPackageName(packageFqName)
|
||||
stubs.addBuiltBy {
|
||||
+"__attribute__((objc_subclassing_restricted))"
|
||||
+"@interface $name : KotlinBase" // TODO: stop inheriting KotlinBase.
|
||||
+"@interface $name : ${namer.kotlinAnyName}" // TODO: stop inheriting KotlinBase.
|
||||
|
||||
translateMembers(declarations)
|
||||
|
||||
@@ -443,6 +477,21 @@ internal class ObjCExportHeaderGenerator(val context: Context) {
|
||||
add("@end;")
|
||||
add("")
|
||||
|
||||
// TODO: add comment to the header.
|
||||
add("@interface $kotlinAnyName (${kotlinAnyName}Copying) <NSCopying>")
|
||||
add("@end;")
|
||||
add("")
|
||||
|
||||
add("__attribute__((objc_runtime_name(\"KotlinMutableSet\")))")
|
||||
add("@interface ${namer.mutableSetName}<ObjectType> : NSMutableSet<ObjectType>") // TODO: only if appears
|
||||
add("@end;")
|
||||
add("")
|
||||
|
||||
add("__attribute__((objc_runtime_name(\"KotlinMutableDictionary\")))")
|
||||
add("@interface ${namer.mutableMapName}<KeyType, ObjectType> : NSMutableDictionary<KeyType, ObjectType>") // TODO: only if appears
|
||||
add("@end;")
|
||||
add("")
|
||||
|
||||
stubs.forEach {
|
||||
addAll(it.lines)
|
||||
add("")
|
||||
@@ -452,19 +501,34 @@ internal class ObjCExportHeaderGenerator(val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ObjCType {
|
||||
internal sealed class ObjCType {
|
||||
final override fun toString(): String = this.render()
|
||||
|
||||
open fun render(varName: String): String = "${this.render()} $varName"
|
||||
abstract fun render(): String
|
||||
}
|
||||
|
||||
private sealed class ObjCReferenceType(kotlinType: KotlinType) : ObjCType() {
|
||||
internal sealed class ObjCReferenceType(kotlinType: KotlinType) : ObjCType() {
|
||||
val attributes = if (TypeUtils.isNullableType(kotlinType)) " _Nullable" else ""
|
||||
}
|
||||
|
||||
private class ObjCClassType(kotlinType: KotlinType, val className: String) : ObjCReferenceType(kotlinType) {
|
||||
override fun render() = "$className*$attributes"
|
||||
private class ObjCClassType(
|
||||
kotlinType: KotlinType,
|
||||
val className: String,
|
||||
val typeArguments: List<ObjCReferenceType> = emptyList()
|
||||
) : ObjCReferenceType(kotlinType) {
|
||||
|
||||
override fun render() = buildString {
|
||||
append(className)
|
||||
if (typeArguments.isNotEmpty()) {
|
||||
append("<")
|
||||
typeArguments.joinTo(this) { it.render() }
|
||||
append(">")
|
||||
}
|
||||
append('*')
|
||||
|
||||
append(attributes)
|
||||
}
|
||||
}
|
||||
|
||||
private class ObjCProtocolType(kotlinType: KotlinType, val protocolName: String) : ObjCReferenceType(kotlinType) {
|
||||
@@ -502,43 +566,101 @@ private object ObjCVoidType : ObjCType() {
|
||||
override fun render(varName: String) = error("variables can't have `void` type")
|
||||
}
|
||||
|
||||
internal interface CustomTypeMapper {
|
||||
val mappedClassDescriptor: ClassDescriptor
|
||||
fun mapType(type: KotlinType, mappedSuperType: KotlinType): ObjCReferenceType
|
||||
|
||||
class Simple(
|
||||
override val mappedClassDescriptor: ClassDescriptor,
|
||||
private val objCClassName: String
|
||||
) : CustomTypeMapper {
|
||||
|
||||
override fun mapType(type: KotlinType, mappedSuperType: KotlinType): ObjCReferenceType =
|
||||
ObjCClassType(type, objCClassName)
|
||||
}
|
||||
|
||||
class Collection(
|
||||
private val generator: ObjCExportHeaderGenerator,
|
||||
override val mappedClassDescriptor: ClassDescriptor,
|
||||
private val objCClassName: String
|
||||
) : CustomTypeMapper {
|
||||
override fun mapType(type: KotlinType, mappedSuperType: KotlinType): ObjCReferenceType {
|
||||
val typeArguments = mappedSuperType.arguments.map {
|
||||
val argument = it.type
|
||||
if (TypeUtils.isNullableType(argument)) {
|
||||
// Kotlin `null` keys and values are represented as `NSNull` singleton.
|
||||
ObjCIdType(generator.context.builtIns.anyType)
|
||||
} else {
|
||||
generator.mapReferenceType(argument)
|
||||
}
|
||||
}
|
||||
|
||||
return ObjCClassType(type, objCClassName, typeArguments)
|
||||
}
|
||||
}
|
||||
|
||||
class Function(
|
||||
private val generator: ObjCExportHeaderGenerator,
|
||||
parameterCount: Int
|
||||
) : CustomTypeMapper {
|
||||
override val mappedClassDescriptor = generator.context.builtIns.getFunction(parameterCount)
|
||||
|
||||
override fun mapType(type: KotlinType, mappedSuperType: KotlinType): ObjCReferenceType {
|
||||
val functionType = mappedSuperType
|
||||
|
||||
val returnType = functionType.getReturnTypeFromFunctionType()
|
||||
val parameterTypes = listOfNotNull(functionType.getReceiverTypeFromFunctionType()) +
|
||||
functionType.getValueParameterTypesFromFunctionType().map { it.type }
|
||||
|
||||
return ObjCBlockPointerType(
|
||||
type,
|
||||
generator.mapReferenceType(returnType),
|
||||
parameterTypes.map { generator.mapReferenceType(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ObjCExportHeaderGenerator.mapReferenceType(kotlinType: KotlinType): ObjCReferenceType {
|
||||
// TODO: translate `where T : BaseClass, T : SomeInterface` to `BaseClass* <SomeInterface>`
|
||||
|
||||
val typeToMapper = (listOf(kotlinType) + kotlinType.supertypes()).mapNotNull { type ->
|
||||
val mapper = customTypeMappers[type.constructor.declarationDescriptor]
|
||||
if (mapper != null) {
|
||||
type to mapper
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.toMap()
|
||||
|
||||
val mostSpecificTypeToMapper = typeToMapper.filter { (_, mapper) ->
|
||||
typeToMapper.values.all { it.mappedClassDescriptor == mapper.mappedClassDescriptor ||
|
||||
!it.mappedClassDescriptor.isSubclassOf(mapper.mappedClassDescriptor) }
|
||||
|
||||
// E.g. if both List and MutableList are present, then retain only MutableList.
|
||||
}
|
||||
|
||||
if (mostSpecificTypeToMapper.size > 1) {
|
||||
val types = mostSpecificTypeToMapper.keys.toList()
|
||||
val firstType = types[0]
|
||||
val secondType = types[1]
|
||||
|
||||
context.reportCompilationWarning(
|
||||
"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.
|
||||
}
|
||||
|
||||
mostSpecificTypeToMapper.entries.firstOrNull()?.let { (type, mapper) ->
|
||||
return mapper.mapType(kotlinType, type)
|
||||
}
|
||||
|
||||
val classDescriptor = kotlinType.getErasedTypeClass()
|
||||
|
||||
if (classDescriptor.isSubclassOf(context.builtIns.list)) {
|
||||
return ObjCClassType(kotlinType, "NSArray")
|
||||
}
|
||||
// TODO: translate `where T : BaseClass, T : SomeInterface` to `BaseClass* <SomeInterface>`
|
||||
|
||||
|
||||
// TODO: Kotlin code doesn't have any checkcasts on unboxing,
|
||||
// so it is possible that it expects boxed number of other type and unboxes it incorrectly.
|
||||
|
||||
if (classDescriptor.isSubclassOf(context.builtIns.number) ||
|
||||
classDescriptor == context.builtIns.boolean ||
|
||||
classDescriptor == context.builtIns.char) return ObjCClassType(kotlinType, "NSNumber")
|
||||
|
||||
when (classDescriptor) {
|
||||
context.builtIns.any -> return ObjCIdType(kotlinType)
|
||||
context.builtIns.string -> return ObjCClassType(kotlinType, "NSString")
|
||||
}
|
||||
|
||||
val functionType = if (kotlinType.isFunctionType) {
|
||||
kotlinType
|
||||
} else {
|
||||
kotlinType.supertypes().firstOrNull { it.isFunctionType }
|
||||
// TODO: may be incorrect if type has more then one function supertype.
|
||||
}
|
||||
if (functionType != null) {
|
||||
val returnType = functionType.getReturnTypeFromFunctionType()
|
||||
val parameterTypes = listOfNotNull(functionType.getReceiverTypeFromFunctionType()) +
|
||||
functionType.getValueParameterTypesFromFunctionType().map { it.type }
|
||||
|
||||
return ObjCBlockPointerType(
|
||||
kotlinType,
|
||||
mapReferenceType(returnType),
|
||||
parameterTypes.map { mapReferenceType(it) }
|
||||
)
|
||||
if (classDescriptor == context.builtIns.any || classDescriptor in hiddenTypes) {
|
||||
return ObjCIdType(kotlinType)
|
||||
}
|
||||
|
||||
if (classDescriptor !in generatedClasses) {
|
||||
|
||||
+11
-2
@@ -21,19 +21,28 @@ import org.jetbrains.kotlin.backend.common.descriptors.isSuspend
|
||||
import org.jetbrains.kotlin.backend.konan.ValueType
|
||||
import org.jetbrains.kotlin.backend.konan.correspondingValueType
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isArray
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
|
||||
import org.jetbrains.kotlin.backend.konan.isObjCObjectType
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||
|
||||
internal interface ObjCExportMapper {
|
||||
fun isRepresentedAsObjCInterface(descriptor: ClassDescriptor): Boolean
|
||||
fun getCategoryMembersFor(descriptor: ClassDescriptor): List<CallableMemberDescriptor>
|
||||
val maxFunctionTypeParameterCount get() = 22
|
||||
val specialMappedTypes: Set<ClassDescriptor>
|
||||
}
|
||||
|
||||
private fun ObjCExportMapper.isRepresentedAsObjCInterface(descriptor: ClassDescriptor): Boolean =
|
||||
!descriptor.isInterface && !isSpecialMapped(descriptor)
|
||||
|
||||
private fun ObjCExportMapper.isSpecialMapped(descriptor: ClassDescriptor): Boolean =
|
||||
descriptor.getAllSuperClassifiers().any { it in specialMappedTypes }
|
||||
|
||||
internal fun ObjCExportMapper.getClassIfCategory(descriptor: CallableMemberDescriptor): ClassDescriptor? {
|
||||
if (descriptor.dispatchReceiverParameter != null) return null
|
||||
|
||||
@@ -55,7 +64,7 @@ internal fun ObjCExportMapper.shouldBeExposed(descriptor: ClassDescriptor): Bool
|
||||
descriptor.isEffectivelyPublicApi && !descriptor.defaultType.isObjCObjectType() && when (descriptor.kind) {
|
||||
ClassKind.CLASS, ClassKind.INTERFACE, ClassKind.ENUM_CLASS, ClassKind.OBJECT -> true
|
||||
ClassKind.ENUM_ENTRY, ClassKind.ANNOTATION_CLASS -> false
|
||||
}
|
||||
} && !isSpecialMapped(descriptor)
|
||||
|
||||
private fun ObjCExportMapper.isBase(descriptor: CallableMemberDescriptor): Boolean =
|
||||
descriptor.overriddenDescriptors.all { !shouldBeExposed(it) }
|
||||
|
||||
+5
@@ -34,6 +34,9 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe
|
||||
private val commonPackageSegments = context.moduleDescriptor.guessMainPackage().pathSegments()
|
||||
private val topLevelNamePrefix = context.moduleDescriptor.namePrefix
|
||||
|
||||
val mutableSetName = "${topLevelNamePrefix}MutableSet"
|
||||
val mutableMapName = "${topLevelNamePrefix}MutableDictionary"
|
||||
|
||||
private val methodSelectors = object : Mapping<FunctionDescriptor, String>() {
|
||||
|
||||
// Try to avoid clashing with critical NSObject instance methods:
|
||||
@@ -231,6 +234,8 @@ internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMappe
|
||||
val any = context.builtIns.any
|
||||
|
||||
classNames.forceAssign(any, kotlinAnyName)
|
||||
classNames.forceAssign(context.builtIns.mutableSet, mutableSetName)
|
||||
classNames.forceAssign(context.builtIns.mutableMap, mutableMapName)
|
||||
|
||||
fun ClassDescriptor.method(name: String) =
|
||||
this.unsubstitutedMemberScope.getContributedFunctions(
|
||||
|
||||
Reference in New Issue
Block a user