From b15c94855e8b9fd9da75f10e72c0f26549416480 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Fri, 26 Jan 2018 12:50:34 +0300 Subject: [PATCH] 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 --- .../kotlinx/cinterop/ObjectiveCExportImpl.kt | 41 - .../kotlin/backend/konan/llvm/ContextUtils.kt | 5 + .../konan/llvm/objc/ObjCDataGenerator.kt | 5 + .../objcexport/ObjCExportCodeGenerator.kt | 32 +- .../objcexport/ObjCExportHeaderGenerator.kt | 210 ++++- .../konan/objcexport/ObjCExportMapper.kt | 13 +- .../konan/objcexport/ObjCExportNamer.kt | 5 + runtime/src/main/cpp/ObjCExport.h | 36 + runtime/src/main/cpp/ObjCExport.mm | 149 +--- runtime/src/main/cpp/ObjCExportCollections.mm | 806 ++++++++++++++++++ runtime/src/main/cpp/Types.cpp | 10 + runtime/src/main/cpp/Types.h | 1 + .../kotlin/konan/internal/KonanCollections.kt | 26 + .../kotlin/konan/internal/ObjCExportUtils.kt | 270 ++++++ .../main/kotlin/kotlin/collections/HashMap.kt | 21 +- .../main/kotlin/kotlin/collections/HashSet.kt | 3 +- .../main/kotlin/kotlin/collections/Sets.kt | 3 +- 17 files changed, 1428 insertions(+), 208 deletions(-) delete mode 100644 Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCExportImpl.kt create mode 100644 runtime/src/main/cpp/ObjCExport.h create mode 100644 runtime/src/main/cpp/ObjCExportCollections.mm create mode 100644 runtime/src/main/kotlin/konan/internal/KonanCollections.kt create mode 100644 runtime/src/main/kotlin/konan/internal/ObjCExportUtils.kt diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCExportImpl.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCExportImpl.kt deleted file mode 100644 index 76793d05708..00000000000 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCExportImpl.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2010-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package kotlinx.cinterop - -import konan.internal.ExportForCppRuntime - -// TODO: it it actually not related to cinterop. - -@ExportTypeInfo("theNSArrayListTypeInfo") -internal class NSArrayList : AbstractList { - - // FIXME: override methods of Any. - - @konan.internal.ExportForCppRuntime("Kotlin_NSArrayList_constructor") - constructor() : super() - - override val size: Int get() = getSize() - - @SymbolName("Kotlin_NSArrayList_getSize") - private external fun getSize(): Int - - @SymbolName("Kotlin_NSArrayList_getElement") - external override fun get(index: Int): Any? -} - -@ExportForCppRuntime private fun Kotlin_List_get(list: List<*>, index: Int): Any? = list.get(index) -@ExportForCppRuntime private fun Kotlin_List_getSize(list: List<*>): Int = list.size diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index 6e8405a8092..03add130348 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -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 diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt index 2e719620356..b7c3cc57732 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt @@ -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_\$_" 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 fefeebfb918..27f95e9696d 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 @@ -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) } 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 c0735f19246..baeeddb3f7b 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 @@ -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() val topLevel = mutableMapOf>() + val customTypeMappers: Map = with (context.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) + 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 = 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() @@ -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) ") + add("@end;") + add("") + + add("__attribute__((objc_runtime_name(\"KotlinMutableSet\")))") + add("@interface ${namer.mutableSetName} : NSMutableSet") // TODO: only if appears + add("@end;") + add("") + + add("__attribute__((objc_runtime_name(\"KotlinMutableDictionary\")))") + add("@interface ${namer.mutableMapName} : NSMutableDictionary") // 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 = 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* ` + + 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* ` - - // 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) { 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 5819374c8ab..f59e209ba72 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 @@ -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 + val maxFunctionTypeParameterCount get() = 22 + val specialMappedTypes: Set } +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) } 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 da197cc9c11..744f8f6fb1c 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 @@ -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() { // 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( diff --git a/runtime/src/main/cpp/ObjCExport.h b/runtime/src/main/cpp/ObjCExport.h new file mode 100644 index 00000000000..614fcab268f --- /dev/null +++ b/runtime/src/main/cpp/ObjCExport.h @@ -0,0 +1,36 @@ +#ifndef RUNTIME_OBJCEXPORT_H +#define RUNTIME_OBJCEXPORT_H + +#if KONAN_OBJC_INTEROP + +#import + +#import "Types.h" +#import "Memory.h" + +extern "C" id objc_retain(id self); +extern "C" void objc_release(id self); + +inline static bool HasAssociatedObjectField(ObjHeader* obj) { + return HasReservedObjectTail(obj); +} + +inline static id GetAssociatedObject(ObjHeader* obj) { + return *reinterpret_cast(GetReservedObjectTail(obj)); +} + +inline static void SetAssociatedObject(ObjHeader* obj, id value) { + *reinterpret_cast(GetReservedObjectTail(obj)) = value; +} + +extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj); +extern "C" OBJ_GETTER(Kotlin_ObjCExport_refFromObjC, id obj); + +@protocol ConvertibleToKotlin +@required +-(KRef)toKotlin:(KRef*)OBJ_RESULT; +@end; + +#endif // KONAN_OBJC_INTEROP + +#endif // RUNTIME_OBJCEXPORT_H \ No newline at end of file diff --git a/runtime/src/main/cpp/ObjCExport.mm b/runtime/src/main/cpp/ObjCExport.mm index 4372e9c689e..7887b79fd4e 100644 --- a/runtime/src/main/cpp/ObjCExport.mm +++ b/runtime/src/main/cpp/ObjCExport.mm @@ -20,14 +20,14 @@ #if KONAN_OBJC_INTEROP #import -#import #import #import -#import #import #import #import #import + +#import "ObjCExport.h" #import "MemoryPrivate.hpp" #import "Runtime.h" #import "Utils.h" @@ -92,18 +92,6 @@ static void setAssociatedTypeInfo(Class clazz, const TypeInfo* typeInfo) { objc_setAssociatedObject(clazz, &associatedTypeInfoKey, [NSValue valueWithPointer:typeInfo], OBJC_ASSOCIATION_RETAIN); } -inline static bool HasAssociatedObjectField(ObjHeader* obj) { - return HasReservedObjectTail(obj); -} - -inline static void SetAssociatedObject(ObjHeader* obj, id value) { - *reinterpret_cast(GetReservedObjectTail(obj)) = value; -} - -static inline id GetAssociatedObject(ObjHeader* obj) { - return *reinterpret_cast(GetReservedObjectTail(obj)); -} - extern "C" id Kotlin_ObjCExport_GetAssociatedObject(ObjHeader* obj) { return GetAssociatedObject(obj); } @@ -118,12 +106,7 @@ inline static OBJ_GETTER(AllocInstanceWithAssociatedObject, const TypeInfo* type static Class getOrCreateClass(const TypeInfo* typeInfo); static void initializeClass(Class clazz); -@protocol ConvertibleToKotlin -@required --(KRef)toKotlin:(KRef*)OBJ_RESULT; -@end; - -@interface KotlinBase : NSObject +@interface KotlinBase : NSObject @end; @implementation KotlinBase { @@ -199,6 +182,11 @@ static void initializeClass(Class clazz); [super release]; } +- (instancetype)copyWithZone:(NSZone *)zone { + // TODO: write documentation. + return [self retain]; +} + @end; extern "C" void Kotlin_ObjCExport_releaseReservedObjectTail(ObjHeader* obj) { @@ -324,9 +312,7 @@ static void initializeClass(Class clazz) { @interface NSObject (NSObjectToKotlin) @end; -extern "C" id objc_retain(id self); extern "C" id objc_retainBlock(id self); -extern "C" void objc_release(id self); extern "C" id objc_retainAutoreleaseReturnValue(id self); @implementation NSObject (NSObjectToKotlin) @@ -351,27 +337,6 @@ extern "C" OBJ_GETTER(Kotlin_Interop_CreateKStringFromNSString, NSString* str); } @end; -@interface NSArray (NSArrayToKotlin) -@end; - -extern "C" void Kotlin_NSArrayList_constructor(ObjHeader* obj); - -extern const TypeInfo *theNSArrayListTypeInfo; - -@implementation NSArray (NSArrayToKotlin) --(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { - ObjHeader* result = AllocInstanceWithAssociatedObject(theNSArrayListTypeInfo, objc_retain(self), OBJ_RESULT); - - Kotlin_NSArrayList_constructor(result); - - return result; -} - --(void)releaseAsAssociatedObject { - objc_release(self); -} -@end; - extern "C" { OBJ_GETTER(Kotlin_boxBoolean, KBoolean value); @@ -425,47 +390,6 @@ static Class __NSCFBooleanClass = nullptr; } @end; -@interface KListNSArray : NSArray -@end; - -extern "C" OBJ_GETTER(Kotlin_List_get, ObjHeader* list, KInt index); -extern "C" KInt Kotlin_List_getSize(ObjHeader* list); - -extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj); - -@implementation KListNSArray { - ObjHeader* list; -} - --(void)dealloc { - UpdateRef(&list, nullptr); - [super dealloc]; -} - -+(id)createWithKList:(ObjHeader*)list { - KListNSArray* result = [[[KListNSArray alloc] init] autorelease]; - UpdateRef(&result->list, list); - return result; -} - --(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { - RETURN_OBJ(list); -} - --(id)objectAtIndex:(NSUInteger)index { - ObjHolder kotlinValueHolder; - ObjHeader* kotlinValue = Kotlin_List_get(list, index, kotlinValueHolder.slot()); - if (kotlinValue == nullptr) return [NSNull null]; - - return Kotlin_ObjCExport_refToObjC(kotlinValue); -} - --(NSUInteger)count { - return Kotlin_List_getSize(list); -} - -@end; - @interface NSBlock @end; @@ -567,10 +491,6 @@ static const TypeInfo* getFunctionTypeInfoForBlock(id block) { @end; -extern "C" id Kotlin_Interop_CreateNSArrayFromKList(ObjHeader* obj) { - return [KListNSArray createWithKList:obj]; -} - static id Kotlin_ObjCExport_refToObjC_slowpath(ObjHeader* obj); extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj) { @@ -603,14 +523,35 @@ static id convertKotlinObject(ObjHeader* obj) { return [clazz createWrapper:obj]; } +extern "C" id Kotlin_Interop_CreateNSStringFromKString(KRef str); + +static convertReferenceToObjC findConverterFromInterfaces(const TypeInfo* typeInfo) { + const TypeInfo* foundTypeInfo = nullptr; + + for (int i = 0; i < typeInfo->implementedInterfacesCount_; ++i) { + const TypeInfo* interfaceTypeInfo = typeInfo->implementedInterfaces_[i]; + if (interfaceTypeInfo->writableInfo_->objCExport.convert != nullptr) { + if (foundTypeInfo == nullptr || IsSubInterface(interfaceTypeInfo, foundTypeInfo)) { + foundTypeInfo = interfaceTypeInfo; + } else if (!IsSubInterface(foundTypeInfo, interfaceTypeInfo)) { + [NSException raise:NSGenericException + format:@"Can't convert to Objective-C Kotlin object that is '%@' and '%@' and the same time", + Kotlin_Interop_CreateNSStringFromKString(foundTypeInfo->relativeName_), + Kotlin_Interop_CreateNSStringFromKString(interfaceTypeInfo->relativeName_)]; + } + } + } + + return foundTypeInfo == nullptr ? + nullptr : + (convertReferenceToObjC)foundTypeInfo->writableInfo_->objCExport.convert; +} + static id Kotlin_ObjCExport_refToObjC_slowpath(ObjHeader* obj) { const TypeInfo* typeInfo = obj->type_info(); convertReferenceToObjC converter = nullptr; - for (int i = 0; i < typeInfo->implementedInterfacesCount_; ++i) { - converter = (convertReferenceToObjC)typeInfo->implementedInterfaces_[i]->writableInfo_->objCExport.convert; - if (converter != nullptr) break; - } + converter = findConverterFromInterfaces(typeInfo); if (converter == nullptr) { getOrCreateClass(typeInfo); @@ -622,18 +563,6 @@ static id Kotlin_ObjCExport_refToObjC_slowpath(ObjHeader* obj) { return converter(obj); } -extern "C" KInt Kotlin_NSArrayList_getSize(ObjHeader* obj) { - NSArray* array = (NSArray*) GetAssociatedObject(obj); - return [array count]; -} - -extern "C" OBJ_GETTER(Kotlin_NSArrayList_getElement, ObjHeader* obj, KInt index) { - NSArray* array = (NSArray*) GetAssociatedObject(obj); - id element = [array objectAtIndex:index]; - if (element == NSNull.null) RETURN_OBJ(nullptr); - RETURN_RESULT_OF(Kotlin_ObjCExport_refFromObjC, element); -} - static const TypeInfo* createTypeInfo( const TypeInfo* superType, const KStdVector& superInterfaces, @@ -968,16 +897,4 @@ extern "C" void Kotlin_ObjCExport_AbstractMethodCalled(id self, SEL selector) { class_getName(object_getClass(self)), sel_getName(selector)]; } -#else // KONAN_OBJC_INTEROP - -extern "C" KInt Kotlin_NSArrayList_getSize(ObjHeader* obj) { - RuntimeAssert(false, "Objective-C interop is disabled"); - return -1; -} - -extern "C" OBJ_GETTER(Kotlin_NSArrayList_getElement, ObjHeader* obj, KInt index) { - RuntimeAssert(false, "Objective-C interop is disabled"); - RETURN_OBJ(nullptr); -} - #endif // KONAN_OBJC_INTEROP diff --git a/runtime/src/main/cpp/ObjCExportCollections.mm b/runtime/src/main/cpp/ObjCExportCollections.mm new file mode 100644 index 00000000000..0653aae65f2 --- /dev/null +++ b/runtime/src/main/cpp/ObjCExportCollections.mm @@ -0,0 +1,806 @@ +#import "Memory.h" +#import "Types.h" + +#if KONAN_OBJC_INTEROP + +#import + +#import +#import +#import +#import +#import + +#import "Exceptions.h" +#import "Runtime.h" +#import "ObjCExport.h" + +extern "C" { + +// Imports from ObjCExportUtils.kt: + +OBJ_GETTER0(Kotlin_NSArrayAsKList_create); +OBJ_GETTER0(Kotlin_NSMutableArrayAsKMutableList_create); +OBJ_GETTER0(Kotlin_NSEnumeratorAsKIterator_create); +OBJ_GETTER0(Kotlin_NSSetAsKSet_create); +OBJ_GETTER0(Kotlin_NSDictionaryAsKMap_create); + +KBoolean Kotlin_Iterator_hasNext(KRef iterator); +OBJ_GETTER(Kotlin_Iterator_next, KRef iterator); +KInt Kotlin_Collection_getSize(KRef collection); +KBoolean Kotlin_Set_contains(KRef set, KRef element); +OBJ_GETTER(Kotlin_Set_getElement, KRef set, KRef element); +OBJ_GETTER(Kotlin_Set_iterator, KRef set); +void Kotlin_MutableCollection_removeObject(KRef collection, KRef element); +void Kotlin_MutableCollection_addObject(KRef list, KRef obj); +OBJ_GETTER(Kotlin_MutableSet_createWithCapacity, KInt capacity); + +KInt Kotlin_Map_getSize(KRef map); +KBoolean Kotlin_Map_containsKey(KRef map, KRef key); +OBJ_GETTER(Kotlin_Map_get, KRef map, KRef key); +OBJ_GETTER(Kotlin_Map_keyIterator, KRef map); +OBJ_GETTER(Kotlin_List_get, KRef list, KInt index); + +OBJ_GETTER(Kotlin_MutableMap_createWithCapacity, KInt capacity); +void Kotlin_MutableMap_set(KRef map, KRef key, KRef value); +void Kotlin_MutableMap_remove(KRef map, KRef key); +void Kotlin_MutableList_addObjectAtIndex(KRef list, KInt index, KRef obj); +void Kotlin_MutableList_removeObjectAtIndex(KRef list, KInt index); +void Kotlin_MutableList_removeLastObject(KRef list); +void Kotlin_MutableList_setObject(KRef list, KInt index, KRef obj); + +void Kotlin_NSEnumeratorAsKIterator_done(KRef thiz); +void Kotlin_NSEnumeratorAsKIterator_setNext(KRef thiz, KRef value); + +void Kotlin_ObjCExport_ThrowCollectionTooLarge(); +void Kotlin_ObjCExport_ThrowCollectionConcurrentModification(); + +} // extern "C" + +// Objective-C collections can't store `nil`, and the common convention is to use `NSNull.null` instead. +// Follow the convention when converting Kotlin `null`: + +static inline id refToObjCOrNSNull(KRef obj) { + if (obj == nullptr) { + return NSNull.null; + } else { + return Kotlin_ObjCExport_refToObjC(obj); + } +} + +static inline OBJ_GETTER(refFromObjCOrNSNull, id obj) { + if (obj == NSNull.null) { + RETURN_OBJ(nullptr); + } else { + RETURN_RESULT_OF(Kotlin_ObjCExport_refFromObjC, obj); + } +} + +static inline KInt objCCapacityToKotlin(NSUInteger capacity) { + KInt max = std::numeric_limits::max(); + return capacity > max ? max : capacity; +} + +static inline KInt objCSizeToKotlinOrThrow(NSUInteger size) { + if (size > std::numeric_limits::max()) { + Kotlin_ObjCExport_ThrowCollectionTooLarge(); + } + + return size; +} + +static inline KInt objCIndexToKotlinOrThrow(NSUInteger index) { + if (index > std::numeric_limits::max()) { + ThrowArrayIndexOutOfBoundsException(); + } + + return index; +} + +static inline OBJ_GETTER(invokeAndAssociate, KRef (*func)(KRef* result), id obj) { + Kotlin_initRuntimeIfNeeded(); + + KRef kotlinObj = func(OBJ_RESULT); + + RuntimeAssert(HasAssociatedObjectField(kotlinObj), ""); + SetAssociatedObject(kotlinObj, obj); + + return kotlinObj; +} + +@interface NSArray (NSArrayToKotlin) +@end; + +@implementation NSArray (NSArrayToKotlin) +-(KRef)toKotlin:(KRef*)OBJ_RESULT { + RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSArrayAsKList_create, objc_retain(self)); +} + +-(void)releaseAsAssociatedObject { + objc_release(self); +} +@end; + +@interface NSMutableArray (NSMutableArrayToKotlin) +@end; + +@implementation NSMutableArray (NSArrayToKotlin) +-(KRef)toKotlin:(KRef*)OBJ_RESULT { + RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSMutableArrayAsKMutableList_create, objc_retain(self)); +} + +-(void)releaseAsAssociatedObject { + objc_release(self); +} +@end; + + +@interface NSSet (NSSetToKotlin) +@end; + +@implementation NSSet (NSSetToKotlin) +-(KRef)toKotlin:(KRef*)OBJ_RESULT { + RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSSetAsKSet_create, objc_retain(self)); +} + +-(void)releaseAsAssociatedObject { + objc_release(self); +} + +@end; + +@interface NSDictionary (NSDictionaryToKotlin) +@end; + +@implementation NSDictionary (NSDictionaryToKotlin) +-(KRef)toKotlin:(KRef*)OBJ_RESULT { + RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSDictionaryAsKMap_create, objc_retain(self)); +} + +-(void)releaseAsAssociatedObject { + objc_release(self); +} + +@end; + +@interface KIteratorAsNSEnumerator : NSEnumerator +@end; + +@implementation KIteratorAsNSEnumerator { + KRef iterator; +} + +-(void)dealloc { + UpdateRef(&iterator, nullptr); + [super dealloc]; +} + ++(id)createWithKIterator:(KRef)iterator { + KIteratorAsNSEnumerator* result = [[[KIteratorAsNSEnumerator alloc] init] autorelease]; + UpdateRef(&result->iterator, iterator); + return result; +} + +- (id)nextObject { + if (Kotlin_Iterator_hasNext(iterator)) { + ObjHolder holder; + return refToObjCOrNSNull(Kotlin_Iterator_next(iterator, holder.slot())); + } else { + return nullptr; + } +} +@end; + +@interface KListAsNSArray : NSArray +@end; + +@implementation KListAsNSArray { + KRef list; +} + +-(void)dealloc { + UpdateRef(&list, nullptr); + [super dealloc]; +} + ++(id)createWithKList:(KRef)list { + KListAsNSArray* result = [[[KListAsNSArray alloc] init] autorelease]; + UpdateRef(&result->list, list); + return result; +} + +-(KRef)toKotlin:(KRef*)OBJ_RESULT { + RETURN_OBJ(list); +} + +-(id)objectAtIndex:(NSUInteger)index { + ObjHolder kotlinValueHolder; + KRef kotlinValue = Kotlin_List_get(list, index, kotlinValueHolder.slot()); + return refToObjCOrNSNull(kotlinValue); +} + +-(NSUInteger)count { + return Kotlin_Collection_getSize(list); +} + +@end; + +@interface KMutableListAsNSMutableArray : NSMutableArray +@end; + +@implementation KMutableListAsNSMutableArray { + KRef list; +} + +-(void)dealloc { + UpdateRef(&list, nullptr); + [super dealloc]; +} + ++(id)createWithKList:(KRef)list { + KMutableListAsNSMutableArray* result = [[[KMutableListAsNSMutableArray alloc] init] autorelease]; + UpdateRef(&result->list, list); + return result; +} + +-(KRef)toKotlin:(KRef*)OBJ_RESULT { + RETURN_OBJ(list); +} + +-(id)objectAtIndex:(NSUInteger)index { + ObjHolder kotlinValueHolder; + KRef kotlinValue = Kotlin_List_get(list, index, kotlinValueHolder.slot()); + return refToObjCOrNSNull(kotlinValue); +} + +-(NSUInteger)count { + return Kotlin_Collection_getSize(list); +} + +- (void)insertObject:(id)anObject atIndex:(NSUInteger)index { + ObjHolder holder; + KRef kotlinObject = refFromObjCOrNSNull(anObject, holder.slot()); + Kotlin_MutableList_addObjectAtIndex(list, objCIndexToKotlinOrThrow(index), kotlinObject); +} + +- (void)removeObjectAtIndex:(NSUInteger)index { + Kotlin_MutableList_removeObjectAtIndex(list, objCIndexToKotlinOrThrow(index)); +} + +- (void)addObject:(id)anObject { + ObjHolder holder; + Kotlin_MutableCollection_addObject(list, refFromObjCOrNSNull(anObject, holder.slot())); +} + +- (void)removeLastObject { + Kotlin_MutableList_removeLastObject(list); +} + +- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject { + ObjHolder holder; + KRef kotlinObject = refFromObjCOrNSNull(anObject, holder.slot()); + Kotlin_MutableList_setObject(list, objCIndexToKotlinOrThrow(index), kotlinObject); +} + +@end; + +@interface KSetAsNSSet : NSSet +@end; + +static inline id KSet_getElement(KRef set, id object) { + if (object == NSNull.null) { + return Kotlin_Set_contains(set, nullptr) ? object : nullptr; + } else { + ObjHolder requestHolder, resultHolder; + KRef request = Kotlin_ObjCExport_refFromObjC(object, requestHolder.slot()); + KRef result = Kotlin_Set_getElement(set, request, resultHolder.slot()); + return refToObjCOrNSNull(result); + } +} + +@implementation KSetAsNSSet { + KRef set; +} + +-(void)dealloc { + UpdateRef(&set, nullptr); + [super dealloc]; +} + ++(id)createWithKSet:(KRef)set { + KSetAsNSSet* result = [[[KSetAsNSSet alloc] init] autorelease]; + UpdateRef(&result->set, set); + return result; +} + +-(KRef)toKotlin:(KRef*)OBJ_RESULT { + RETURN_OBJ(set); +} + +-(NSUInteger) count { + return Kotlin_Collection_getSize(set); +} + +- (id)member:(id)object { + return KSet_getElement(set, object); +} + +// Not mandatory, just an optimization: +- (BOOL)containsObject:(id)anObject { + ObjHolder holder; + return Kotlin_Set_contains(set, refFromObjCOrNSNull(anObject, holder.slot())); +} + +- (NSEnumerator*)objectEnumerator { + ObjHolder holder; + return [KIteratorAsNSEnumerator createWithKIterator:Kotlin_Set_iterator(set, holder.slot())]; +} +@end; + +@interface KotlinMutableSet : NSMutableSet +@end; + +@implementation KotlinMutableSet { + KRef set; +} + +-(instancetype)init { + if (self = [super init]) { + Kotlin_initRuntimeIfNeeded(); + Kotlin_MutableSet_createWithCapacity(8, &self->set); + } + + return self; +} + +- (instancetype)initWithCapacity:(NSUInteger)numItems { + if (self = [super init]) { + Kotlin_initRuntimeIfNeeded(); + Kotlin_MutableSet_createWithCapacity(objCCapacityToKotlin(numItems), &self->set); + } + + return self; +} + +- (instancetype)initWithObjects:(const id _Nonnull [_Nullable])objects count:(NSUInteger)cnt { + if (self = [self initWithCapacity:cnt]) { + for (NSUInteger i = 0; i < cnt; ++i) { + [self addObject:objects[i]]; + } + } + + return self; +} + +// TODO: what about +// - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder +// ? + +-(void)dealloc { + UpdateRef(&set, nullptr); + [super dealloc]; +} + ++(id)createWithKSet:(KRef)set { + KotlinMutableSet* result = [[[super allocWithZone:nullptr] init] autorelease]; + UpdateRef(&result->set, set); + return result; +} + +-(KRef)toKotlin:(KRef*)OBJ_RESULT { + RETURN_OBJ(set); +} + +-(NSUInteger) count { + return Kotlin_Collection_getSize(set); +} + +- (id)member:(id)object { + return KSet_getElement(set, object); +} + +// Not mandatory, just an optimization: +- (BOOL)containsObject:(id)anObject { + ObjHolder holder; + return Kotlin_Set_contains(set, refFromObjCOrNSNull(anObject, holder.slot())); +} + +- (NSEnumerator*)objectEnumerator { + ObjHolder holder; + return [KIteratorAsNSEnumerator createWithKIterator:Kotlin_Set_iterator(set, holder.slot())]; +} + +- (void)addObject:(id)object { + ObjHolder holder; + Kotlin_MutableCollection_addObject(set, refFromObjCOrNSNull(object, holder.slot())); +} + +- (void)removeObject:(id)object { + ObjHolder holder; + Kotlin_MutableCollection_removeObject(set, refFromObjCOrNSNull(object, holder.slot())); +} +@end; + +@interface KMapAsNSDictionary : NSDictionary +@end; + +static inline id KMap_get(KRef map, id aKey) { + ObjHolder keyHolder, valueHolder; + + KRef kotlinKey = refFromObjCOrNSNull(aKey, keyHolder.slot()); + KRef kotlinValue = Kotlin_Map_get(map, kotlinKey, valueHolder.slot()); + + if (kotlinValue == nullptr) { + // Either null or not found. + return Kotlin_Map_containsKey(map, kotlinKey) ? NSNull.null : nullptr; + } else { + return refToObjCOrNSNull(kotlinValue); + } +} + +@implementation KMapAsNSDictionary { + KRef map; +} + +-(void)dealloc { + UpdateRef(&map, nullptr); + [super dealloc]; +} + ++(id)createWithKMap:(KRef)map { + KMapAsNSDictionary* result = [[[KMapAsNSDictionary alloc] init] autorelease]; + UpdateRef(&result->map, map); + return result; +} + +-(KRef)toKotlin:(KRef*)OBJ_RESULT { + RETURN_OBJ(map); +} + +// According to documentation, initWithObjects:forKeys:count: is required to be overridden when subclassing. +// But that doesn't make any sense, since this class can't be arbitrary initialized. + +-(NSUInteger) count { + return Kotlin_Map_getSize(map); +} + +- (id)objectForKey:(id)aKey { + return KMap_get(map, aKey); +} + +- (NSEnumerator *)keyEnumerator { + ObjHolder holder; + return [KIteratorAsNSEnumerator createWithKIterator:Kotlin_Map_keyIterator(map, holder.slot())]; +} + +@end; + +@interface KotlinMutableDictionary : NSMutableDictionary +@end; + +@implementation KotlinMutableDictionary { + KRef map; +} + +-(void)dealloc { + UpdateRef(&map, nullptr); + [super dealloc]; +} + +-(instancetype)init { + if (self = [super init]) { + Kotlin_initRuntimeIfNeeded(); + Kotlin_MutableMap_createWithCapacity(8, &self->map); + } + return self; +} + +// 'initWithObjects:forKeys:count:' seems to be implemented in base class. + +// TODO: what about +// - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder +// ? + +- (instancetype)initWithCapacity:(NSUInteger)numItems { + if (self = [super init]) { + Kotlin_initRuntimeIfNeeded(); + Kotlin_MutableMap_createWithCapacity(objCCapacityToKotlin(numItems), &self->map); + } + return self; +} + ++(id)createWithKMap:(KRef)map { + KotlinMutableDictionary* result = [[[KotlinMutableDictionary alloc] init] autorelease]; + UpdateRef(&result->map, map); + return result; +} + +-(KRef)toKotlin:(KRef*)OBJ_RESULT { + RETURN_OBJ(map); +} + +-(NSUInteger) count { + return Kotlin_Map_getSize(map); +} + +- (id)objectForKey:(id)aKey { + return KMap_get(map, aKey); +} + +- (NSEnumerator *)keyEnumerator { + ObjHolder holder; + return [KIteratorAsNSEnumerator createWithKIterator:Kotlin_Map_keyIterator(map, holder.slot())]; +} + +- (void)setObject:(id)anObject forKey:(id)aKey { + ObjHolder keyHolder, valueHolder; + + id keyCopy = [aKey copyWithZone:nullptr]; // Correspond to the expected NSMutableDictionary behaviour. + KRef kotlinKey = refFromObjCOrNSNull(keyCopy, keyHolder.slot()); + objc_release(keyCopy); + + KRef kotlinValue = refFromObjCOrNSNull(anObject, valueHolder.slot()); + + Kotlin_MutableMap_set(map, kotlinKey, kotlinValue); +} + +- (void)removeObjectForKey:(id)aKey { + ObjHolder holder; + KRef kotlinKey = refFromObjCOrNSNull(aKey, holder.slot()); + + Kotlin_MutableMap_remove(map, kotlinKey); +} + +@end; + +@interface NSEnumerator (NSEnumeratorAsAssociatedObject) +@end; + +@implementation NSEnumerator (NSEnumeratorAsAssociatedObject) +-(void)releaseAsAssociatedObject { + objc_release(self); +} +@end; + +// Referenced from the generated code: + +extern "C" id Kotlin_Interop_CreateNSArrayFromKList(KRef obj) { + return [KListAsNSArray createWithKList:obj]; +} + +extern "C" id Kotlin_Interop_CreateNSMutableArrayFromKList(KRef obj) { + return [KMutableListAsNSMutableArray createWithKList:obj]; +} + +extern "C" id Kotlin_Interop_CreateNSSetFromKSet(KRef obj) { + return [KSetAsNSSet createWithKSet:obj]; +} + +extern "C" id Kotlin_Interop_CreateKotlinMutableSetFromKSet(KRef obj) { + return [KotlinMutableSet createWithKSet:obj]; +} + +extern "C" id Kotlin_Interop_CreateNSDictionaryFromKMap(KRef obj) { + return [KMapAsNSDictionary createWithKMap:obj]; +} + +extern "C" id Kotlin_Interop_CreateKotlinMutableDictonaryFromKMap(KRef obj) { + return [KotlinMutableDictionary createWithKMap:obj]; +} + +// Imported to ObjCExportUtils.kt: + +extern "C" KInt Kotlin_NSArrayAsKList_getSize(KRef obj) { + NSArray* array = (NSArray*) GetAssociatedObject(obj); + return objCSizeToKotlinOrThrow([array count]); +} + +extern "C" OBJ_GETTER(Kotlin_NSArrayAsKList_get, KRef obj, KInt index) { + NSArray* array = (NSArray*) GetAssociatedObject(obj); + id element = [array objectAtIndex:index]; + RETURN_RESULT_OF(refFromObjCOrNSNull, element); +} + +extern "C" void Kotlin_NSMutableArrayAsKMutableList_add(KRef thiz, KInt index, KRef element) { + NSMutableArray* mutableArray = (NSMutableArray*) GetAssociatedObject(thiz); + [mutableArray insertObject:refToObjCOrNSNull(element) atIndex:index]; +} + +extern "C" OBJ_GETTER(Kotlin_NSMutableArrayAsKMutableList_removeAt, KRef thiz, KInt index) { + NSMutableArray* mutableArray = (NSMutableArray*) GetAssociatedObject(thiz); + + KRef res = refFromObjCOrNSNull([mutableArray objectAtIndex:index], OBJ_RESULT); + [mutableArray removeObjectAtIndex:index]; + + return res; +} + +extern "C" OBJ_GETTER(Kotlin_NSMutableArrayAsKMutableList_set, KRef thiz, KInt index, KRef element) { + NSMutableArray* mutableArray = (NSMutableArray*) GetAssociatedObject(thiz); + + KRef res = refFromObjCOrNSNull([mutableArray objectAtIndex:index], OBJ_RESULT); + [mutableArray replaceObjectAtIndex:index withObject:refToObjCOrNSNull(element)]; + + return res; +} + +extern "C" void Kotlin_NSEnumeratorAsKIterator_computeNext(KRef thiz) { + NSEnumerator* enumerator = (NSEnumerator*) GetAssociatedObject(thiz); + id next = [enumerator nextObject]; + if (next == nullptr) { + Kotlin_NSEnumeratorAsKIterator_done(thiz); + } else { + ObjHolder holder; + Kotlin_NSEnumeratorAsKIterator_setNext(thiz, refFromObjCOrNSNull(next, holder.slot())); + } +} + +extern "C" KInt Kotlin_NSSetAsKSet_getSize(KRef thiz) { + NSSet* set = (NSSet*) GetAssociatedObject(thiz); + return objCSizeToKotlinOrThrow(set.count); +} + +extern "C" KBoolean Kotlin_NSSetAsKSet_contains(KRef thiz, KRef element) { + NSSet* set = (NSSet*) GetAssociatedObject(thiz); + return [set containsObject:refToObjCOrNSNull(element)]; +} + +extern "C" OBJ_GETTER(Kotlin_NSSetAsKSet_getElement, KRef thiz, KRef element) { + NSSet* set = (NSSet*) GetAssociatedObject(thiz); + id res = [set member:refToObjCOrNSNull(element)]; + RETURN_RESULT_OF(refFromObjCOrNSNull, res); +} + +static inline OBJ_GETTER(CreateKIteratorFromNSEnumerator, NSEnumerator* enumerator) { + RETURN_RESULT_OF(invokeAndAssociate, Kotlin_NSEnumeratorAsKIterator_create, objc_retain(enumerator)); +} + +extern "C" OBJ_GETTER(Kotlin_NSSetAsKSet_iterator, KRef thiz) { + NSSet* set = (NSSet*) GetAssociatedObject(thiz); + RETURN_RESULT_OF(CreateKIteratorFromNSEnumerator, [set objectEnumerator]); +} + +extern "C" KInt Kotlin_NSDictionaryAsKMap_getSize(KRef thiz) { + NSDictionary* dict = (NSDictionary*) GetAssociatedObject(thiz); + return objCSizeToKotlinOrThrow(dict.count); +} + +extern "C" KBoolean Kotlin_NSDictionaryAsKMap_containsKey(KRef thiz, KRef key) { + NSDictionary* dict = (NSDictionary*) GetAssociatedObject(thiz); + return [dict objectForKey:refToObjCOrNSNull(key)] != nullptr; +} + +extern "C" KBoolean Kotlin_NSDictionaryAsKMap_containsValue(KRef thiz, KRef value) { + NSDictionary* dict = (NSDictionary*) GetAssociatedObject(thiz); + id objCValue = refToObjCOrNSNull(value); + for (id key in dict) { + if ([[dict objectForKey:key] isEqual:objCValue]) { + return true; + } + } + + return false; +} + +extern "C" OBJ_GETTER(Kotlin_NSDictionaryAsKMap_get, KRef thiz, KRef key) { + NSDictionary* dict = (NSDictionary*) GetAssociatedObject(thiz); + id value = [dict objectForKey:refToObjCOrNSNull(key)]; + RETURN_RESULT_OF(refFromObjCOrNSNull, value); +} + +extern "C" OBJ_GETTER(Kotlin_NSDictionaryAsKMap_getOrThrowConcurrentModification, KRef thiz, KRef key) { + NSDictionary* dict = (NSDictionary*) GetAssociatedObject(thiz); + id value = [dict objectForKey:refToObjCOrNSNull(key)]; + if (value == nullptr) { + Kotlin_ObjCExport_ThrowCollectionConcurrentModification(); + } + + RETURN_RESULT_OF(refFromObjCOrNSNull, value); +} + +extern "C" KBoolean Kotlin_NSDictionaryAsKMap_containsEntry(KRef thiz, KRef key, KRef value) { + NSDictionary* dict = (NSDictionary*) GetAssociatedObject(thiz); + return [refToObjCOrNSNull(value) isEqual:[dict objectForKey:refToObjCOrNSNull(key)]]; +} + +extern "C" OBJ_GETTER(Kotlin_NSDictionaryAsKMap_keyIterator, KRef thiz) { + NSDictionary* dict = (NSDictionary*) GetAssociatedObject(thiz); + RETURN_RESULT_OF(CreateKIteratorFromNSEnumerator, [dict keyEnumerator]); +} + +extern "C" OBJ_GETTER(Kotlin_NSDictionaryAsKMap_valueIterator, KRef thiz) { + NSDictionary* dict = (NSDictionary*) GetAssociatedObject(thiz); + RETURN_RESULT_OF(CreateKIteratorFromNSEnumerator, [dict objectEnumerator]); +} + +#else // KONAN_OBJC_INTEROP + +extern "C" KInt Kotlin_NSArrayAsKList_getSize(KRef obj) { + RuntimeAssert(false, "Objective-C interop is disabled"); + return -1; +} + +extern "C" OBJ_GETTER(Kotlin_NSArrayAsKList_get, KRef obj, KInt index) { + RuntimeAssert(false, "Objective-C interop is disabled"); + RETURN_OBJ(nullptr); +} + +extern "C" void Kotlin_NSMutableArrayAsKMutableList_add(KRef thiz, KInt index, KRef element) { + RuntimeAssert(false, "Objective-C interop is disabled"); +} + +extern "C" OBJ_GETTER(Kotlin_NSMutableArrayAsKMutableList_removeAt, KRef thiz, KInt index) { + RuntimeAssert(false, "Objective-C interop is disabled"); + RETURN_OBJ(nullptr); +} + +extern "C" OBJ_GETTER(Kotlin_NSMutableArrayAsKMutableList_set, KRef thiz, KInt index, KRef element) { + RuntimeAssert(false, "Objective-C interop is disabled"); + RETURN_OBJ(nullptr); +} + +extern "C" void Kotlin_NSEnumeratorAsKIterator_computeNext(KRef thiz) { + RuntimeAssert(false, "Objective-C interop is disabled"); +} + +extern "C" KInt Kotlin_NSSetAsKSet_getSize(KRef thiz) { + RuntimeAssert(false, "Objective-C interop is disabled"); + return -1; +} + +extern "C" KBoolean Kotlin_NSSetAsKSet_contains(KRef thiz, KRef element) { + RuntimeAssert(false, "Objective-C interop is disabled"); + return false; +} + +extern "C" OBJ_GETTER(Kotlin_NSSetAsKSet_getElement, KRef thiz, KRef element) { + RuntimeAssert(false, "Objective-C interop is disabled"); + RETURN_OBJ(nullptr); +} + +extern "C" OBJ_GETTER(Kotlin_NSSetAsKSet_iterator, KRef thiz) { + RuntimeAssert(false, "Objective-C interop is disabled"); + RETURN_OBJ(nullptr); +} + +extern "C" KInt Kotlin_NSDictionaryAsKMap_getSize(KRef thiz) { + RuntimeAssert(false, "Objective-C interop is disabled"); + return -1; +} + +extern "C" KBoolean Kotlin_NSDictionaryAsKMap_containsKey(KRef thiz, KRef key) { + RuntimeAssert(false, "Objective-C interop is disabled"); + return false; +} + +extern "C" KBoolean Kotlin_NSDictionaryAsKMap_containsValue(KRef thiz, KRef value) { + RuntimeAssert(false, "Objective-C interop is disabled"); + return false; +} + +extern "C" OBJ_GETTER(Kotlin_NSDictionaryAsKMap_get, KRef thiz, KRef key) { + RuntimeAssert(false, "Objective-C interop is disabled"); + RETURN_OBJ(nullptr); +} + +extern "C" OBJ_GETTER(Kotlin_NSDictionaryAsKMap_getOrThrowConcurrentModification, KRef thiz, KRef key) { + RuntimeAssert(false, "Objective-C interop is disabled"); + RETURN_OBJ(nullptr); +} + +extern "C" KBoolean Kotlin_NSDictionaryAsKMap_containsEntry(KRef thiz, KRef key, KRef value) { + RuntimeAssert(false, "Objective-C interop is disabled"); + return false; +} + +extern "C" OBJ_GETTER(Kotlin_NSDictionaryAsKMap_keyIterator, KRef thiz) { + RuntimeAssert(false, "Objective-C interop is disabled"); + RETURN_OBJ(nullptr); +} + +extern "C" OBJ_GETTER(Kotlin_NSDictionaryAsKMap_valueIterator, KRef thiz) { + RuntimeAssert(false, "Objective-C interop is disabled"); + RETURN_OBJ(nullptr); +} + +#endif // KONAN_OBJC_INTEROP \ No newline at end of file diff --git a/runtime/src/main/cpp/Types.cpp b/runtime/src/main/cpp/Types.cpp index 21b1e9b36a5..2ca4e6e67e4 100644 --- a/runtime/src/main/cpp/Types.cpp +++ b/runtime/src/main/cpp/Types.cpp @@ -62,4 +62,14 @@ OBJ_GETTER(Kotlin_TypeInfo_getRelativeName, KNativePtr typeInfo) { RETURN_OBJ(reinterpret_cast(typeInfo)->relativeName_); } +bool IsSubInterface(const TypeInfo* thiz, const TypeInfo* other) { + for (int i = 0; i < thiz->implementedInterfacesCount_; ++i) { + if (thiz->implementedInterfaces_[i] == other) { + return true; + } + } + + return false; +} + } // extern "C" diff --git a/runtime/src/main/cpp/Types.h b/runtime/src/main/cpp/Types.h index 33338e71065..d490605ba42 100644 --- a/runtime/src/main/cpp/Types.h +++ b/runtime/src/main/cpp/Types.h @@ -88,6 +88,7 @@ extern const TypeInfo* theObjCPointerHolderTypeInfo; KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info) RUNTIME_PURE; void CheckCast(const ObjHeader* obj, const TypeInfo* type_info); KBoolean IsArray(KConstRef obj) RUNTIME_PURE; +bool IsSubInterface(const TypeInfo* thiz, const TypeInfo* other) RUNTIME_PURE; #ifdef __cplusplus } diff --git a/runtime/src/main/kotlin/konan/internal/KonanCollections.kt b/runtime/src/main/kotlin/konan/internal/KonanCollections.kt new file mode 100644 index 00000000000..17bf9bd80a4 --- /dev/null +++ b/runtime/src/main/kotlin/konan/internal/KonanCollections.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package konan.internal + +internal interface KonanSet : Set { + /** + * Searches for the specified element in this set. + * + * @return the element from the set equal to [element], or `null` if no such element found. + */ + fun getElement(element: @UnsafeVariance E): E? +} diff --git a/runtime/src/main/kotlin/konan/internal/ObjCExportUtils.kt b/runtime/src/main/kotlin/konan/internal/ObjCExportUtils.kt new file mode 100644 index 00000000000..81f9ca999ac --- /dev/null +++ b/runtime/src/main/kotlin/konan/internal/ObjCExportUtils.kt @@ -0,0 +1,270 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package konan.internal + +import konan.internal.ExportForCppRuntime + +internal class NSArrayAsKList : AbstractList() { + + override val size: Int get() = getSize() + + @SymbolName("Kotlin_NSArrayAsKList_getSize") + private external fun getSize(): Int + + @SymbolName("Kotlin_NSArrayAsKList_get") + external override fun get(index: Int): Any? +} + +internal class NSMutableArrayAsKMutableList : AbstractMutableList() { + + override val size: Int get() = getSize() + + @SymbolName("Kotlin_NSArrayAsKList_getSize") + private external fun getSize(): Int + + @SymbolName("Kotlin_NSArrayAsKList_get") + external override fun get(index: Int): Any? + + @SymbolName("Kotlin_NSMutableArrayAsKMutableList_add") + external override fun add(index: Int, element: Any?): Unit + + @SymbolName("Kotlin_NSMutableArrayAsKMutableList_removeAt") + external override fun removeAt(index: Int): Any? + + @SymbolName("Kotlin_NSMutableArrayAsKMutableList_set") + external override fun set(index: Int, element: Any?): Any? +} + +internal class NSSetAsKSet : AbstractSet(), konan.internal.KonanSet { + + override val size: Int get() = getSize() + + @SymbolName("Kotlin_NSSetAsKSet_getSize") + private external fun getSize(): Int + + @SymbolName("Kotlin_NSSetAsKSet_contains") + external override fun contains(element: Any?): Boolean + + @SymbolName("Kotlin_NSSetAsKSet_getElement") + external override fun getElement(element: Any?): Any? + + @SymbolName("Kotlin_NSSetAsKSet_iterator") + external override fun iterator(): Iterator +} + +internal class NSDictionaryAsKMap : Map { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Map<*, *>) return false + if (this.size != other.size) return false + + return other.entries.all { this.containsEntry(it.key, it.value) } + } + + override fun hashCode(): Int { + var result = 0 + keyIterator().forEach { key -> + result += key.hashCode() xor this.getOrThrowConcurrentModification(key).hashCode() + } + return result + } + + override fun toString(): String = entries.joinToString(", ", "{", "}") { toString(it.key) + "=" + toString(it.value) } + + private fun toString(o: Any?): String = if (o === this) "(this Map)" else o.toString() + + override val size: Int get() = getSize() + + @SymbolName("Kotlin_NSDictionaryAsKMap_getSize") + private external fun getSize(): Int + + override fun isEmpty(): Boolean = (size == 0) + + @SymbolName("Kotlin_NSDictionaryAsKMap_containsKey") + override external fun containsKey(key: Any?): Boolean + + @SymbolName("Kotlin_NSDictionaryAsKMap_containsValue") + override external fun containsValue(value: Any?): Boolean + + @SymbolName("Kotlin_NSDictionaryAsKMap_get") + external override operator fun get(key: Any?): Any? + + @SymbolName("Kotlin_NSDictionaryAsKMap_getOrThrowConcurrentModification") + private external fun getOrThrowConcurrentModification(key: Any?): Any? + + @SymbolName("Kotlin_NSDictionaryAsKMap_containsEntry") + private external fun containsEntry(key: Any?, value: Any?): Boolean + + // Views + override val keys: Set get() = this.Keys() + + override val values: Collection get() = this.Values() + + override val entries: Set> get() = this.Entries() + + @SymbolName("Kotlin_NSDictionaryAsKMap_keyIterator") + private external fun keyIterator(): Iterator + + private inner class Keys : AbstractSet() { + + override val size: Int get() = this@NSDictionaryAsKMap.size + + override fun iterator(): Iterator = this@NSDictionaryAsKMap.keyIterator() + + override fun contains(element: Any?): Boolean = this@NSDictionaryAsKMap.containsKey(element) + } + + @SymbolName("Kotlin_NSDictionaryAsKMap_valueIterator") + private external fun valueIterator(): Iterator + + private inner class Values : AbstractCollection() { + // TODO: what about equals and hashCode? + + override val size: Int get() = this@NSDictionaryAsKMap.size + + override fun iterator(): Iterator = this@NSDictionaryAsKMap.valueIterator() + + override fun contains(element: Any?): Boolean = this@NSDictionaryAsKMap.containsValue(element) + } + + private inner class Entries : AbstractSet>() { + + override val size: Int get() = this@NSDictionaryAsKMap.size + + @SymbolName("Kotlin_NSSetAsKSet_iterator") + override fun iterator(): Iterator> = this@NSDictionaryAsKMap.EntryIterator() + + override fun contains(element: Map.Entry): Boolean { + return this@NSDictionaryAsKMap.containsEntry(element.key, element.value) + } + } + + private class Entry(override val key: Any?, override val value: Any?) : Map.Entry { + override fun equals(other: Any?): Boolean = + other is Map.Entry<*, *> && + other.key == key && + other.value == value + + override fun hashCode(): Int = key.hashCode() xor value.hashCode() + + override fun toString(): String = "$key=$value" + } + + private inner class EntryIterator : Iterator> { + val keyIterator = this@NSDictionaryAsKMap.keyIterator() + + override fun hasNext(): Boolean = keyIterator.hasNext() + + override fun next(): Map.Entry { + val nextKey = keyIterator.next() + val nextValue = this@NSDictionaryAsKMap.getOrThrowConcurrentModification(nextKey) + + return Entry(nextKey, nextValue) + } + } + +} + +internal class NSEnumeratorAsKIterator : AbstractIterator() { + + @SymbolName("Kotlin_NSEnumeratorAsKIterator_computeNext") + override external fun computeNext() + + @ExportForCppRuntime + private fun Kotlin_NSEnumeratorAsKIterator_done() = this.done() + + @ExportForCppRuntime + private fun Kotlin_NSEnumeratorAsKIterator_setNext(value: Any?) = this.setNext(value) +} + +@ExportForCppRuntime private fun Kotlin_Collection_getSize(collection: Collection<*>): Int = collection.size + +@ExportForCppRuntime private fun Kotlin_List_get(list: List<*>, index: Int): Any? = list.get(index) + +@ExportForCppRuntime private fun Kotlin_MutableList_addObjectAtIndex(list: MutableList, index: Int, obj: Any?) { + list.add(index, obj) +} + +@ExportForCppRuntime private fun Kotlin_MutableList_removeObjectAtIndex(list: MutableList, index: Int) { + list.removeAt(index) +} + +@ExportForCppRuntime private fun Kotlin_MutableCollection_addObject(list: MutableCollection, obj: Any?) { + list.add(obj) +} + +@ExportForCppRuntime private fun Kotlin_MutableList_removeLastObject(list: MutableList) { + list.removeAt(list.lastIndex) +} + +@ExportForCppRuntime private fun Kotlin_MutableList_setObject(list: MutableList, index: Int, obj: Any?) { + list.set(index, obj) +} + +@ExportForCppRuntime private fun Kotlin_MutableCollection_removeObject( + collection: MutableCollection, element: Any? +) { + collection.remove(element) +} + +@ExportForCppRuntime private fun Kotlin_Iterator_hasNext(iterator: Iterator): Boolean = iterator.hasNext() +@ExportForCppRuntime private fun Kotlin_Iterator_next(iterator: Iterator): Any? = iterator.next() + +@ExportForCppRuntime private fun Kotlin_Set_contains(set: Set, element: Any?): Boolean = set.contains(element) + +@ExportForCppRuntime private fun Kotlin_Set_getElement(set: Set, element: Any?): Any? = + if (set is konan.internal.KonanSet) { + set.getElement(element) + } else if (set.contains(element)) { + set.first { it == element } + } else { + null + } + +@ExportForCppRuntime private fun Kotlin_Set_iterator(set: Set): Iterator = set.iterator() +@ExportForCppRuntime private fun Kotlin_MutableSet_createWithCapacity(capacity: Int): MutableSet = + HashSet(capacity) + +@ExportForCppRuntime private fun Kotlin_Map_getSize(map: Map): Int = map.size +@ExportForCppRuntime private fun Kotlin_Map_containsKey(map: Map, key: Any?): Boolean = map.containsKey(key) +@ExportForCppRuntime private fun Kotlin_Map_get(map: Map, key: Any?): Any? = map.get(key) +@ExportForCppRuntime private fun Kotlin_Map_keyIterator(map: Map): Iterator = map.keys.iterator() + +@ExportForCppRuntime private fun Kotlin_MutableMap_createWithCapacity(capacity: Int): MutableMap = + HashMap(capacity) + +@ExportForCppRuntime private fun Kotlin_MutableMap_set(map: MutableMap, key: Any?, value: Any?) { + map.set(key, value) +} +@ExportForCppRuntime private fun Kotlin_MutableMap_remove(map: MutableMap, key: Any?) { + map.remove(key) +} + +@ExportForCppRuntime private fun Kotlin_ObjCExport_ThrowCollectionTooLarge() { + throw Error("an Objective-C collection is too large") +} + +@ExportForCppRuntime private fun Kotlin_ObjCExport_ThrowCollectionConcurrentModification() { + throw Error("an Objective-C collection was modified while iterating") +} + +@ExportForCppRuntime private fun Kotlin_NSArrayAsKList_create() = NSArrayAsKList() +@ExportForCppRuntime private fun Kotlin_NSMutableArrayAsKMutableList_create() = NSMutableArrayAsKMutableList() +@ExportForCppRuntime private fun Kotlin_NSEnumeratorAsKIterator_create() = NSEnumeratorAsKIterator() +@ExportForCppRuntime private fun Kotlin_NSSetAsKSet_create() = NSSetAsKSet() +@ExportForCppRuntime private fun Kotlin_NSDictionaryAsKMap_create() = NSDictionaryAsKMap() diff --git a/runtime/src/main/kotlin/kotlin/collections/HashMap.kt b/runtime/src/main/kotlin/kotlin/collections/HashMap.kt index 5dc9e895ecb..0330e5eea17 100644 --- a/runtime/src/main/kotlin/kotlin/collections/HashMap.kt +++ b/runtime/src/main/kotlin/kotlin/collections/HashMap.kt @@ -368,6 +368,24 @@ class HashMap private constructor( return valuesArray!![index] == entry.value } + internal fun getEntry(entry: Map.Entry): MutableMap.MutableEntry? { + val index = findKey(entry.key) + return if (index < 0 || valuesArray!![index] != entry.value) { + null + } else { + EntryRef(this, index) + } + } + + internal fun getKey(key: K): K? { + val index = findKey(key) + return if (index >= 0) { + keysArray[index]!! + } else { + null + } + } + private fun contentEquals(other: Map<*, *>): Boolean = size == other.size && containsAllEntries(other.entries) internal fun containsAllEntries(m: Collection>): Boolean { @@ -644,11 +662,12 @@ internal class HashMapValues internal constructor( internal class HashMapEntrySet internal constructor( val backing: HashMap -) : MutableSet> { +) : MutableSet>, konan.internal.KonanSet> { override val size: Int get() = backing.size override fun isEmpty(): Boolean = backing.isEmpty() override fun contains(element: MutableMap.MutableEntry): Boolean = backing.containsEntry(element) + override fun getElement(element: MutableMap.MutableEntry): MutableMap.MutableEntry? = backing.getEntry(element) override fun clear() = backing.clear() override fun add(element: MutableMap.MutableEntry): Boolean = backing.putEntry(element) override fun remove(element: MutableMap.MutableEntry): Boolean = backing.removeEntry(element) diff --git a/runtime/src/main/kotlin/kotlin/collections/HashSet.kt b/runtime/src/main/kotlin/kotlin/collections/HashSet.kt index eaaa0ca782c..47e3140cb3a 100644 --- a/runtime/src/main/kotlin/kotlin/collections/HashSet.kt +++ b/runtime/src/main/kotlin/kotlin/collections/HashSet.kt @@ -18,7 +18,7 @@ package kotlin.collections class HashSet internal constructor( val backing: HashMap -) : MutableSet, AbstractMutableCollection() { +) : MutableSet, AbstractMutableCollection(), konan.internal.KonanSet { constructor() : this(HashMap()) @@ -31,6 +31,7 @@ class HashSet internal constructor( override val size: Int get() = backing.size override fun isEmpty(): Boolean = backing.isEmpty() override fun contains(element: K): Boolean = backing.containsKey(element) + override fun getElement(element: K): K? = backing.getKey(element) override fun clear() = backing.clear() override fun add(element: K): Boolean = backing.addKey(element) >= 0 override fun remove(element: K): Boolean = backing.removeKey(element) >= 0 diff --git a/runtime/src/main/kotlin/kotlin/collections/Sets.kt b/runtime/src/main/kotlin/kotlin/collections/Sets.kt index 49e96622c1a..92cee016207 100644 --- a/runtime/src/main/kotlin/kotlin/collections/Sets.kt +++ b/runtime/src/main/kotlin/kotlin/collections/Sets.kt @@ -17,7 +17,7 @@ package kotlin.collections -internal object EmptySet : Set { +internal object EmptySet : Set, konan.internal.KonanSet { override fun equals(other: Any?): Boolean = other is Set<*> && other.isEmpty() override fun hashCode(): Int = 0 override fun toString(): String = "[]" @@ -25,6 +25,7 @@ internal object EmptySet : Set { override val size: Int get() = 0 override fun isEmpty(): Boolean = true override fun contains(element: Nothing): Boolean = false + override fun getElement(element: Nothing): Nothing? = null override fun containsAll(elements: Collection): Boolean = elements.isEmpty() override fun iterator(): Iterator = EmptyIterator