From 4e285a5ef40d9d929d583d1e3beb7f83a0a236d5 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Thu, 31 Aug 2017 14:05:48 +0300 Subject: [PATCH] Implement `equals`, `hashCode` and `toString` for Objective-C objects --- .../kotlin/kotlinx/cinterop/ObjectiveCImpl.kt | 28 ++++++++++----- backend.native/tests/build.gradle | 3 +- backend.native/tests/interop/objc/smoke.kt | 15 ++++++++ runtime/src/main/cpp/ObjCInteropUtils.mm | 35 ++++++++++++++++--- 4 files changed, 68 insertions(+), 13 deletions(-) diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt index 7e96b5a4f9e..dddd9c4c5ec 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCImpl.kt @@ -23,9 +23,9 @@ interface ObjCClass : ObjCObject typealias ObjCObjectMeta = ObjCClass abstract class ObjCObjectBase protected constructor() : ObjCObject { - final override fun equals(other: Any?): Boolean = TODO() - final override fun hashCode(): Int = TODO() - final override fun toString(): String = TODO() + final override fun equals(other: Any?): Boolean = this.uncheckedCast().equals(other) + final override fun hashCode(): Int = ObjCHashCode(this.rawPtr) + final override fun toString(): String = ObjCToString(this.rawPtr) } abstract class ObjCObjectBaseMeta protected constructor() : ObjCObjectBase(), ObjCObjectMeta {} @@ -42,9 +42,11 @@ class ObjCPointerHolder(inline val rawPtr: NativePtr) { objc_retain(rawPtr) } - final override fun equals(other: Any?): Boolean = TODO() - final override fun hashCode(): Int = TODO() - final override fun toString(): String = TODO() + final override fun equals(other: Any?): Boolean = + (other is ObjCPointerHolder) && ObjCEquals(this.rawPtr, other.rawPtr) + + final override fun hashCode(): Int = ObjCHashCode(this.rawPtr) + final override fun toString(): String = ObjCToString(this.rawPtr) } @konan.internal.Intrinsic @@ -146,12 +148,22 @@ private external fun getObjCClass(): NativePtr // Konan runtme: -@SymbolName("CreateNSStringFromKString") +@SymbolName("Kotlin_Interop_CreateNSStringFromKString") external fun CreateNSStringFromKString(str: String?): NativePtr -@SymbolName("CreateKStringFromNSString") +@SymbolName("Kotlin_Interop_CreateKStringFromNSString") external fun CreateKStringFromNSString(ptr: NativePtr): String? +@SymbolName("Kotlin_Interop_ObjCToString") +private external fun ObjCToString(ptr: NativePtr): String + +@SymbolName("Kotlin_Interop_ObjCHashCode") +private external fun ObjCHashCode(ptr: NativePtr): Int + +@SymbolName("Kotlin_Interop_ObjCEquals") +private external fun ObjCEquals(ptr: NativePtr, otherPtr: NativePtr): Boolean + + // Objective-C runtime: @SymbolName("objc_retainAutoreleaseReturnValue") diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index bb6ee6412b5..be8893864a5 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -2101,7 +2101,8 @@ if (isMac()) { task interop_objc_smoke(type: RunInteropKonanTest) { disabled = (project.testTarget == 'wasm32') // No interop for wasm yet. - goldValue = "Hello, World!\nKotlin says: Hello, everybody!\nHello from Kotlin\n2, 1\nDeallocated\nDeallocated\n" + goldValue = "Hello, World!\nKotlin says: Hello, everybody!\nHello from Kotlin\n2, 1\n" + + "true\ntrue\nDeallocated\nDeallocated\n" source = "interop/objc/smoke.kt" interop = 'objcSmoke' diff --git a/backend.native/tests/interop/objc/smoke.kt b/backend.native/tests/interop/objc/smoke.kt index 23f03627e45..f27991f76ef 100644 --- a/backend.native/tests/interop/objc/smoke.kt +++ b/backend.native/tests/interop/objc/smoke.kt @@ -23,6 +23,21 @@ fun run() { replacePairElements(pair, 1, 2) pair.swap() println("${pair.first}, ${pair.second}") + + // equals and hashCode (virtually): + val map = mapOf(foo to pair, pair to foo) + + // equals (directly): + if (!foo.equals(pair)) { + // toString (directly): + println(map[pair].toString() + map[foo].toString() == foo.description() + pair.description()) + } + + // hashCode (directly): + if (foo.hashCode() == foo.hash().toInt()) { + // toString (virtually): + println(map.keys.map { it.toString() }.min() == foo.description()) + } } fun MutablePairProtocol.swap() { diff --git a/runtime/src/main/cpp/ObjCInteropUtils.mm b/runtime/src/main/cpp/ObjCInteropUtils.mm index 02b5d0832db..8a07aa82106 100644 --- a/runtime/src/main/cpp/ObjCInteropUtils.mm +++ b/runtime/src/main/cpp/ObjCInteropUtils.mm @@ -44,7 +44,7 @@ namespace { extern "C" { -NSString* CreateNSStringFromKString(const ArrayHeader* str) { +NSString* Kotlin_Interop_CreateNSStringFromKString(const ArrayHeader* str) { if (str == nullptr) { return nullptr; } @@ -58,7 +58,7 @@ NSString* CreateNSStringFromKString(const ArrayHeader* str) { return result; } -OBJ_GETTER(CreateKStringFromNSString, NSString* str) { +OBJ_GETTER(Kotlin_Interop_CreateKStringFromNSString, NSString* str) { if (str == nullptr) { RETURN_OBJ(nullptr); } @@ -73,6 +73,18 @@ OBJ_GETTER(CreateKStringFromNSString, NSString* str) { RETURN_OBJ(result->obj()); } +OBJ_GETTER(Kotlin_Interop_ObjCToString, id ptr) { + RETURN_RESULT_OF(Kotlin_Interop_CreateKStringFromNSString, ptr.description); +} + +KInt Kotlin_Interop_ObjCHashCode(id ptr) { + return (KInt) ptr.hash; +} + +KBoolean Kotlin_Interop_ObjCEquals(id ptr, id otherPtr) { + return [ptr isEqual:otherPtr]; +} + // Note: this body is used for init methods with signatures differing from this; // it is correct on arm64 and x86_64, because the body uses only the first two arguments which are fixed, // and returns pointers. @@ -97,16 +109,31 @@ id MissingInitImp(id self, SEL _cmd) { extern "C" { -void* CreateNSStringFromKString(const ArrayHeader* str) { +void* Kotlin_Interop_CreateNSStringFromKString(const ArrayHeader* str) { RuntimeAssert(false, "Objective-C interop is disabled"); return nullptr; } -OBJ_GETTER(CreateKStringFromNSString, void* str) { +OBJ_GETTER(Kotlin_Interop_CreateKStringFromNSString, void* str) { RuntimeAssert(false, "Objective-C interop is disabled"); RETURN_OBJ(nullptr); } +OBJ_GETTER(Kotlin_Interop_ObjCToString, KNativePtr ptr) { + RuntimeAssert(false, "Objective-C interop is disabled"); + RETURN_OBJ(nullptr); +} + +KInt Kotlin_Interop_ObjCHashCode(KNativePtr ptr) { + RuntimeAssert(false, "Objective-C interop is disabled"); + return 0; +} + +KBoolean Kotlin_Interop_ObjCEquals(KNativePtr ptr, KNativePtr otherPtr) { + RuntimeAssert(false, "Objective-C interop is disabled"); + return 0; +} + } // extern "C" #endif // KONAN_OBJC_INTEROP \ No newline at end of file