Skip to content

NSNumber custom AnyHashable fix #11138

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 21 additions & 15 deletions stdlib/public/SDK/Foundation/NSNumber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -647,33 +647,39 @@ extension NSNumber : _HasCustomAnyHashableRepresentation {
// AnyHashable to NSObject.
@nonobjc
public func _toCustomAnyHashable() -> AnyHashable? {
// The custom AnyHashable representation here is used when checking for
// equality during bridging NSDictionary to Dictionary (or looking up
// values in a Dictionary bridged to NSDictionary).
//
// When we've got NSNumber values as keys that we want to compare
// through an AnyHashable box, we want to compare values through the
// largest box size we've got available to us (i.e. upcast numbers).
// This happens to resemble the representation that NSNumber uses
// internally: (long long | unsigned long long | double).
//
// This allows us to compare things like
//
// ([Int : Any] as [AnyHashable : Any]) vs. [NSNumber : Any]
//
// because Int can be upcast to Int64 and compared with the number's
// Int64 value.
//
// If NSNumber adds 128-bit representations, this will need to be
// updated to use those.
if let nsDecimalNumber: NSDecimalNumber = self as? NSDecimalNumber {
return AnyHashable(nsDecimalNumber.decimalValue)
} else if self === kCFBooleanTrue as NSNumber {
return AnyHashable(true)
} else if self === kCFBooleanFalse as NSNumber {
return AnyHashable(false)
} else if NSNumber(value: int8Value) == self {
return AnyHashable(int8Value)
} else if NSNumber(value: uint8Value) == self {
return AnyHashable(uint8Value)
} else if NSNumber(value: int16Value) == self {
return AnyHashable(int16Value)
} else if NSNumber(value: uint16Value) == self {
return AnyHashable(uint16Value)
} else if NSNumber(value: int32Value) == self {
return AnyHashable(int32Value)
} else if NSNumber(value: uint32Value) == self {
return AnyHashable(uint32Value)
} else if NSNumber(value: int64Value) == self {
return AnyHashable(int64Value)
} else if NSNumber(value: floatValue) == self {
return AnyHashable(floatValue)
} else if NSNumber(value: uint64Value) == self {
return AnyHashable(uint64Value)
} else if NSNumber(value: doubleValue) == self {
return AnyHashable(doubleValue)
} else {
return nil
}
}
}

6 changes: 6 additions & 0 deletions test/stdlib/Inputs/FoundationBridge/FoundationBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,10 @@ BOOL identityOfData(NSData *data);
- (BOOL)verifyAutoupdatingLocale:(NSLocale *)locale;
@end

#pragma mark - NSNumber verification

@interface NumberBridgingTester : NSObject
- (BOOL)verifyKeysInRange:(NSRange)range existInDictionary:(NSDictionary *)dictionary;
@end

NS_ASSUME_NONNULL_END
12 changes: 12 additions & 0 deletions test/stdlib/Inputs/FoundationBridge/FoundationBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -264,4 +264,16 @@ - (BOOL)verifyAutoupdatingLocale:(NSLocale *)locale {

@end

@implementation NumberBridgingTester

- (BOOL)verifyKeysInRange:(NSRange)range existInDictionary:(NSDictionary *)dictionary {
for (NSUInteger i = 0; i < range.length; i += 1) {
if (!dictionary[@(range.location + i)]) {
return NO;
}
}

return YES;
}

@end
48 changes: 46 additions & 2 deletions test/stdlib/TestNSNumberBridging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
// RUN: %target-run-simple-swift
//
// RUN: %empty-directory(%t)
//
// RUN: %target-clang %S/Inputs/FoundationBridge/FoundationBridge.m -c -o %t/FoundationBridgeObjC.o -g
// RUN: %target-build-swift %s -I %S/Inputs/FoundationBridge/ -Xlinker %t/FoundationBridgeObjC.o -o %t/TestNSNumberBridging
//
// RUN: %target-run %t/TestNSNumberBridging
// REQUIRES: executable_test
// REQUIRES: objc_interop


import StdlibUnittest
import Foundation
import CoreGraphics
import FoundationBridgeObjC

extension Float {
init?(reasonably value: Float) {
Expand Down Expand Up @@ -873,6 +879,42 @@ func test_numericBitPatterns_to_floatingPointTypes() {
}
}

func testNSNumberBridgeAnyHashable() {
var dict = [AnyHashable : Any]()
for i in -Int(UInt8.min) ... Int(UInt8.max) {
dict[i] = "\(i)"
}

// When bridging a dictionary to NSDictionary, we should be able to access
// the keys through either an Int (the original type boxed in AnyHashable)
// or NSNumber (the type Int bridged to).
let ns_dict = dict as NSDictionary
for i in -Int(UInt8.min) ... Int(UInt8.max) {
guard let value = ns_dict[i] as? String else {
expectUnreachable("Unable to look up value by Int key.")
continue
}

guard let ns_value = ns_dict[NSNumber(value: i)] as? String else {
expectUnreachable("Unable to look up value by NSNumber key.")
continue
}

expectEqual(value, ns_value)
}
}

func testNSNumberBridgeAnyHashableObjc() {
let range = -Int(UInt8.min) ... Int(UInt8.max)
var dict = [AnyHashable : Any]()
for i in range {
dict[i] = "\(i)"
}

let verifier = NumberBridgingTester()
expectTrue(verifier.verifyKeys(in: NSRange(range), existIn: dict))
}

nsNumberBridging.test("Bridge Int8") { testNSNumberBridgeFromInt8() }
nsNumberBridging.test("Bridge UInt8") { testNSNumberBridgeFromUInt8() }
nsNumberBridging.test("Bridge Int16") { testNSNumberBridgeFromInt16() }
Expand All @@ -887,4 +929,6 @@ nsNumberBridging.test("Bridge Float") { testNSNumberBridgeFromFloat() }
nsNumberBridging.test("Bridge Double") { testNSNumberBridgeFromDouble() }
nsNumberBridging.test("Bridge CGFloat") { testNSNumberBridgeFromCGFloat() }
nsNumberBridging.test("bitPattern to exactly") { test_numericBitPatterns_to_floatingPointTypes() }
nsNumberBridging.test("Bridge AnyHashable") { testNSNumberBridgeAnyHashable() }
nsNumberBridging.test("Bridge AnyHashable (ObjC)") { testNSNumberBridgeAnyHashableObjc() }
runAllTests()