Skip to content

Commit cb6b312

Browse files
authored
Merge pull request #11138 from itaiferber/nsnumber-custom-anyhashable-fix
NSNumber custom AnyHashable fix
2 parents 4c366d8 + aa317ba commit cb6b312

File tree

4 files changed

+85
-17
lines changed

4 files changed

+85
-17
lines changed

stdlib/public/SDK/Foundation/NSNumber.swift

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -647,33 +647,39 @@ extension NSNumber : _HasCustomAnyHashableRepresentation {
647647
// AnyHashable to NSObject.
648648
@nonobjc
649649
public func _toCustomAnyHashable() -> AnyHashable? {
650+
// The custom AnyHashable representation here is used when checking for
651+
// equality during bridging NSDictionary to Dictionary (or looking up
652+
// values in a Dictionary bridged to NSDictionary).
653+
//
654+
// When we've got NSNumber values as keys that we want to compare
655+
// through an AnyHashable box, we want to compare values through the
656+
// largest box size we've got available to us (i.e. upcast numbers).
657+
// This happens to resemble the representation that NSNumber uses
658+
// internally: (long long | unsigned long long | double).
659+
//
660+
// This allows us to compare things like
661+
//
662+
// ([Int : Any] as [AnyHashable : Any]) vs. [NSNumber : Any]
663+
//
664+
// because Int can be upcast to Int64 and compared with the number's
665+
// Int64 value.
666+
//
667+
// If NSNumber adds 128-bit representations, this will need to be
668+
// updated to use those.
650669
if let nsDecimalNumber: NSDecimalNumber = self as? NSDecimalNumber {
651670
return AnyHashable(nsDecimalNumber.decimalValue)
652671
} else if self === kCFBooleanTrue as NSNumber {
653672
return AnyHashable(true)
654673
} else if self === kCFBooleanFalse as NSNumber {
655674
return AnyHashable(false)
656-
} else if NSNumber(value: int8Value) == self {
657-
return AnyHashable(int8Value)
658-
} else if NSNumber(value: uint8Value) == self {
659-
return AnyHashable(uint8Value)
660-
} else if NSNumber(value: int16Value) == self {
661-
return AnyHashable(int16Value)
662-
} else if NSNumber(value: uint16Value) == self {
663-
return AnyHashable(uint16Value)
664-
} else if NSNumber(value: int32Value) == self {
665-
return AnyHashable(int32Value)
666-
} else if NSNumber(value: uint32Value) == self {
667-
return AnyHashable(uint32Value)
668675
} else if NSNumber(value: int64Value) == self {
669676
return AnyHashable(int64Value)
670-
} else if NSNumber(value: floatValue) == self {
671-
return AnyHashable(floatValue)
677+
} else if NSNumber(value: uint64Value) == self {
678+
return AnyHashable(uint64Value)
672679
} else if NSNumber(value: doubleValue) == self {
673680
return AnyHashable(doubleValue)
674681
} else {
675682
return nil
676683
}
677684
}
678685
}
679-

test/stdlib/Inputs/FoundationBridge/FoundationBridge.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,10 @@ BOOL identityOfData(NSData *data);
6969
- (BOOL)verifyAutoupdatingLocale:(NSLocale *)locale;
7070
@end
7171

72+
#pragma mark - NSNumber verification
73+
74+
@interface NumberBridgingTester : NSObject
75+
- (BOOL)verifyKeysInRange:(NSRange)range existInDictionary:(NSDictionary *)dictionary;
76+
@end
77+
7278
NS_ASSUME_NONNULL_END

test/stdlib/Inputs/FoundationBridge/FoundationBridge.m

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,4 +264,16 @@ - (BOOL)verifyAutoupdatingLocale:(NSLocale *)locale {
264264

265265
@end
266266

267+
@implementation NumberBridgingTester
267268

269+
- (BOOL)verifyKeysInRange:(NSRange)range existInDictionary:(NSDictionary *)dictionary {
270+
for (NSUInteger i = 0; i < range.length; i += 1) {
271+
if (!dictionary[@(range.location + i)]) {
272+
return NO;
273+
}
274+
}
275+
276+
return YES;
277+
}
278+
279+
@end

test/stdlib/TestNSNumberBridging.swift

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@
99
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
12-
// RUN: %target-run-simple-swift
12+
//
13+
// RUN: %empty-directory(%t)
14+
//
15+
// RUN: %target-clang %S/Inputs/FoundationBridge/FoundationBridge.m -c -o %t/FoundationBridgeObjC.o -g
16+
// RUN: %target-build-swift %s -I %S/Inputs/FoundationBridge/ -Xlinker %t/FoundationBridgeObjC.o -o %t/TestNSNumberBridging
17+
//
18+
// RUN: %target-run %t/TestNSNumberBridging
1319
// REQUIRES: executable_test
1420
// REQUIRES: objc_interop
1521

16-
1722
import StdlibUnittest
1823
import Foundation
1924
import CoreGraphics
25+
import FoundationBridgeObjC
2026

2127
extension Float {
2228
init?(reasonably value: Float) {
@@ -873,6 +879,42 @@ func test_numericBitPatterns_to_floatingPointTypes() {
873879
}
874880
}
875881

882+
func testNSNumberBridgeAnyHashable() {
883+
var dict = [AnyHashable : Any]()
884+
for i in -Int(UInt8.min) ... Int(UInt8.max) {
885+
dict[i] = "\(i)"
886+
}
887+
888+
// When bridging a dictionary to NSDictionary, we should be able to access
889+
// the keys through either an Int (the original type boxed in AnyHashable)
890+
// or NSNumber (the type Int bridged to).
891+
let ns_dict = dict as NSDictionary
892+
for i in -Int(UInt8.min) ... Int(UInt8.max) {
893+
guard let value = ns_dict[i] as? String else {
894+
expectUnreachable("Unable to look up value by Int key.")
895+
continue
896+
}
897+
898+
guard let ns_value = ns_dict[NSNumber(value: i)] as? String else {
899+
expectUnreachable("Unable to look up value by NSNumber key.")
900+
continue
901+
}
902+
903+
expectEqual(value, ns_value)
904+
}
905+
}
906+
907+
func testNSNumberBridgeAnyHashableObjc() {
908+
let range = -Int(UInt8.min) ... Int(UInt8.max)
909+
var dict = [AnyHashable : Any]()
910+
for i in range {
911+
dict[i] = "\(i)"
912+
}
913+
914+
let verifier = NumberBridgingTester()
915+
expectTrue(verifier.verifyKeys(in: NSRange(range), existIn: dict))
916+
}
917+
876918
nsNumberBridging.test("Bridge Int8") { testNSNumberBridgeFromInt8() }
877919
nsNumberBridging.test("Bridge UInt8") { testNSNumberBridgeFromUInt8() }
878920
nsNumberBridging.test("Bridge Int16") { testNSNumberBridgeFromInt16() }
@@ -887,4 +929,6 @@ nsNumberBridging.test("Bridge Float") { testNSNumberBridgeFromFloat() }
887929
nsNumberBridging.test("Bridge Double") { testNSNumberBridgeFromDouble() }
888930
nsNumberBridging.test("Bridge CGFloat") { testNSNumberBridgeFromCGFloat() }
889931
nsNumberBridging.test("bitPattern to exactly") { test_numericBitPatterns_to_floatingPointTypes() }
932+
nsNumberBridging.test("Bridge AnyHashable") { testNSNumberBridgeAnyHashable() }
933+
nsNumberBridging.test("Bridge AnyHashable (ObjC)") { testNSNumberBridgeAnyHashableObjc() }
890934
runAllTests()

0 commit comments

Comments
 (0)