Skip to content

Commit a840c69

Browse files
committed
Use Swift Equatable and Hashable conformances from ObjC
If a Swift type implements Equatable and/or Hashable and we then pass that object into ObjC, we want ObjC `isEqual:` and `hashValue` to use that. This allows ObjC code to build ObjC collections of Swift objects. * Support for Hashable struct/enum types was implemented in #4124 * Support for Equatable struct/enum types was implemented in swiftlang#68720 * This implements support for Hashable and Equatable _class_ types Caveats: 1. This does a lot of dynamic lookup work for each operation, so is inherently rather slow. Unlike the struct/enum case, there is no convenient place to cache the conformance information, so it's not clear that there is a viable way to make it significantly faster. 2. This is a behavioral change to low-level support code. There is a risk of breaking code that may be relying on the old behavior.
1 parent e8761b8 commit a840c69

File tree

3 files changed

+91
-6
lines changed

3 files changed

+91
-6
lines changed

stdlib/public/runtime/SwiftObject.mm

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "../CompatibilityOverride/CompatibilityOverride.h"
4242
#include "ErrorObject.h"
4343
#include "Private.h"
44+
#include "SwiftEquatableSupport.h"
4445
#include "SwiftObject.h"
4546
#include "SwiftValue.h"
4647
#include "WeakReference.h"
@@ -373,11 +374,62 @@ + (BOOL)conformsToProtocol:(Protocol*)proto {
373374
}
374375

375376
- (NSUInteger)hash {
376-
return (NSUInteger)self;
377+
auto selfMetadata = _swift_getClassOfAllocated(self);
378+
auto hashableConformance =
379+
reinterpret_cast<const hashable_support::HashableWitnessTable *>(
380+
swift_conformsToProtocolCommon(
381+
selfMetadata, &hashable_support::HashableProtocolDescriptor));
382+
if (hashableConformance == NULL) {
383+
return (NSUInteger)self;
384+
}
385+
return _swift_stdlib_Hashable_hashValue_indirect(
386+
&self, selfMetadata, hashableConformance);
377387
}
378388

379-
- (BOOL)isEqual:(id)object {
380-
return self == object;
389+
- (BOOL)isEqual:(id)other {
390+
if (self == other) {
391+
return YES;
392+
}
393+
// Both objects must be `SwiftObject` in ObjC
394+
if (object_getClass(other) != object_getClass((id)self)) {
395+
return NO;
396+
}
397+
398+
// TODO: Bincompat check -- return NO if this is an old executable
399+
400+
// Get Swift type for self and other
401+
auto selfMetadata = _swift_getClassOfAllocated(self);
402+
auto otherMetadata = _swift_getClassOfAllocated(other);
403+
404+
// Find common parent Swift class
405+
const ClassMetadata * parentMetadata = NULL;
406+
// Collect parents of self
407+
std::unordered_set<const ClassMetadata *> selfParents;
408+
for (auto m = selfMetadata; m != NULL; m = m->Superclass) {
409+
selfParents.emplace(m);
410+
}
411+
// Find first parent of other that is also a parent of self
412+
for (auto m = otherMetadata; m != NULL; m = m->Superclass) {
413+
if (selfParents.find(m) != selfParents.end()) {
414+
parentMetadata = m;
415+
break;
416+
}
417+
}
418+
// If there's no common Swift parent class, we can't compare them
419+
if (parentMetadata == NULL) {
420+
return NO;
421+
}
422+
423+
auto equatableConformance =
424+
reinterpret_cast<const equatable_support::EquatableWitnessTable *>(
425+
swift_conformsToProtocolCommon(
426+
parentMetadata, &equatable_support::EquatableProtocolDescriptor));
427+
if (equatableConformance == NULL) {
428+
return NO;
429+
}
430+
431+
return _swift_stdlib_Equatable_isEqual_indirect(
432+
&self, &other, parentMetadata, equatableConformance);
381433
}
382434

383435
- (id)performSelector:(SEL)aSelector {

test/stdlib/Inputs/SwiftObjectNSObject/SwiftObjectNSObject.m

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ void HackSwiftObject()
8080
class_addMethod(cls, @selector(perform2::), (IMP)Perform2, "@@:@@");
8181
}
8282

83-
void TestSwiftObjectNSObject(id c, id d)
83+
void TestSwiftObjectNSObject(id c, id d,
84+
id e1a, id e1b, id e2,
85+
id h1a, id h1b, id h2,
86+
NSUInteger hash1, NSUInteger hash2)
8487
{
8588
printf("TestSwiftObjectNSObject\n");
8689

@@ -159,12 +162,24 @@ void TestSwiftObjectNSObject(id c, id d)
159162
expectFalse([C_meta isEqual:D_meta]);
160163
expectFalse([S_meta isEqual:C_meta]);
161164

165+
// Check that ObjC isEqual delegates to Equatable
166+
expectTrue([e1a isEqual: e1b]);
167+
expectFalse([e1a isEqual: e2]);
168+
expectFalse([e1b isEqual: e2]);
169+
170+
// Check that ObjC isEqual delegates to Hashable
171+
expectTrue([h1a isEqual: h1b]);
172+
expectFalse([h1a isEqual: h2]);
173+
expectFalse([h1b isEqual: h2]);
162174

163175
printf("NSObjectProtocol.hash\n");
164176

165177
expectTrue ([d hash] + [c hash] + [D hash] + [C hash] + [S hash] +
166178
[D_meta hash] + [C_meta hash] + [S_meta hash] != 0);
167179

180+
expectTrue([h1a hash] == hash1);
181+
expectTrue([h1b hash] == hash1);
182+
expectTrue([h2 hash] == hash2);
168183

169184
printf("NSObjectProtocol.self\n");
170185

test/stdlib/SwiftObjectNSObject.swift

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,24 @@ class D : C {
3939
@objc override class func cClassOverride() -> Int { return 8 }
4040
}
4141

42+
class E : Equatable {
43+
var i : Int
44+
static func ==(lhs: E, rhs: E) -> Bool { lhs.i == rhs.i }
45+
init(i: Int) { self.i = i }
46+
}
47+
48+
class H : Hashable {
49+
var i : Int
50+
static func ==(lhs: H, rhs: H) -> Bool { lhs.i == rhs.i }
51+
func hash(into hasher: inout Hasher) { hasher.combine(i + 17) }
52+
init(i: Int) { self.i = i }
53+
}
54+
4255
@_silgen_name("TestSwiftObjectNSObject")
43-
func TestSwiftObjectNSObject(_ c: C, _ d: D)
56+
func TestSwiftObjectNSObject(
57+
_ c: C, _ d: D,
58+
_ e1a: E, _ e1b: E, _ e2: E,
59+
_ h1a: H, _ h1b: H, _ h2: H, _ hash1: UInt, _ hash2: UInt)
4460

4561
// This check is for NSLog() output from TestSwiftObjectNSObject().
4662
// CHECK: c ##SwiftObjectNSObject.C##
@@ -50,7 +66,9 @@ func TestSwiftObjectNSObject(_ c: C, _ d: D)
5066
// Temporarily disable this test on older OSes until we have time to
5167
// look into why it's failing there. rdar://problem/47870743
5268
if #available(OSX 10.12, iOS 10.0, *) {
53-
TestSwiftObjectNSObject(C(), D())
69+
TestSwiftObjectNSObject(C(), D(),
70+
E(i:1), E(i:1), E(i:2),
71+
H(i:1), H(i:1), H(i:2), UInt(H(i:1).hashValue), UInt(H(i:2).hashValue))
5472
// does not return
5573
} else {
5674
// Horrible hack to satisfy FileCheck

0 commit comments

Comments
 (0)