diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt index 04706ab553b..ded0d2929b4 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt @@ -20,7 +20,7 @@ internal open class ObjCCodeGenerator(val codegen: CodeGenerator) { return load(selectorRef.llvm) } - fun FunctionGenerationContext.genGetSystemClass(name: String): LLVMValueRef { + fun FunctionGenerationContext.genGetLinkedClass(name: String): LLVMValueRef { val classRef = dataGenerator.genClassRef(name) return load(classRef.llvm) } 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 001d71c7707..bbf9d236794 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 @@ -169,6 +169,9 @@ internal class ObjCExportCodeGenerator( dataGenerator.emitEmptyClass(namer.getPackageName(fqName), namer.kotlinAnyName) } + NSNumberKind.values().mapNotNull { it.mappedKotlinClassId }.forEach { + dataGenerator.exportClass("Kotlin${it.shortClassName}") + } dataGenerator.exportClass("KotlinMutableSet") dataGenerator.exportClass("KotlinMutableDictionary") @@ -345,7 +348,7 @@ private fun ObjCExportCodeGenerator.emitBoxConverters() { emitBoxConverter(irBuiltIns.shortClass, ObjCValueType.SHORT, "numberWithShort:") emitBoxConverter(irBuiltIns.intClass, ObjCValueType.INT, "numberWithInt:") emitBoxConverter(irBuiltIns.longClass, ObjCValueType.LONG_LONG, "numberWithLongLong:") - emitBoxConverter(symbols.uByte, ObjCValueType.UNSIGNED_CHAR, "numberWithUnsignedChar") + emitBoxConverter(symbols.uByte, ObjCValueType.UNSIGNED_CHAR, "numberWithUnsignedChar:") emitBoxConverter(symbols.uShort, ObjCValueType.UNSIGNED_SHORT, "numberWithUnsignedShort:") emitBoxConverter(symbols.uInt, ObjCValueType.UNSIGNED_INT, "numberWithUnsignedInt:") emitBoxConverter(symbols.uLong, ObjCValueType.UNSIGNED_LONG_LONG, "numberWithUnsignedLongLong:") @@ -371,8 +374,8 @@ private fun ObjCExportCodeGenerator.emitBoxConverter( val value = kotlinToObjC(kotlinValue, objCValueType) - val nsNumber = genGetSystemClass("NSNumber") - ret(genSendMessage(int8TypePtr, nsNumber, nsNumberFactorySelector, value)) + val nsNumberSubclass = genGetLinkedClass("Kotlin${boxClass.name}") + ret(genSendMessage(int8TypePtr, nsNumberSubclass, nsNumberFactorySelector, value)) } LLVMSetLinkage(converter, LLVMLinkage.LLVMPrivateLinkage) 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 963437d96a5..22bea5327a9 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.UnsignedType import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.constants.ArrayValue @@ -44,6 +45,16 @@ abstract class ObjCExportHeaderGenerator( internal val generatedClasses = mutableSetOf() internal val topLevel = mutableMapOf>() + private val mappedToNSNumber: List = with(builtIns) { + val result = mutableListOf(boolean, byte, short, int, long, float, double) + + UnsignedType.values().mapTo(result) { unsignedType -> + moduleDescriptor.findClassAcrossModuleDependencies(unsignedType.classId)!! + } + + result + } + private val customTypeMappers: Map = with(builtIns) { val result = mutableListOf() @@ -56,16 +67,13 @@ abstract class ObjCExportHeaderGenerator( result += CustomTypeMapper.Collection(generator, map, "NSDictionary") result += CustomTypeMapper.Collection(generator, mutableMap, namer.mutableMapName) - for (descriptor in listOf(boolean, 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. + NSNumberKind.values().forEach { // TODO: NSNumber seem to have different equality semantics. - result += CustomTypeMapper.Simple(descriptor, "NSNumber") - } + if (it.mappedKotlinClassId != null) { + val descriptor = moduleDescriptor.findClassAcrossModuleDependencies(it.mappedKotlinClassId)!! + result += CustomTypeMapper.Simple(descriptor, namer.numberBoxName(descriptor)) + } - for (unsignedType in UnsignedType.values()) { - val descriptor = moduleDescriptor.findClassAcrossModuleDependencies(unsignedType.classId)!! - result += CustomTypeMapper.Simple(descriptor, "NSNumber") } result += CustomTypeMapper.Simple(string, "NSString") @@ -121,7 +129,7 @@ abstract class ObjCExportHeaderGenerator( namer.mutableSetName, generics = listOf("ObjectType"), superClass = "NSMutableSet", - attributes = listOf("objc_runtime_name(\"KotlinMutableSet\")") + attributes = listOf(objcRuntimeNameAttribute("KotlinMutableSet")) )) // TODO: only if appears @@ -129,13 +137,15 @@ abstract class ObjCExportHeaderGenerator( namer.mutableMapName, generics = listOf("KeyType", "ObjectType"), superClass = "NSMutableDictionary", - attributes = listOf("objc_runtime_name(\"KotlinMutableDictionary\")") + attributes = listOf(objcRuntimeNameAttribute("KotlinMutableDictionary")) )) stubs.add(ObjCInterface("NSError", categoryName = "NSErrorKotlinException", members = buildMembers { +ObjCProperty("kotlinException", null, ObjCNullableReferenceType(ObjCIdType), listOf("readonly")) })) + genKotlinNumbers() + val packageFragments = moduleDescriptor.getPackageFragments() packageFragments.forEach { packageFragment -> @@ -195,6 +205,67 @@ abstract class ObjCExportHeaderGenerator( return stubs } + 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, + attributes = listOf(objcRuntimeNameAttribute("KotlinNumber")) + )) + + NSNumberKind.values().forEach { + if (it.mappedKotlinClassId != null) { + stubs += genKotlinNumber(it.mappedKotlinClassId, it) + } + } + } + + private fun genKotlinNumber(kotlinClassId: ClassId, kind: NSNumberKind): ObjCInterface { + val descriptor = moduleDescriptor.findClassAcrossModuleDependencies(kotlinClassId)!! + val name = namer.numberBoxName(descriptor) + + val members = buildMembers { + +nsNumberFactory(kind) + +nsNumberInit(kind) + } + return ObjCInterface( + name, + superClass = namer.kotlinNumberName, + members = members, + attributes = listOf(objcRuntimeNameAttribute("Kotlin${kotlinClassId.shortClassName}")) + ) + } + + private fun nsNumberInit(kind: NSNumberKind, attributes: List = emptyList()): ObjCMethod { + return ObjCMethod( + null, + false, + ObjCInstanceType, + listOf(kind.factorySelector), + listOf(ObjCParameter("value", null, kind.objCType)), + attributes + ) + } + + private fun nsNumberFactory(kind: NSNumberKind, attributes: List = emptyList()): ObjCMethod { + return ObjCMethod( + null, + true, + ObjCInstanceType, + listOf(kind.initSelector), + listOf(ObjCParameter("value", null, kind.objCType)), + attributes + ) + } + private fun translateClassName(descriptor: ClassDescriptor): String = classOrInterfaceToName.getOrPut(descriptor) { assert(mapper.shouldBeExposed(descriptor)) val forwardDeclarations = if (descriptor.isInterface) protocolForwardDeclarations else classForwardDeclarations @@ -549,6 +620,7 @@ abstract class ObjCExportHeaderGenerator( } private fun swiftNameAttribute(swiftName: String) = "swift_name(\"$swiftName\")" + private fun objcRuntimeNameAttribute(name: String) = "objc_runtime_name(\"$name\")" private val methodsWithThrowAnnotationConsidered = mutableSetOf() 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 4bc2174b8d4..b11fa3e8205 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 @@ -12,8 +12,11 @@ import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenDescriptors import org.jetbrains.kotlin.backend.konan.descriptors.isArray import org.jetbrains.kotlin.backend.konan.descriptors.isInterface import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.builtins.PrimitiveType import org.jetbrains.kotlin.builtins.UnsignedType import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.resolve.descriptorUtil.* import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils @@ -215,3 +218,49 @@ internal fun ObjCExportMapper.bridgePropertyType(descriptor: PropertyDescriptor) return bridgeType(descriptor.type) } + +internal enum class NSNumberKind(val mappedKotlinClassId: ClassId?, val objCType: ObjCType) { + CHAR(PrimitiveType.BYTE, "char"), + UNSIGNED_CHAR(UnsignedType.UBYTE, "unsigned char"), + SHORT(PrimitiveType.SHORT, "short"), + UNSIGNED_SHORT(UnsignedType.USHORT, "unsigned short"), + INT(PrimitiveType.INT, "int"), + UNSIGNED_INT(UnsignedType.UINT, "unsigned int"), + LONG("long"), + UNSIGNED_LONG("unsigned long"), + LONG_LONG(PrimitiveType.LONG, "long long"), + UNSIGNED_LONG_LONG(UnsignedType.ULONG, "unsigned long long"), + FLOAT(PrimitiveType.FLOAT, "float"), + DOUBLE(PrimitiveType.DOUBLE, "double"), + BOOL(PrimitiveType.BOOLEAN, "BOOL"), + INTEGER("NSInteger"), + UNSIGNED_INTEGER("NSUInteger") + + ; + + // UNSIGNED_SHORT -> unsignedShort + private val kindName = this.name.split('_') + .joinToString("") { it.toLowerCase().capitalize() }.decapitalize() + + + val valueSelector = kindName // unsignedShort + val initSelector = "initWith${kindName.capitalize()}:" // initWithUnsignedShort: + val factorySelector = "numberWith${kindName.capitalize()}:" // numberWithUnsignedShort: + + constructor( + mappedKotlinClassId: ClassId?, + objCPrimitiveTypeName: String + ) : this(mappedKotlinClassId, ObjCPrimitiveType(objCPrimitiveTypeName)) + + constructor( + primitiveType: PrimitiveType, + objCPrimitiveTypeName: String + ) : this(ClassId.topLevel(primitiveType.typeFqName), objCPrimitiveTypeName) + + constructor( + unsignedType: UnsignedType, + objCPrimitiveTypeName: String + ) : this(unsignedType.classId, objCPrimitiveTypeName) + + constructor(objCPrimitiveTypeName: String) : this(null, ObjCPrimitiveType(objCPrimitiveTypeName)) +} 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 6a595efc936..cae6561a113 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 @@ -29,6 +29,9 @@ internal class ObjCExportNamer(val moduleDescriptor: ModuleDescriptor, val mutableSetName = "${topLevelNamePrefix}MutableSet" val mutableMapName = "${topLevelNamePrefix}MutableDictionary" + fun numberBoxName(descriptor: ClassDescriptor): String = "$topLevelNamePrefix${descriptor.name.asString()}" + val kotlinNumberName = "${topLevelNamePrefix}Number" + private val methodSelectors = object : Mapping() { // Try to avoid clashing with critical NSObject instance methods: diff --git a/backend.native/tests/framework/stdlib/stdlib.swift b/backend.native/tests/framework/stdlib/stdlib.swift index b6fea6af624..01c42714189 100644 --- a/backend.native/tests/framework/stdlib/stdlib.swift +++ b/backend.native/tests/framework/stdlib/stdlib.swift @@ -134,7 +134,7 @@ class StdlibTests : TestProvider { print("MAP: \(k) - \(v)") } - var smd = StdlibMutableDictionary() + var smd = StdlibMutableDictionary() smd.setObject(333, forKey: "333" as NSString) try assertEquals(actual: smd.object(forKey: "333" as NSString) as! Int, expected: 333, "Add element to dict") diff --git a/backend.native/tests/framework/values/values.kt b/backend.native/tests/framework/values/values.kt index e6f5b068256..b4728be5b22 100644 --- a/backend.native/tests/framework/values/values.kt +++ b/backend.native/tests/framework/values/values.kt @@ -32,6 +32,37 @@ val nanFloatVal: Float = Float.NaN val infDoubleVal: Double = Double.POSITIVE_INFINITY val infFloatVal: Float = Float.NEGATIVE_INFINITY +private fun T.toNullable(): T? = this + +fun box(booleanValue: Boolean) = booleanValue.toNullable() +fun box(byteValue: Byte) = byteValue.toNullable() +fun box(shortValue: Short) = shortValue.toNullable() +fun box(intValue: Int) = intValue.toNullable() +fun box(longValue: Long) = longValue.toNullable() +fun box(uByteValue: UByte) = uByteValue.toNullable() +fun box(uShortValue: UShort) = uShortValue.toNullable() +fun box(uIntValue: UInt) = uIntValue.toNullable() +fun box(uLongValue: ULong) = uLongValue.toNullable() +fun box(floatValue: Float) = floatValue.toNullable() +fun box(doubleValue: Double) = doubleValue.toNullable() + +private inline fun ensureEquals(actual: T?, expected: T) { + if (actual !is T) error(T::class) + if (actual != expected) error(T::class) +} + +fun ensureEqualBooleans(actual: Boolean?, expected: Boolean) = ensureEquals(actual, expected) +fun ensureEqualBytes(actual: Byte?, expected: Byte) = ensureEquals(actual, expected) +fun ensureEqualShorts(actual: Short?, expected: Short) = ensureEquals(actual, expected) +fun ensureEqualInts(actual: Int?, expected: Int) = ensureEquals(actual, expected) +fun ensureEqualLongs(actual: Long?, expected: Long) = ensureEquals(actual, expected) +fun ensureEqualUBytes(actual: UByte?, expected: UByte) = ensureEquals(actual, expected) +fun ensureEqualUShorts(actual: UShort?, expected: UShort) = ensureEquals(actual, expected) +fun ensureEqualUInts(actual: UInt?, expected: UInt) = ensureEquals(actual, expected) +fun ensureEqualULongs(actual: ULong?, expected: ULong) = ensureEquals(actual, expected) +fun ensureEqualFloats(actual: Float?, expected: Float) = ensureEquals(actual, expected) +fun ensureEqualDoubles(actual: Double?, expected: Double) = ensureEquals(actual, expected) + // Boolean val boolVal: Boolean = true val boolAnyVal: Any = false diff --git a/backend.native/tests/framework/values/values.swift b/backend.native/tests/framework/values/values.swift index cfd7e6e6589..81ba4f2ccb2 100644 --- a/backend.native/tests/framework/values/values.swift +++ b/backend.native/tests/framework/values/values.swift @@ -91,6 +91,116 @@ func testDoubles() throws { try assertEquals(actual: Values.infFloatVal, expected: -Float.infinity, "-Inf float") } +func testNumbers() throws { + try assertEquals(actual: ValuesBoolean(value: true).boolValue, expected: true) + try assertEquals(actual: ValuesBoolean(value: false).intValue, expected: 0) + try assertEquals(actual: ValuesBoolean(value: true), expected: true) + try assertFalse(ValuesBoolean(value: false) as! Bool) + + try assertEquals(actual: ValuesByte(value: -1).int8Value, expected: -1) + try assertEquals(actual: ValuesByte(value: -1).int32Value, expected: -1) + try assertEquals(actual: ValuesByte(value: -1).doubleValue, expected: -1.0) + try assertEquals(actual: ValuesByte(value: -1), expected: NSNumber(value: Int64(-1))) + try assertFalse(ValuesByte(value: -1) == NSNumber(value: -1.5)) + try assertEquals(actual: ValuesByte(value: -1), expected: -1) + try assertTrue(ValuesByte(value: -1) == -1) + try assertFalse(ValuesByte(value: -1) == 1) + try assertEquals(actual: ValuesByte(value: -1) as! Int32, expected: -1) + + try assertEquals(actual: ValuesShort(value: 111).int16Value, expected: 111) + try assertEquals(actual: ValuesShort(value: -15) as! Int16, expected: -15) + try assertEquals(actual: ValuesShort(value: 47), expected: 47) + + try assertEquals(actual: ValuesInt(value: 99).int32Value, expected: 99) + try assertEquals(actual: ValuesInt(value: -1) as! Int32, expected: -1) + try assertEquals(actual: ValuesInt(value: 72), expected: 72) + + try assertEquals(actual: ValuesLong(value: 65).int64Value, expected: 65) + try assertEquals(actual: ValuesLong(value: 10000000000) as! Int64, expected: 10000000000) + try assertEquals(actual: ValuesLong(value: 8), expected: 8) + + try assertEquals(actual: ValuesUByte(value: 17).uint8Value, expected: 17) + try assertEquals(actual: ValuesUByte(value: 42) as! UInt8, expected: 42) + try assertEquals(actual: 88, expected: ValuesUByte(value: 88)) + + try assertEquals(actual: ValuesUShort(value: 40000).uint16Value, expected: 40000) + try assertEquals(actual: ValuesUShort(value: 1) as! UInt16, expected: UInt16(1)) + try assertEquals(actual: ValuesUShort(value: 65000), expected: 65000) + + try assertEquals(actual: ValuesUInt(value: 3).uint32Value, expected: 3) + try assertEquals(actual: ValuesUInt(value: UInt32.max) as! UInt32, expected: UInt32.max) + try assertEquals(actual: ValuesUInt(value: 2), expected: 2) + + try assertEquals(actual: ValuesULong(value: 55).uint64Value, expected: 55) + try assertEquals(actual: ValuesULong(value: 0) as! UInt64, expected: 0) + try assertEquals(actual: ValuesULong(value: 7), expected: 7) + + try assertEquals(actual: ValuesFloat(value: 1.0).floatValue, expected: 1.0) + try assertEquals(actual: ValuesFloat(value: 22.0) as! Float, expected: 22) + try assertEquals(actual: ValuesFloat(value: 41.0), expected: 41) + try assertEquals(actual: ValuesFloat(value: -5.5), expected: -5.5) + + try assertEquals(actual: ValuesDouble(value: 0.5).doubleValue, expected: 0.5) + try assertEquals(actual: ValuesDouble(value: 45.0) as! Double, expected: 45) + try assertEquals(actual: ValuesDouble(value: 89.0), expected: 89) + try assertEquals(actual: ValuesDouble(value: -3.7), expected: -3.7) + + Values.ensureEqualBooleans(actual: ValuesBoolean(value: true), expected: true) + Values.ensureEqualBooleans(actual: false, expected: false) + + Values.ensureEqualBytes(actual: ValuesByte(value: 42), expected: 42) + Values.ensureEqualBytes(actual: -11, expected: -11) + + Values.ensureEqualShorts(actual: ValuesShort(value: 256), expected: 256) + Values.ensureEqualShorts(actual: -1, expected: -1) + + Values.ensureEqualInts(actual: ValuesInt(value: 100000), expected: 100000) + Values.ensureEqualInts(actual: -7, expected: -7) + + Values.ensureEqualLongs(actual: ValuesLong(value: Int64.max), expected: Int64.max) + Values.ensureEqualLongs(actual: 17, expected: 17) + + Values.ensureEqualUBytes(actual: ValuesUByte(value: 6), expected: 6) + Values.ensureEqualUBytes(actual: 255, expected: 255) + + Values.ensureEqualUShorts(actual: ValuesUShort(value: 300), expected: 300) + Values.ensureEqualUShorts(actual: 65535, expected: UInt16.max) + + Values.ensureEqualUInts(actual: ValuesUInt(value: 70000), expected: 70000) + Values.ensureEqualUInts(actual: 48, expected: 48) + + Values.ensureEqualULongs(actual: ValuesULong(value: UInt64.max), expected: UInt64.max) + Values.ensureEqualULongs(actual: 39, expected: 39) + + Values.ensureEqualFloats(actual: ValuesFloat(value: 36.6), expected: 36.6) + Values.ensureEqualFloats(actual: 49.5, expected: 49.5) + Values.ensureEqualFloats(actual: 18, expected: 18.0) + + Values.ensureEqualDoubles(actual: ValuesDouble(value: 12.34), expected: 12.34) + Values.ensureEqualDoubles(actual: 56.78, expected: 56.78) + Values.ensureEqualDoubles(actual: 3, expected: 3) + + func checkBox(_ value: T, _ boxFunction: (T) -> B?) throws { + let box = boxFunction(value)! + try assertEquals(actual: box as! T, expected: value) + print(type(of: box)) + print(B.self) + try assertTrue(box.isKind(of: B.self)) + } + + try checkBox(true, Values.box) + try checkBox(Int8(-1), Values.box) + try checkBox(Int16(-2), Values.box) + try checkBox(Int32(-3), Values.box) + try checkBox(Int64(-4), Values.box) + try checkBox(UInt8(5), Values.box) + try checkBox(UInt16(6), Values.box) + try checkBox(UInt32(7), Values.box) + try checkBox(UInt64(8), Values.box) + try checkBox(Float(8.7), Values.box) + try checkBox(Double(9.4), Values.box) +} + func testLists() throws { let numbersList = Values.numbersList let gold = [1, 2, 13] @@ -307,6 +417,7 @@ class ValuesTests : TestProvider { TestCase(name: "TestValues", method: withAutorelease(testVals)), TestCase(name: "TestVars", method: withAutorelease(testVars)), TestCase(name: "TestDoubles", method: withAutorelease(testDoubles)), + TestCase(name: "TestNumbers", method: withAutorelease(testNumbers)), TestCase(name: "TestLists", method: withAutorelease(testLists)), TestCase(name: "TestLazyValues", method: withAutorelease(testLazyVal)), TestCase(name: "TestDelegatedProperties", method: withAutorelease(testDelegatedProp)), diff --git a/runtime/src/main/cpp/ObjCExportNumbers.mm b/runtime/src/main/cpp/ObjCExportNumbers.mm new file mode 100644 index 00000000000..3795e329890 --- /dev/null +++ b/runtime/src/main/cpp/ObjCExportNumbers.mm @@ -0,0 +1,710 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if KONAN_OBJC_INTEROP + +#import +#import +#import +#import + +#import "ObjCExport.h" +#import "Runtime.h" +#import "Utils.h" + +extern "C" { + +OBJ_GETTER(Kotlin_boxBoolean, KBoolean value); +OBJ_GETTER(Kotlin_boxByte, KByte value); +OBJ_GETTER(Kotlin_boxShort, KShort value); +OBJ_GETTER(Kotlin_boxInt, KInt value); +OBJ_GETTER(Kotlin_boxLong, KLong value); +OBJ_GETTER(Kotlin_boxUByte, KUByte value); +OBJ_GETTER(Kotlin_boxUShort, KUShort value); +OBJ_GETTER(Kotlin_boxUInt, KUInt value); +OBJ_GETTER(Kotlin_boxULong, KULong value); +OBJ_GETTER(Kotlin_boxFloat, KFloat value); +OBJ_GETTER(Kotlin_boxDouble, KDouble value); + +} + +#pragma clang diagnostic ignored "-Wobjc-designated-initializers" + +@interface KotlinNumber : NSNumber +@end; + +[[ noreturn ]] static void incorrectNumberInitialization(KotlinNumber* self, SEL _cmd) { + [NSException raise:NSGenericException format:@"%@ can't be initialized with %s, use properly typed initialized", + NSStringFromClass([self class]), sel_getName(_cmd)]; + + abort(); +} + +[[ noreturn ]] static void incorrectNumberFactory(Class self, SEL _cmd) { + [NSException raise:NSGenericException format:@"%@ can't be created with %s, use properly typed factory", + NSStringFromClass(self), sel_getName(_cmd)]; + + abort(); +} + +@implementation KotlinNumber : NSNumber + +- (NSNumber *)initWithBool:(BOOL)value { incorrectNumberInitialization(self, _cmd); } +- (NSNumber *)initWithChar:(char)value { incorrectNumberInitialization(self, _cmd); } +- (NSNumber *)initWithShort:(short)value { incorrectNumberInitialization(self, _cmd); } +- (NSNumber *)initWithInt:(int)value { incorrectNumberInitialization(self, _cmd); } +- (NSNumber *)initWithInteger:(NSInteger)value { incorrectNumberInitialization(self, _cmd); } +- (NSNumber *)initWithLong:(long)value { incorrectNumberInitialization(self, _cmd); } +- (NSNumber *)initWithLongLong:(long long)value { incorrectNumberInitialization(self, _cmd); } +- (NSNumber *)initWithUnsignedChar:(unsigned char)value { incorrectNumberInitialization(self, _cmd); } +- (NSNumber *)initWithUnsignedShort:(unsigned short)value { incorrectNumberInitialization(self, _cmd); } +- (NSNumber *)initWithUnsignedInt:(unsigned int)value { incorrectNumberInitialization(self, _cmd); } +- (NSNumber *)initWithUnsignedInteger:(NSUInteger)value { incorrectNumberInitialization(self, _cmd); } +- (NSNumber *)initWithUnsignedLong:(unsigned long)value { incorrectNumberInitialization(self, _cmd); } +- (NSNumber *)initWithUnsignedLongLong:(unsigned long long)value { incorrectNumberInitialization(self, _cmd); } +- (NSNumber *)initWithFloat:(float)value { incorrectNumberInitialization(self, _cmd); } +- (NSNumber *)initWithDouble:(double)value { incorrectNumberInitialization(self, _cmd); } + ++ (NSNumber *)numberWithBool:(BOOL)value { incorrectNumberFactory(self, _cmd); } ++ (NSNumber *)numberWithChar:(char)value { incorrectNumberFactory(self, _cmd); } ++ (NSNumber *)numberWithShort:(short)value { incorrectNumberFactory(self, _cmd); } ++ (NSNumber *)numberWithInt:(int)value { incorrectNumberFactory(self, _cmd); } ++ (NSNumber *)numberWithInteger:(NSInteger)value { incorrectNumberFactory(self, _cmd); } ++ (NSNumber *)numberWithLong:(long)value { incorrectNumberFactory(self, _cmd); } ++ (NSNumber *)numberWithLongLong:(long long)value { incorrectNumberFactory(self, _cmd); } ++ (NSNumber *)numberWithUnsignedChar:(unsigned char)value { incorrectNumberFactory(self, _cmd); } ++ (NSNumber *)numberWithUnsignedShort:(unsigned short)value { incorrectNumberFactory(self, _cmd); } ++ (NSNumber *)numberWithUnsignedInt:(unsigned int)value { incorrectNumberFactory(self, _cmd); } ++ (NSNumber *)numberWithUnsignedLong:(unsigned long)value { incorrectNumberFactory(self, _cmd); } ++ (NSNumber *)numberWithUnsignedInteger:(NSUInteger)value { incorrectNumberFactory(self, _cmd); } ++ (NSNumber *)numberWithUnsignedLongLong:(unsigned long long)value { incorrectNumberFactory(self, _cmd); } ++ (NSNumber *)numberWithFloat:(float)value { incorrectNumberFactory(self, _cmd); } ++ (NSNumber *)numberWithDouble:(double)value { incorrectNumberFactory(self, _cmd); } + +@end; + +/* +The code below is generated by: + + +fun main(args: Array) { + println(genBoolean()) + println(genInteger("Byte", "c", "char")) + println(genInteger("Short", "s", "short")) + println(genInteger("Int", "i", "int")) + println(genInteger("Long", "q", "long long")) + println(genInteger("UByte", "C", "unsigned char")) + println(genInteger("UShort", "S", "unsigned short")) + println(genInteger("UInt", "I", "unsigned int")) + println(genInteger("ULong", "Q", "unsigned long long")) + println(genFloating("Float", "f", "float")) + println(genFloating("Double", "d", "double")) +} + +private fun genBoolean(): String = """ +@interface KotlinBoolean : KotlinNumber +@end; + +@implementation KotlinBoolean { + BOOL value_; +} + +- (instancetype)initWithBool:(BOOL)value { + self = [super init]; + value_ = value; + return self; +} + ++ (instancetype)numberWithBool:(BOOL)value { + KotlinBoolean* result = [[self new] autorelease]; + result->value_ = value; + return result; +} + +- (BOOL)boolValue { + return value_; +} + +- (char)charValue { + return value_; +} + +- (const char *)objCType { + return "c"; +} + +-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { + RETURN_RESULT_OF(Kotlin_boxBoolean, value_); +} + +@end; + +""".trimIndent() + +private fun genInteger( + name: String, + encoding: String, + cType: String, + kind: String = getNSNumberKind(cType) +) = """ +@interface Kotlin$name : KotlinNumber +@end; + +@implementation Kotlin$name { + $cType value_; +} + +- (instancetype)initWith${kind.capitalize()}:($cType)value { + self = [super init]; + value_ = value; + return self; +} + ++ (instancetype)numberWith${kind.capitalize()}:($cType)value { + Kotlin$name* result = [[self new] autorelease]; + result->value_ = value; + return result; +} + +// Required to convert Swift integer literals. +- (instancetype)initWithInteger:(NSInteger)value { + self = [super init]; + value_ = value; // TODO: check fits. + return self; +} + +- ($cType)${kind}Value { + return value_; +} + +- (const char *)objCType { + return "$encoding"; +} + +-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { + RETURN_RESULT_OF(Kotlin_box$name, value_); +} + +@end; + +""".trimIndent() + +private fun getNSNumberKind(cType: String) = + cType.split(' ').joinToString("") { it.capitalize() }.decapitalize() + +private fun genFloating( + name: String, + encoding: String, + cType: String, + kind: String = getNSNumberKind(cType) +): String = """ +@interface Kotlin$name : KotlinNumber +@end; + +@implementation Kotlin$name { + $cType value_; +} + +- (instancetype)initWith${kind.capitalize()}:($cType)value { + self = [super init]; + value_ = value; + return self; +} + ++ (instancetype)numberWith${kind.capitalize()}:($cType)value { + Kotlin$name* result = [[self new] autorelease]; + result->value_ = value; + return result; +} + +// Required to convert Swift integer literals. +- (instancetype)initWithInteger:(NSInteger)value { + self = [super init]; + value_ = value; // TODO: check fits. + return self; +} +${if (cType != "double") """ +// Required to convert Swift floating literals. +- (instancetype)initWithDouble:(double)value { + self = [super init]; + value_ = value; // TODO: check fits. + return self; +} +""" else ""} +- ($cType)${kind}Value { + return value_; +} + +- (const char *)objCType { + return "$encoding"; +} + +-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { + RETURN_RESULT_OF(Kotlin_box$name, value_); +} + +@end; + +""".trimIndent() +*/ + +// TODO: consider generating it by compiler. + +@interface KotlinBoolean : KotlinNumber +@end; + +@implementation KotlinBoolean { + BOOL value_; +} + +- (instancetype)initWithBool:(BOOL)value { + self = [super init]; + value_ = value; + return self; +} + ++ (instancetype)numberWithBool:(BOOL)value { + KotlinBoolean* result = [[self new] autorelease]; + result->value_ = value; + return result; +} + +- (BOOL)boolValue { + return value_; +} + +- (char)charValue { + return value_; +} + +- (const char *)objCType { + return "c"; +} + +-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { + RETURN_RESULT_OF(Kotlin_boxBoolean, value_); +} + +@end; + +@interface KotlinByte : KotlinNumber +@end; + +@implementation KotlinByte { + char value_; +} + +- (instancetype)initWithChar:(char)value { + self = [super init]; + value_ = value; + return self; +} + ++ (instancetype)numberWithChar:(char)value { + KotlinByte* result = [[self new] autorelease]; + result->value_ = value; + return result; +} + +// Required to convert Swift integer literals. +- (instancetype)initWithInteger:(NSInteger)value { + self = [super init]; + value_ = value; // TODO: check fits. + return self; +} + +- (char)charValue { + return value_; +} + +- (const char *)objCType { + return "c"; +} + +-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { + RETURN_RESULT_OF(Kotlin_boxByte, value_); +} + +@end; + +@interface KotlinShort : KotlinNumber +@end; + +@implementation KotlinShort { + short value_; +} + +- (instancetype)initWithShort:(short)value { + self = [super init]; + value_ = value; + return self; +} + ++ (instancetype)numberWithShort:(short)value { + KotlinShort* result = [[self new] autorelease]; + result->value_ = value; + return result; +} + +// Required to convert Swift integer literals. +- (instancetype)initWithInteger:(NSInteger)value { + self = [super init]; + value_ = value; // TODO: check fits. + return self; +} + +- (short)shortValue { + return value_; +} + +- (const char *)objCType { + return "s"; +} + +-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { + RETURN_RESULT_OF(Kotlin_boxShort, value_); +} + +@end; + +@interface KotlinInt : KotlinNumber +@end; + +@implementation KotlinInt { + int value_; +} + +- (instancetype)initWithInt:(int)value { + self = [super init]; + value_ = value; + return self; +} + ++ (instancetype)numberWithInt:(int)value { + KotlinInt* result = [[self new] autorelease]; + result->value_ = value; + return result; +} + +// Required to convert Swift integer literals. +- (instancetype)initWithInteger:(NSInteger)value { + self = [super init]; + value_ = value; // TODO: check fits. + return self; +} + +- (int)intValue { + return value_; +} + +- (const char *)objCType { + return "i"; +} + +-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { + RETURN_RESULT_OF(Kotlin_boxInt, value_); +} + +@end; + +@interface KotlinLong : KotlinNumber +@end; + +@implementation KotlinLong { + long long value_; +} + +- (instancetype)initWithLongLong:(long long)value { + self = [super init]; + value_ = value; + return self; +} + ++ (instancetype)numberWithLongLong:(long long)value { + KotlinLong* result = [[self new] autorelease]; + result->value_ = value; + return result; +} + +// Required to convert Swift integer literals. +- (instancetype)initWithInteger:(NSInteger)value { + self = [super init]; + value_ = value; // TODO: check fits. + return self; +} + +- (long long)longLongValue { + return value_; +} + +- (const char *)objCType { + return "q"; +} + +-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { + RETURN_RESULT_OF(Kotlin_boxLong, value_); +} + +@end; + +@interface KotlinUByte : KotlinNumber +@end; + +@implementation KotlinUByte { + unsigned char value_; +} + +- (instancetype)initWithUnsignedChar:(unsigned char)value { + self = [super init]; + value_ = value; + return self; +} + ++ (instancetype)numberWithUnsignedChar:(unsigned char)value { + KotlinUByte* result = [[self new] autorelease]; + result->value_ = value; + return result; +} + +// Required to convert Swift integer literals. +- (instancetype)initWithInteger:(NSInteger)value { + self = [super init]; + value_ = value; // TODO: check fits. + return self; +} + +- (unsigned char)unsignedCharValue { + return value_; +} + +- (const char *)objCType { + return "C"; +} + +-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { + RETURN_RESULT_OF(Kotlin_boxUByte, value_); +} + +@end; + +@interface KotlinUShort : KotlinNumber +@end; + +@implementation KotlinUShort { + unsigned short value_; +} + +- (instancetype)initWithUnsignedShort:(unsigned short)value { + self = [super init]; + value_ = value; + return self; +} + ++ (instancetype)numberWithUnsignedShort:(unsigned short)value { + KotlinUShort* result = [[self new] autorelease]; + result->value_ = value; + return result; +} + +// Required to convert Swift integer literals. +- (instancetype)initWithInteger:(NSInteger)value { + self = [super init]; + value_ = value; // TODO: check fits. + return self; +} + +- (unsigned short)unsignedShortValue { + return value_; +} + +- (const char *)objCType { + return "S"; +} + +-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { + RETURN_RESULT_OF(Kotlin_boxUShort, value_); +} + +@end; + +@interface KotlinUInt : KotlinNumber +@end; + +@implementation KotlinUInt { + unsigned int value_; +} + +- (instancetype)initWithUnsignedInt:(unsigned int)value { + self = [super init]; + value_ = value; + return self; +} + ++ (instancetype)numberWithUnsignedInt:(unsigned int)value { + KotlinUInt* result = [[self new] autorelease]; + result->value_ = value; + return result; +} + +// Required to convert Swift integer literals. +- (instancetype)initWithInteger:(NSInteger)value { + self = [super init]; + value_ = value; // TODO: check fits. + return self; +} + +- (unsigned int)unsignedIntValue { + return value_; +} + +- (const char *)objCType { + return "I"; +} + +-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { + RETURN_RESULT_OF(Kotlin_boxUInt, value_); +} + +@end; + +@interface KotlinULong : KotlinNumber +@end; + +@implementation KotlinULong { + unsigned long long value_; +} + +- (instancetype)initWithUnsignedLongLong:(unsigned long long)value { + self = [super init]; + value_ = value; + return self; +} + ++ (instancetype)numberWithUnsignedLongLong:(unsigned long long)value { + KotlinULong* result = [[self new] autorelease]; + result->value_ = value; + return result; +} + +// Required to convert Swift integer literals. +- (instancetype)initWithInteger:(NSInteger)value { + self = [super init]; + value_ = value; // TODO: check fits. + return self; +} + +- (unsigned long long)unsignedLongLongValue { + return value_; +} + +- (const char *)objCType { + return "Q"; +} + +-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { + RETURN_RESULT_OF(Kotlin_boxULong, value_); +} + +@end; + +@interface KotlinFloat : KotlinNumber +@end; + +@implementation KotlinFloat { + float value_; +} + +- (instancetype)initWithFloat:(float)value { + self = [super init]; + value_ = value; + return self; +} + ++ (instancetype)numberWithFloat:(float)value { + KotlinFloat* result = [[self new] autorelease]; + result->value_ = value; + return result; +} + +// Required to convert Swift integer literals. +- (instancetype)initWithInteger:(NSInteger)value { + self = [super init]; + value_ = value; // TODO: check fits. + return self; +} + +// Required to convert Swift floating literals. +- (instancetype)initWithDouble:(double)value { + self = [super init]; + value_ = value; // TODO: check fits. + return self; +} + +- (float)floatValue { + return value_; +} + +- (const char *)objCType { + return "f"; +} + +-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { + RETURN_RESULT_OF(Kotlin_boxFloat, value_); +} + +@end; + +@interface KotlinDouble : KotlinNumber +@end; + +@implementation KotlinDouble { + double value_; +} + +- (instancetype)initWithDouble:(double)value { + self = [super init]; + value_ = value; + return self; +} + ++ (instancetype)numberWithDouble:(double)value { + KotlinDouble* result = [[self new] autorelease]; + result->value_ = value; + return result; +} + +// Required to convert Swift integer literals. +- (instancetype)initWithInteger:(NSInteger)value { + self = [super init]; + value_ = value; // TODO: check fits. + return self; +} + +- (double)doubleValue { + return value_; +} + +- (const char *)objCType { + return "d"; +} + +-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT { + RETURN_RESULT_OF(Kotlin_boxDouble, value_); +} + +@end; + +#endif // KONAN_OBJC_INTEROP \ No newline at end of file