Skip to content

Commit 0308b16

Browse files
authored
Merge pull request #71620 from tbkka/tbkka-bincompat-objc-hash-isequal-interop
Bincompat hooks for revised hash/isEqual interop
2 parents 21eaaaa + 6c173fe commit 0308b16

File tree

6 files changed

+61
-9
lines changed

6 files changed

+61
-9
lines changed

include/swift/Runtime/Bincompat.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ bool useLegacyObjCBoxingInCasting();
4141
/// Whether to use legacy semantics when unboxing __SwiftValue
4242
bool useLegacySwiftValueUnboxingInCasting();
4343

44+
/// Legacy semantics use trivial implementations for -hashValue/-isEqual:
45+
/// requests from ObjC to Swift values.
46+
/// New semantics attempt to dispatch to Swift Hashable/Equatable conformances
47+
/// if present.
48+
bool useLegacySwiftObjCHashing();
49+
4450
} // namespace bincompat
4551

4652
} // namespace runtime

stdlib/public/runtime/Bincompat.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,30 @@ bool useLegacySwiftValueUnboxingInCasting() {
228228
#endif
229229
}
230230

231+
// Controls how ObjC -hashValue and -isEqual are handled
232+
// by Swift objects.
233+
// There are two basic semantics:
234+
// * pointer: -hashValue returns pointer, -isEqual: tests pointer equality
235+
// * proxy: -hashValue calls on Hashable conformance, -isEqual: calls Equatable conformance
236+
//
237+
// Legacy handling:
238+
// * Swift struct/enum values that implement Hashable: proxy -hashValue and -isEqual:
239+
// * Swift struct/enum values that implement Equatable but not Hashable: pointer semantics
240+
// * Swift class values regardless of hashable/Equatable support: pointer semantics
241+
//
242+
// New behavior:
243+
// * Swift struct/enum/class values that implement Hashable: proxy -hashValue and -isEqual:
244+
// * Swift struct/enum/class values that implement Equatable but not Hashable: proxy -isEqual:, constant -hashValue
245+
// * All other cases: pointer semantics
246+
//
247+
bool useLegacySwiftObjCHashing() {
248+
#if BINARY_COMPATIBILITY_APPLE
249+
return true; // For now, legacy behavior on Apple OSes
250+
#else
251+
return false; // Always use the new behavior on non-Apple OSes
252+
#endif
253+
}
254+
231255
} // namespace bincompat
232256

233257
} // namespace runtime

stdlib/public/runtime/SwiftObject.mm

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,11 @@ + (BOOL)conformsToProtocol:(Protocol*)proto {
376376
}
377377

378378
- (NSUInteger)hash {
379+
if (runtime::bincompat::useLegacySwiftObjCHashing()) {
380+
// Legacy behavior: Don't proxy to Swift Hashable
381+
return (NSUInteger)self;
382+
}
383+
379384
auto selfMetadata = _swift_getClassOfAllocated(self);
380385

381386
// If it's Hashable, use that
@@ -423,6 +428,11 @@ - (BOOL)isEqual:(id)other {
423428
if (self == other) {
424429
return YES;
425430
}
431+
if (runtime::bincompat::useLegacySwiftObjCHashing()) {
432+
// Legacy behavior: Don't proxy to Swift Hashable or Equatable
433+
return NO; // We know the ids are different
434+
}
435+
426436

427437
// Get Swift type for self and other
428438
auto selfMetadata = _swift_getClassOfAllocated(self);

stdlib/public/runtime/SwiftValue.mm

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "SwiftObject.h"
2525
#include "SwiftValue.h"
2626
#include "swift/Basic/Lazy.h"
27+
#include "swift/Runtime/Bincompat.h"
2728
#include "swift/Runtime/Casting.h"
2829
#include "swift/Runtime/HeapObject.h"
2930
#include "swift/Runtime/Metadata.h"
@@ -429,6 +430,11 @@ - (BOOL)isEqual:(id)other {
429430
}
430431
}
431432

433+
if (runtime::bincompat::useLegacySwiftObjCHashing()) {
434+
// Legacy behavior only proxies isEqual: for Hashable, not Equatable
435+
return NO;
436+
}
437+
432438
if (auto equatableConformance = selfHeader->getEquatableConformance()) {
433439
if (auto selfEquatableBaseType = selfHeader->getEquatableBaseType()) {
434440
auto otherEquatableBaseType = otherHeader->getEquatableBaseType();
@@ -458,6 +464,11 @@ - (NSUInteger)hash {
458464
selfHeader->type, hashableConformance);
459465
}
460466

467+
if (!runtime::bincompat::useLegacySwiftObjCHashing()) {
468+
// Legacy behavior doesn't honor Equatable conformance, only Hashable
469+
return (NSUInteger)self;
470+
}
471+
461472
// If Swift type is Equatable but not Hashable,
462473
// we have to return something here that is compatible
463474
// with the `isEqual:` above.

test/stdlib/Inputs/SwiftValueNSObject/SwiftValueNSObject.m

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,10 @@ void TestSwiftValueNSObjectNotEquals(id e1, id e2)
102102

103103
void TestSwiftValueNSObjectHashValue(id e, NSUInteger hashValue)
104104
{
105-
printf("NSObjectProtocol.hash: Expect [%s hashValue] == %lu\n",
105+
printf("NSObjectProtocol.hash: Expect [%s hashValue] == %lu, Got %lu\n",
106106
[[e description] UTF8String],
107-
(unsigned long)hashValue);
107+
(unsigned long)hashValue,
108+
[e hash]);
108109
expectTrue([e hash] == hashValue);
109110
}
110111

test/stdlib/SwiftValueNSObject.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,14 @@ func TestHashable<T: Hashable>(_ h: T)
9898
// Test Obj-C hashValue for Swift types that are Equatable but not Hashable
9999
func TestEquatableHash<T: Equatable>(_ e: T)
100100
{
101-
// These should have a constant hash value
101+
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
102+
// Legacy behavior used the pointer value, which is
103+
// incompatible with user-defined equality.
104+
TestSwiftValueNSObjectDefaultHashValue(e as AnyObject)
105+
#else
106+
// New behavior uses a constant hash value in this case
102107
TestSwiftValueNSObjectHashValue(e as AnyObject, 1)
108+
#endif
103109
}
104110

105111
func TestNonEquatableHash<T>(_ e: T)
@@ -113,10 +119,6 @@ func TestNonEquatableHash<T>(_ e: T)
113119
// CHECK-NEXT: d ##This is D's description##
114120
// CHECK-NEXT: S ##{{.*}}__SwiftValue##
115121

116-
// Full message is longer, but this is the essential part...
117-
// CHECK-NEXT: Obj-C `-hash` {{.*}} type `SwiftValueNSObject.E` {{.*}} Equatable but not Hashable
118-
// CHECK-NEXT: Obj-C `-hash` {{.*}} type `SwiftValueNSObject.E1` {{.*}} Equatable but not Hashable
119-
120122
// Temporarily disable this test on older OSes until we have time to
121123
// look into why it's failing there. rdar://problem/47870743
122124
if #available(OSX 10.12, iOS 10.0, *) {
@@ -156,6 +158,4 @@ if #available(OSX 10.12, iOS 10.0, *) {
156158
fputs("c ##This is C's debug description##\n", stderr)
157159
fputs("d ##This is D's description##\n", stderr)
158160
fputs("S ##__SwiftValue##\n", stderr)
159-
fputs("Obj-C `-hash` ... type `SwiftValueNSObject.E` ... Equatable but not Hashable", stderr)
160-
fputs("Obj-C `-hash` ... type `SwiftValueNSObject.E1` ... Equatable but not Hashable", stderr)
161161
}

0 commit comments

Comments
 (0)