Merge pull request #17396 from lorentey/anyhashable-is-not-hashable

[stdlib] Fix AnyHashable's Equatable/Hashable conformance
This commit is contained in:
Karoy Lorentey
2018-06-29 17:38:08 +01:00
committed by GitHub
14 changed files with 961 additions and 268 deletions

View File

@@ -2410,6 +2410,33 @@ internal func hash<H: Hashable>(_ value: H, seed: Int? = nil) -> Int {
return hasher.finalize() return hasher.finalize()
} }
/// Test that the elements of `groups` consist of instances that satisfy the
/// semantic requirements of `Hashable`, with each group defining a distinct
/// equivalence class under `==`.
public func checkHashableGroups<Groups: Collection>(
_ groups: Groups,
_ message: @autoclosure () -> String = "",
stackTrace: SourceLocStack = SourceLocStack(),
showFrame: Bool = true,
file: String = #file, line: UInt = #line
) where Groups.Element: Collection, Groups.Element.Element: Hashable {
let instances = groups.flatMap { $0 }
// groupIndices[i] is the index of the element in groups that contains
// instances[i].
let groupIndices =
zip(0..., groups).flatMap { i, group in group.map { _ in i } }
func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool {
return groupIndices[lhs] == groupIndices[rhs]
}
checkHashable(
instances,
equalityOracle: equalityOracle,
hashEqualityOracle: equalityOracle,
allowBrokenTransitivity: false,
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line),
showFrame: false)
}
/// Test that the elements of `instances` satisfy the semantic requirements of /// Test that the elements of `instances` satisfy the semantic requirements of
/// `Hashable`, using `equalityOracle` to generate equality and hashing /// `Hashable`, using `equalityOracle` to generate equality and hashing
/// expectations from pairs of positions in `instances`. /// expectations from pairs of positions in `instances`.

View File

@@ -39,20 +39,29 @@ public protocol _HasCustomAnyHashableRepresentation {
@usableFromInline // FIXME(sil-serialize-all) @usableFromInline // FIXME(sil-serialize-all)
internal protocol _AnyHashableBox { internal protocol _AnyHashableBox {
func _unbox<T : Hashable>() -> T? var _canonicalBox: _AnyHashableBox { get }
/// Determine whether values in the boxes are equivalent. /// Determine whether values in the boxes are equivalent.
/// ///
/// - Precondition: `self` and `box` are in canonical form.
/// - Returns: `nil` to indicate that the boxes store different types, so /// - Returns: `nil` to indicate that the boxes store different types, so
/// no comparison is possible. Otherwise, contains the result of `==`. /// no comparison is possible. Otherwise, contains the result of `==`.
func _isEqual(to: _AnyHashableBox) -> Bool? func _isEqual(to box: _AnyHashableBox) -> Bool?
var _hashValue: Int { get } var _hashValue: Int { get }
func _hash(into hasher: inout Hasher) func _hash(into hasher: inout Hasher)
func _rawHashValue(_seed: (UInt64, UInt64)) -> Int
var _base: Any { get } var _base: Any { get }
func _unbox<T: Hashable>() -> T?
func _downCastConditional<T>(into result: UnsafeMutablePointer<T>) -> Bool func _downCastConditional<T>(into result: UnsafeMutablePointer<T>) -> Bool
} }
extension _AnyHashableBox {
var _canonicalBox: _AnyHashableBox {
return self
}
}
@_fixed_layout // FIXME(sil-serialize-all) @_fixed_layout // FIXME(sil-serialize-all)
@usableFromInline // FIXME(sil-serialize-all) @usableFromInline // FIXME(sil-serialize-all)
internal struct _ConcreteHashableBox<Base : Hashable> : _AnyHashableBox { internal struct _ConcreteHashableBox<Base : Hashable> : _AnyHashableBox {
@@ -87,6 +96,11 @@ internal struct _ConcreteHashableBox<Base : Hashable> : _AnyHashableBox {
_baseHashable.hash(into: &hasher) _baseHashable.hash(into: &hasher)
} }
@inlinable // FIXME(sil-serialize-all)
func _rawHashValue(_seed: (UInt64, UInt64)) -> Int {
return _baseHashable._rawHashValue(seed: _seed)
}
@inlinable // FIXME(sil-serialize-all) @inlinable // FIXME(sil-serialize-all)
internal var _base: Any { internal var _base: Any {
return _baseHashable return _baseHashable
@@ -101,19 +115,6 @@ internal struct _ConcreteHashableBox<Base : Hashable> : _AnyHashableBox {
} }
} }
#if _runtime(_ObjC)
// Retrieve the custom AnyHashable representation of the value after it
// has been bridged to Objective-C. This mapping to Objective-C and back
// turns a non-custom representation into a custom one, which is used as
// the lowest-common-denominator for comparisons.
@inlinable // FIXME(sil-serialize-all)
internal func _getBridgedCustomAnyHashable<T>(_ value: T) -> AnyHashable? {
let bridgedValue = _bridgeAnythingToObjectiveC(value)
return (bridgedValue as?
_HasCustomAnyHashableRepresentation)?._toCustomAnyHashable()
}
#endif
/// A type-erased hashable value. /// A type-erased hashable value.
/// ///
/// The `AnyHashable` type forwards equality comparisons and hashing operations /// The `AnyHashable` type forwards equality comparisons and hashing operations
@@ -137,8 +138,11 @@ internal func _getBridgedCustomAnyHashable<T>(_ value: T) -> AnyHashable? {
public struct AnyHashable { public struct AnyHashable {
@usableFromInline // FIXME(sil-serialize-all) @usableFromInline // FIXME(sil-serialize-all)
internal var _box: _AnyHashableBox internal var _box: _AnyHashableBox
@usableFromInline // FIXME(sil-serialize-all)
internal var _usedCustomRepresentation: Bool @inlinable // FIXME(sil-serialize-all)
internal init(_box box: _AnyHashableBox) {
self._box = box
}
/// Creates a type-erased hashable value that wraps the given instance. /// Creates a type-erased hashable value that wraps the given instance.
/// ///
@@ -160,15 +164,13 @@ public struct AnyHashable {
/// - Parameter base: A hashable value to wrap. /// - Parameter base: A hashable value to wrap.
@inlinable // FIXME(sil-serialize-all) @inlinable // FIXME(sil-serialize-all)
public init<H : Hashable>(_ base: H) { public init<H : Hashable>(_ base: H) {
if let customRepresentation = if let custom =
(base as? _HasCustomAnyHashableRepresentation)?._toCustomAnyHashable() { (base as? _HasCustomAnyHashableRepresentation)?._toCustomAnyHashable() {
self = customRepresentation self = custom
self._usedCustomRepresentation = true
return return
} }
self._box = _ConcreteHashableBox(0 as Int) self.init(_box: _ConcreteHashableBox(false)) // Dummy value
self._usedCustomRepresentation = false
_makeAnyHashableUpcastingToHashableBaseType( _makeAnyHashableUpcastingToHashableBaseType(
base, base,
storingResultInto: &self) storingResultInto: &self)
@@ -177,7 +179,6 @@ public struct AnyHashable {
@inlinable // FIXME(sil-serialize-all) @inlinable // FIXME(sil-serialize-all)
internal init<H : Hashable>(_usingDefaultRepresentationOf base: H) { internal init<H : Hashable>(_usingDefaultRepresentationOf base: H) {
self._box = _ConcreteHashableBox(base) self._box = _ConcreteHashableBox(base)
self._usedCustomRepresentation = false
} }
/// The value wrapped by this instance. /// The value wrapped by this instance.
@@ -206,13 +207,11 @@ public struct AnyHashable {
if _box._downCastConditional(into: result) { return true } if _box._downCastConditional(into: result) { return true }
#if _runtime(_ObjC) #if _runtime(_ObjC)
// If we used a custom representation, bridge to Objective-C and then // Bridge to Objective-C and then attempt the cast from there.
// attempt the cast from there. // FIXME: This should also work without the Objective-C runtime.
if _usedCustomRepresentation { if let value = _bridgeAnythingToObjectiveC(_box._base) as? T {
if let value = _bridgeAnythingToObjectiveC(_box._base) as? T { result.initialize(to: value)
result.initialize(to: value) return true
return true
}
} }
#endif #endif
@@ -248,34 +247,7 @@ extension AnyHashable : Equatable {
/// - rhs: Another type-erased hashable value. /// - rhs: Another type-erased hashable value.
@inlinable // FIXME(sil-serialize-all) @inlinable // FIXME(sil-serialize-all)
public static func == (lhs: AnyHashable, rhs: AnyHashable) -> Bool { public static func == (lhs: AnyHashable, rhs: AnyHashable) -> Bool {
// If they're equal, we're done. return lhs._box._canonicalBox._isEqual(to: rhs._box._canonicalBox) ?? false
if let result = lhs._box._isEqual(to: rhs._box) { return result }
#if _runtime(_ObjC)
// If one used a custom representation but the other did not, bridge
// the one that did *not* use the custom representation to Objective-C:
// if the bridged result has a custom representation, compare those custom
// custom representations.
if lhs._usedCustomRepresentation != rhs._usedCustomRepresentation {
// If the lhs used a custom representation, try comparing against the
// custom representation of the bridged rhs (if there is one).
if lhs._usedCustomRepresentation {
if let customRHS = _getBridgedCustomAnyHashable(rhs._box._base) {
return lhs._box._isEqual(to: customRHS._box) ?? false
}
return false
}
// Otherwise, try comparing the rhs against the custom representation of
// the bridged lhs (if there is one).
if let customLHS = _getBridgedCustomAnyHashable(lhs._box._base) {
return customLHS._box._isEqual(to: rhs._box) ?? false
}
return false
}
#endif
return false
} }
} }
@@ -283,7 +255,7 @@ extension AnyHashable : Hashable {
/// The hash value. /// The hash value.
@inlinable @inlinable
public var hashValue: Int { public var hashValue: Int {
return _box._hashValue return _box._canonicalBox._hashValue
} }
/// Hashes the essential components of this value by feeding them into the /// Hashes the essential components of this value by feeding them into the
@@ -293,7 +265,12 @@ extension AnyHashable : Hashable {
/// of this instance. /// of this instance.
@inlinable @inlinable
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {
_box._hash(into: &hasher) _box._canonicalBox._hash(into: &hasher)
}
@inlinable // FIXME(sil-serialize-all)
public func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
return _box._canonicalBox._rawHashValue(_seed: seed)
} }
} }

View File

@@ -1758,3 +1758,76 @@ extension Array {
} }
} }
#endif #endif
extension Array: _HasCustomAnyHashableRepresentation
where Element: Hashable {
public func _toCustomAnyHashable() -> AnyHashable? {
return AnyHashable(_box: _ArrayAnyHashableBox(self))
}
}
internal protocol _ArrayAnyHashableProtocol: _AnyHashableBox {
var count: Int { get }
subscript(index: Int) -> AnyHashable { get }
}
internal struct _ArrayAnyHashableBox<Element: Hashable>
: _ArrayAnyHashableProtocol {
internal let _value: [Element]
internal init(_ value: [Element]) {
self._value = value
}
internal var _base: Any {
return _value
}
internal var count: Int {
return _value.count
}
internal subscript(index: Int) -> AnyHashable {
return _value[index] as AnyHashable
}
func _isEqual(to other: _AnyHashableBox) -> Bool? {
guard let other = other as? _ArrayAnyHashableProtocol else { return nil }
guard _value.count == other.count else { return false }
for i in 0 ..< _value.count {
if self[i] != other[i] { return false }
}
return true
}
var _hashValue: Int {
var hasher = Hasher()
_hash(into: &hasher)
return hasher.finalize()
}
func _hash(into hasher: inout Hasher) {
hasher.combine(_value.count) // discriminator
for i in 0 ..< _value.count {
hasher.combine(self[i])
}
}
func _rawHashValue(_seed: (UInt64, UInt64)) -> Int {
var hasher = Hasher(_seed: _seed)
self._hash(into: &hasher)
return hasher._finalize()
}
internal func _unbox<T : Hashable>() -> T? {
return _value as? T
}
internal func _downCastConditional<T>(
into result: UnsafeMutablePointer<T>
) -> Bool {
guard let value = _value as? T else { return false }
result.initialize(to: value)
return true
}
}

View File

@@ -1471,6 +1471,65 @@ extension Dictionary: Hashable where Value: Hashable {
} }
} }
extension Dictionary: _HasCustomAnyHashableRepresentation
where Value: Hashable {
public func _toCustomAnyHashable() -> AnyHashable? {
return AnyHashable(_box: _DictionaryAnyHashableBox(self))
}
}
internal struct _DictionaryAnyHashableBox<Key: Hashable, Value: Hashable>
: _AnyHashableBox {
internal let _value: Dictionary<Key, Value>
internal let _canonical: Dictionary<AnyHashable, AnyHashable>
internal init(_ value: Dictionary<Key, Value>) {
self._value = value
self._canonical = value as Dictionary<AnyHashable, AnyHashable>
}
internal var _base: Any {
return _value
}
internal var _canonicalBox: _AnyHashableBox {
return _DictionaryAnyHashableBox<AnyHashable, AnyHashable>(_canonical)
}
internal func _isEqual(to other: _AnyHashableBox) -> Bool? {
guard
let other = other as? _DictionaryAnyHashableBox<AnyHashable, AnyHashable>
else {
return nil
}
return _canonical == other._value
}
internal var _hashValue: Int {
return _canonical.hashValue
}
internal func _hash(into hasher: inout Hasher) {
_canonical.hash(into: &hasher)
}
internal func _rawHashValue(_seed: (UInt64, UInt64)) -> Int {
return _canonical._rawHashValue(seed: _seed)
}
internal func _unbox<T: Hashable>() -> T? {
return _value as? T
}
internal func _downCastConditional<T>(
into result: UnsafeMutablePointer<T>
) -> Bool {
guard let value = _value as? T else { return false }
result.initialize(to: value)
return true
}
}
extension Dictionary: CustomStringConvertible, CustomDebugStringConvertible { extension Dictionary: CustomStringConvertible, CustomDebugStringConvertible {
@inlinable // FIXME(sil-serialize-all) @inlinable // FIXME(sil-serialize-all)
internal func _makeDescription() -> String { internal func _makeDescription() -> String {

View File

@@ -1559,6 +1559,13 @@ extension ${Self} : Hashable {
} }
} }
extension ${Self}: _HasCustomAnyHashableRepresentation {
// Not @inlinable
public func _toCustomAnyHashable() -> AnyHashable? {
return AnyHashable(_box: _${Self}AnyHashableBox(self))
}
}
extension ${Self} { extension ${Self} {
/// The magnitude of this value. /// The magnitude of this value.
/// ///
@@ -1809,6 +1816,87 @@ extension ${Self} : Strideable {
} }
} }
//===----------------------------------------------------------------------===//
// AnyHashable
//===----------------------------------------------------------------------===//
internal struct _${Self}AnyHashableBox: _AnyHashableBox {
internal typealias Base = ${Self}
internal let _value: Base
internal init(_ value: Base) {
self._value = value
}
internal var _canonicalBox: _AnyHashableBox {
// Float and Double are bridged with NSNumber, so we have to follow
// NSNumber's rules for equality. I.e., we need to make sure equal
// numerical values end up in identical boxes after canonicalization, so
// that _isEqual will consider them equal and they're hashed the same way.
//
// Note that these AnyHashable boxes don't currently feed discriminator bits
// to the hasher, so we allow repeatable collisions. E.g., -1 will always
// collide with UInt64.max.
if _value < 0 {
if let i = Int64(exactly: _value) {
return _IntegerAnyHashableBox(i)
}
} else {
if let i = UInt64(exactly: _value) {
return _IntegerAnyHashableBox(i)
}
}
if let d = Double(exactly: _value) {
return _DoubleAnyHashableBox(d)
}
// If a value can't be represented by a Double, keep it in its original
// representation so that it won't compare equal to approximations. (So that
// we don't round off Float80 values.)
return self
}
internal func _isEqual(to box: _AnyHashableBox) -> Bool? {
_sanityCheck(Int64(exactly: _value) == nil, "self isn't canonical")
_sanityCheck(UInt64(exactly: _value) == nil, "self isn't canonical")
if let box = box as? _${Self}AnyHashableBox {
return _value == box._value
}
return nil
}
internal var _hashValue: Int {
return _rawHashValue(_seed: Hasher._seed)
}
internal func _hash(into hasher: inout Hasher) {
_sanityCheck(Int64(exactly: _value) == nil, "self isn't canonical")
_sanityCheck(UInt64(exactly: _value) == nil, "self isn't canonical")
hasher.combine(_value)
}
internal func _rawHashValue(_seed: (UInt64, UInt64)) -> Int {
var hasher = Hasher(_seed: _seed)
_hash(into: &hasher)
return hasher.finalize()
}
internal var _base: Any {
return _value
}
internal func _unbox<T: Hashable>() -> T? {
return _value as? T
}
internal func _downCastConditional<T>(
into result: UnsafeMutablePointer<T>
) -> Bool {
guard let value = _value as? T else { return false }
result.initialize(to: value)
return true
}
}
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// Deprecated operators // Deprecated operators
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//

View File

@@ -437,6 +437,7 @@ public struct Hasher {
seed: (UInt64, UInt64), seed: (UInt64, UInt64),
bytes value: UInt64, bytes value: UInt64,
count: Int) -> Int { count: Int) -> Int {
_sanityCheck(count >= 0 && count < 8)
var core = RawCore(seed: seed) var core = RawCore(seed: seed)
let tbc = _HasherTailBuffer(tail: value, byteCount: count) let tbc = _HasherTailBuffer(tail: value, byteCount: count)
return Int(truncatingIfNeeded: core.finalize(tailAndByteCount: tbc.value)) return Int(truncatingIfNeeded: core.finalize(tailAndByteCount: tbc.value))

View File

@@ -3880,41 +3880,31 @@ extension ${Self} : Hashable {
/// of this instance. /// of this instance.
@inlinable @inlinable
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {
// FIXME(hasher): To correctly bridge `Set`s/`Dictionary`s containing hasher._combine(${U}${Self}(_value))
// `AnyHashable`-boxed integers, all integer values are currently required
// to hash exactly the same way as the corresponding (U)Int64 value. To fix
// this, we should introduce a custom AnyHashable box for integer values
// that sign-extends values to 64 bits.
% if bits <= word_bits:
hasher._combine(_lowWord)
% elif bits == 2 * word_bits:
if let word = ${"" if signed else "U"}Int(exactly: self) {
hasher._combine(word._lowWord)
} else {
hasher._combine(UInt64(_value))
}
% else:
fatalError("Unsupported integer width")
% end
} }
@inlinable @inlinable
public func _rawHashValue(seed: (UInt64, UInt64)) -> Int { public func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
// FIXME(hasher): Note that the AnyHashable concern applies here too, % if bits == 64:
// because hashValue uses top-level hashing.
% if bits <= word_bits:
return Hasher._hash(seed: seed, _lowWord)
% elif bits == 2 * word_bits:
if let word = ${"" if signed else "U"}Int(exactly: self) {
return Hasher._hash(seed: seed, word._lowWord)
}
return Hasher._hash(seed: seed, UInt64(_value)) return Hasher._hash(seed: seed, UInt64(_value))
% elif bits == word_bits:
return Hasher._hash(seed: seed, UInt(_value))
% else: % else:
fatalError("Unsupported integer width") return Hasher._hash(
seed: seed,
bytes: UInt64(truncatingIfNeeded: ${U}${Self}(_value)),
count: ${bits / 8})
% end % end
} }
} }
extension ${Self} : _HasCustomAnyHashableRepresentation {
// Not @inlinable
public func _toCustomAnyHashable() -> AnyHashable? {
return AnyHashable(_box: _IntegerAnyHashableBox(self))
}
}
// Create an ambiguity when indexing or slicing // Create an ambiguity when indexing or slicing
// Range[OfStrideable]<${Self}> outside a generic context. See // Range[OfStrideable]<${Self}> outside a generic context. See
@@ -4223,6 +4213,73 @@ extension SignedInteger where Self : FixedWidthInteger {
% end % end
} }
internal struct _IntegerAnyHashableBox<
Base: FixedWidthInteger
>: _AnyHashableBox {
internal let _value: Base
internal init(_ value: Base) {
self._value = value
}
internal var _canonicalBox: _AnyHashableBox {
// We need to follow NSNumber semantics here; the AnyHashable forms of
// integer types holding the same mathematical value should compare equal.
// Sign-extend value to a 64-bit integer. This will generate hash conflicts
// between, say -1 and UInt.max, but that's fine.
if _value < 0 {
return _IntegerAnyHashableBox<Int64>(Int64(truncatingIfNeeded: _value))
}
return _IntegerAnyHashableBox<UInt64>(UInt64(truncatingIfNeeded: _value))
}
internal func _isEqual(to box: _AnyHashableBox) -> Bool? {
if Base.self == UInt64.self {
guard let box = box as? _IntegerAnyHashableBox<UInt64> else { return nil }
return _value == box._value
}
if Base.self == Int64.self {
guard let box = box as? _IntegerAnyHashableBox<Int64> else { return nil }
return _value == box._value
}
_preconditionFailure("self isn't canonical")
}
internal var _hashValue: Int {
_sanityCheck(Base.self == UInt64.self || Base.self == Int64.self,
"self isn't canonical")
return _value.hashValue
}
internal func _hash(into hasher: inout Hasher) {
_sanityCheck(Base.self == UInt64.self || Base.self == Int64.self,
"self isn't canonical")
_value.hash(into: &hasher)
}
internal func _rawHashValue(_seed: (UInt64, UInt64)) -> Int {
_sanityCheck(Base.self == UInt64.self || Base.self == Int64.self,
"self isn't canonical")
return _value._rawHashValue(seed: _seed)
}
internal var _base: Any {
return _value
}
internal func _unbox<T: Hashable>() -> T? {
return _value as? T
}
internal func _downCastConditional<T>(
into result: UnsafeMutablePointer<T>
) -> Bool {
guard let value = _value as? T else { return false }
result.initialize(to: value)
return true
}
}
// ${'Local Variables'}: // ${'Local Variables'}:
// eval: (read-only-mode 1) // eval: (read-only-mode 1)
// End: // End:

View File

@@ -13,9 +13,10 @@
/// An implementation detail used to implement support importing /// An implementation detail used to implement support importing
/// (Objective-)C entities marked with the swift_newtype Clang /// (Objective-)C entities marked with the swift_newtype Clang
/// attribute. /// attribute.
public protocol _SwiftNewtypeWrapper : RawRepresentable { } public protocol _SwiftNewtypeWrapper
: RawRepresentable, _HasCustomAnyHashableRepresentation { }
extension _SwiftNewtypeWrapper where Self: Hashable, Self.RawValue : Hashable { extension _SwiftNewtypeWrapper where Self: Hashable, Self.RawValue: Hashable {
/// The hash value. /// The hash value.
@inlinable @inlinable
public var hashValue: Int { public var hashValue: Int {
@@ -31,6 +32,70 @@ extension _SwiftNewtypeWrapper where Self: Hashable, Self.RawValue : Hashable {
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {
hasher.combine(rawValue) hasher.combine(rawValue)
} }
@inlinable // FIXME(sil-serialize-all)
public func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
return rawValue._rawHashValue(seed: seed)
}
}
extension _SwiftNewtypeWrapper {
public func _toCustomAnyHashable() -> AnyHashable? {
return nil
}
}
extension _SwiftNewtypeWrapper where Self: Hashable, Self.RawValue: Hashable {
public func _toCustomAnyHashable() -> AnyHashable? {
return AnyHashable(_box: _NewtypeWrapperAnyHashableBox(self))
}
}
internal struct _NewtypeWrapperAnyHashableBox<Base>: _AnyHashableBox
where Base: _SwiftNewtypeWrapper & Hashable, Base.RawValue: Hashable {
var _value: Base
init(_ value: Base) {
self._value = value
}
var _canonicalBox: _AnyHashableBox {
return (_value.rawValue as AnyHashable)._box._canonicalBox
}
func _isEqual(to other: _AnyHashableBox) -> Bool? {
_preconditionFailure("_isEqual called on non-canonical AnyHashable box")
}
var _hashValue: Int {
_preconditionFailure("_hashValue called on non-canonical AnyHashable box")
}
func _hash(into hasher: inout Hasher) {
_preconditionFailure("_hash(into:) called on non-canonical AnyHashable box")
}
func _rawHashValue(_seed: (UInt64, UInt64)) -> Int {
_preconditionFailure("_rawHashValue(_seed:) called on non-canonical AnyHashable box")
}
var _base: Any { return _value }
func _unbox<T: Hashable>() -> T? {
return _value as? T ?? _value.rawValue as? T
}
func _downCastConditional<T>(into result: UnsafeMutablePointer<T>) -> Bool {
if let value = _value as? T {
result.initialize(to: value)
return true
}
if let value = _value.rawValue as? T {
result.initialize(to: value)
return true
}
return false
}
} }
#if _runtime(_ObjC) #if _runtime(_ObjC)

View File

@@ -503,6 +503,61 @@ extension Set: Hashable {
} }
} }
extension Set: _HasCustomAnyHashableRepresentation {
public func _toCustomAnyHashable() -> AnyHashable? {
return AnyHashable(_box: _SetAnyHashableBox(self))
}
}
internal struct _SetAnyHashableBox<Element: Hashable>: _AnyHashableBox {
internal let _value: Set<Element>
internal let _canonical: Set<AnyHashable>
internal init(_ value: Set<Element>) {
self._value = value
self._canonical = value as Set<AnyHashable>
}
internal var _base: Any {
return _value
}
internal var _canonicalBox: _AnyHashableBox {
return _SetAnyHashableBox<AnyHashable>(_canonical)
}
internal func _isEqual(to other: _AnyHashableBox) -> Bool? {
guard let other = other as? _SetAnyHashableBox<AnyHashable> else {
return nil
}
return _canonical == other._value
}
internal var _hashValue: Int {
return _canonical.hashValue
}
internal func _hash(into hasher: inout Hasher) {
_canonical.hash(into: &hasher)
}
func _rawHashValue(_seed: (UInt64, UInt64)) -> Int {
return _canonical._rawHashValue(seed: _seed)
}
internal func _unbox<T: Hashable>() -> T? {
return _value as? T
}
internal func _downCastConditional<T>(
into result: UnsafeMutablePointer<T>
) -> Bool {
guard let value = _value as? T else { return false }
result.initialize(to: value)
return true
}
}
extension Set: SetAlgebra { extension Set: SetAlgebra {
/// Inserts the given element in the set if it is not already present. /// Inserts the given element in the set if it is not already present.

View File

@@ -1,10 +1,10 @@
// RUN: %empty-directory(%t) // RUN: %empty-directory(%t)
// //
// RUN: %gyb %s -o %t/AnyHashableCasts.swift // RUN: %gyb %s -o %t/AnyHashableCasts.swift
// RUN: %target-build-swift -g -module-name a %t/AnyHashableCasts.swift -o %t.out // RUN: %line-directive %t/AnyHashableCasts.swift -- %target-build-swift -g -module-name a %t/AnyHashableCasts.swift -o %t.out
// RUN: %target-run %t.out // RUN: %line-directive %t/AnyHashableCasts.swift -- %target-run %t.out
// RUN: %target-build-swift -g -O -module-name a %t/AnyHashableCasts.swift -o %t.out.optimized // RUN: %line-directive %t/AnyHashableCasts.swift -- %target-build-swift -g -O -module-name a %t/AnyHashableCasts.swift -o %t.out.optimized
// RUN: %target-run %t.out.optimized // RUN: %line-directive %t/AnyHashableCasts.swift -- %target-run %t.out.optimized
// REQUIRES: executable_test // REQUIRES: executable_test
import StdlibUnittest import StdlibUnittest
@@ -117,34 +117,129 @@ AnyHashableCasts.test("${valueExpr} as ${coercedType} as? ${castType}") {
% end % end
#if _runtime(_ObjC) #if _runtime(_ObjC)
// A wrapper type around Int that bridges to NSNumber.
struct IntWrapper1: _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable {
let rawValue: Int
}
// A wrapper type around Int that bridges to NSNumber.
struct IntWrapper2: _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable {
let rawValue: Int
}
AnyHashableCasts.test("Wrappers around bridged integers") {
let wrapper1: AnyHashable = IntWrapper1(rawValue: 42)
let wrapper2: AnyHashable = IntWrapper2(rawValue: 42)
let integer: AnyHashable = 42 as Int
let byte: AnyHashable = 42 as UInt8
let double: AnyHashable = 42.0 as Double
let number: AnyHashable = 42 as NSNumber
// Wrappers compare equal to their wrapped value as AnyHashable.
expectEqual(wrapper1, wrapper2)
expectEqual(wrapper1, integer)
expectEqual(wrapper1, byte)
expectEqual(wrapper1, double)
expectEqual(wrapper1, number)
// Original types are preserved in the base property.
expectTrue(wrapper1.base is IntWrapper1)
expectTrue(wrapper2.base is IntWrapper2)
expectTrue(integer.base is Int)
expectTrue(byte.base is UInt8)
expectTrue(double.base is Double)
expectTrue(number.base is NSNumber) // Through bridging
// AnyHashable forms can be casted to any standard numeric type that can hold
// their value.
expectNotNil(wrapper1 as? IntWrapper1)
expectNotNil(wrapper1 as? IntWrapper2)
expectNotNil(wrapper1 as? Int)
expectNotNil(wrapper1 as? UInt8)
expectNotNil(wrapper1 as? Double)
expectNotNil(wrapper1 as? NSNumber)
expectNotNil(byte as? IntWrapper1)
expectNotNil(byte as? IntWrapper2)
expectNotNil(byte as? Int)
expectNotNil(byte as? UInt8)
expectNotNil(byte as? Double)
expectNotNil(byte as? NSNumber)
expectNotNil(integer as? IntWrapper1)
expectNotNil(integer as? IntWrapper2)
expectNotNil(integer as? Int)
expectNotNil(integer as? UInt8)
expectNotNil(integer as? Double)
expectNotNil(integer as? NSNumber)
expectNotNil(double as? IntWrapper1)
expectNotNil(double as? IntWrapper2)
expectNotNil(double as? Int)
expectNotNil(double as? UInt8)
expectNotNil(double as? Double)
expectNotNil(double as? NSNumber)
expectNotNil(number as? IntWrapper1)
expectNotNil(number as? IntWrapper2)
expectNotNil(number as? Int)
expectNotNil(number as? UInt8)
expectNotNil(number as? Double)
expectNotNil(number as? NSNumber)
// We can't cast to a numeric type that can't hold the value.
let big: AnyHashable = Int32.max
expectNotNil(big as? IntWrapper1)
expectNotNil(big as? IntWrapper2)
expectNotNil(big as? Int)
expectNil(big as? UInt8) // <--
expectNotNil(big as? Double)
expectNotNil(big as? NSNumber)
}
// A wrapper type around a String that bridges to NSString. // A wrapper type around a String that bridges to NSString.
struct StringWrapper1 : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable { struct StringWrapper1: _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable {
let rawValue: String let rawValue: String
} }
// A wrapper type around a String that bridges to NSString. // A wrapper type around a String that bridges to NSString.
struct StringWrapper2 : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable { struct StringWrapper2: _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable {
let rawValue: String let rawValue: String
} }
AnyHashableCasts.test("Wrappers around bridged types") { AnyHashableCasts.test("Wrappers around bridged strings") {
let wrapper1Hello: AnyHashable = StringWrapper1(rawValue: "hello") let wrapper1Hello: AnyHashable = StringWrapper1(rawValue: "hello")
let wrapper2Hello: AnyHashable = StringWrapper2(rawValue: "hello")
let stringHello: AnyHashable = "hello" as String let stringHello: AnyHashable = "hello" as String
let nsStringHello: AnyHashable = "hello" as NSString let nsStringHello: AnyHashable = "hello" as NSString
// Casting from Swift wrapper maintains type identity // Wrappers compare equal to their wrapped value as AnyHashable.
expectEqual(wrapper1Hello, wrapper2Hello)
expectEqual(wrapper1Hello, stringHello)
expectEqual(wrapper1Hello, nsStringHello)
expectEqual(wrapper2Hello, stringHello)
expectEqual(wrapper2Hello, nsStringHello)
expectEqual(stringHello, nsStringHello)
// Type identity is maintained through the base property.
expectTrue(wrapper1Hello.base is StringWrapper1)
expectTrue(wrapper2Hello.base is StringWrapper2)
expectTrue(stringHello.base is String)
expectTrue(nsStringHello.base is NSString) // Through bridging
// Swift wrapper's AnyHashable form doesn't enfore type identity.
expectNotNil(wrapper1Hello as? StringWrapper1) expectNotNil(wrapper1Hello as? StringWrapper1)
expectNil(wrapper1Hello as? StringWrapper2) expectNotNil(wrapper1Hello as? StringWrapper2)
expectNil(wrapper1Hello as? String) expectNotNil(wrapper1Hello as? String)
expectNotNil(wrapper1Hello as? NSString) expectNotNil(wrapper1Hello as? NSString)
// Casting from String maintains type identity // String's AnyHashable form doesn't enfore type identity.
expectNil(stringHello as? StringWrapper1) expectNotNil(stringHello as? StringWrapper1)
expectNil(stringHello as? StringWrapper2) expectNotNil(stringHello as? StringWrapper2)
expectNotNil(stringHello as? String) expectNotNil(stringHello as? String)
expectNotNil(stringHello as? NSString) expectNotNil(stringHello as? NSString)
// Casting form NSString works with anything. // NSString's AnyHashable form doesn't enfore type identity.
expectNotNil(nsStringHello as? StringWrapper1) expectNotNil(nsStringHello as? StringWrapper1)
expectNotNil(nsStringHello as? StringWrapper2) expectNotNil(nsStringHello as? StringWrapper2)
expectNotNil(nsStringHello as? String) expectNotNil(nsStringHello as? String)

View File

@@ -767,6 +767,132 @@ AnyHashableTests.test("AnyHashable(MinimalHashableRCSwiftError).base") {
expectEqual(MinimalHashableRCSwiftError.self, type(of: ah.base)) expectEqual(MinimalHashableRCSwiftError.self, type(of: ah.base))
} }
AnyHashableTests.test("AnyHashable(NumericTypes)/Hashable") {
// Numeric types holding mathematically equal values must compare equal and
// hash the same way when converted to AnyHashable.
let groups: [[AnyHashable]] = [
[
1 as Int,
1 as UInt,
1 as Int8,
1 as UInt8,
1 as Int16,
1 as UInt16,
1 as Int32,
1 as UInt32,
1 as Int64,
1 as UInt64,
1 as Float,
1 as Double,
],
[
42 as Int,
42 as UInt,
42 as Int8,
42 as UInt8,
42 as Int16,
42 as UInt16,
42 as Int32,
42 as UInt32,
42 as Int64,
42 as UInt64,
42 as Float,
42 as Double,
],
[
Int(Int32.max),
UInt(Int32.max),
Int32.max,
UInt32(Int32.max),
Int64(Int32.max),
UInt64(Int32.max),
Double(Int32.max),
],
[
Float.infinity,
Double.infinity,
],
[
0x1.aP1 as Float, // 3.25
0x1.aP1 as Double,
],
[
0x1.a000000000001P1, // 3.25.nextUp, not representable by a Float
]
]
checkHashableGroups(groups)
}
#if !os(Windows) && (arch(i386) || arch(x86_64))
AnyHashableTests.test("AnyHashable(Float80)/Hashable") {
let groups: [[AnyHashable]] = [
[
42 as Int,
42 as Float,
42 as Double,
42 as Float80,
],
[
Float.infinity,
Double.infinity,
Float80.infinity,
],
[
3.25 as Float,
3.25 as Double,
3.25 as Float80,
],
[
0x1.a000000000001P1 as Double, // 3.25.nextUp
0x1.a000000000001P1 as Float80,
],
[
0x1.a000000000000002p1 as Float80, // (3.25 as Float80).nextUp
],
]
checkHashableGroups(groups)
}
#endif
#if _runtime(_ObjC)
// A wrapper type around an Int that bridges to NSNumber.
struct IntWrapper1 : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable {
let rawValue: Int
}
// A wrapper type around an Int that bridges to NSNumber.
struct IntWrapper2 : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable {
let rawValue: Int
}
// A wrapper type around an Int that bridges to NSNumber.
struct Int8Wrapper : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable {
let rawValue: Int8
}
AnyHashableTests.test("AnyHashable(IntWrappers)/Hashable") {
let groups: [[AnyHashable]] = [
[
IntWrapper1(rawValue: 42),
IntWrapper2(rawValue: 42),
Int8Wrapper(rawValue: 42),
42,
42 as Double,
42 as NSNumber,
],
[
IntWrapper1(rawValue: -23),
IntWrapper2(rawValue: -23),
Int8Wrapper(rawValue: -23),
-23,
-23 as Double,
-23 as NSNumber,
],
]
checkHashableGroups(groups)
}
#endif
#if _runtime(_ObjC) #if _runtime(_ObjC)
// A wrapper type around a String that bridges to NSString. // A wrapper type around a String that bridges to NSString.
struct StringWrapper1 : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable { struct StringWrapper1 : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable {
@@ -778,126 +904,101 @@ struct StringWrapper2 : _SwiftNewtypeWrapper, Hashable, _ObjectiveCBridgeable {
let rawValue: String let rawValue: String
} }
AnyHashableTests.test("AnyHashable(Wrappers)/Hashable") { AnyHashableTests.test("AnyHashable(StringWrappers)/Hashable") {
let values: [AnyHashable] = [ let groups: [[AnyHashable]] = [
StringWrapper1(rawValue: "hello"), [
StringWrapper2(rawValue: "hello"), StringWrapper1(rawValue: "hello"),
"hello" as String, StringWrapper2(rawValue: "hello"),
"hello" as NSString, "hello" as String,
StringWrapper1(rawValue: "world"), "hello" as NSString,
StringWrapper2(rawValue: "world"), ],
"world" as String, [
"world" as NSString, StringWrapper1(rawValue: "world"),
StringWrapper2(rawValue: "world"),
"world" as String,
"world" as NSString,
]
] ]
checkHashableGroups(groups)
func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool {
// Elements in [0, 3] match 3.
if lhs == 3 { return rhs >= 0 && rhs <= 3 }
if rhs == 3 { return lhs >= 0 && lhs <= 3 }
// Elements in [4, 7] match 7.
if lhs == 7 { return rhs >= 4 && rhs <= 7 }
if rhs == 7 { return lhs >= 4 && lhs <= 7 }
return lhs == rhs
}
func hashEqualityOracle(_ lhs: Int, _ rhs: Int) -> Bool {
// Elements in [0, 3] hash the same, as do elements in [4, 7].
return lhs / 4 == rhs / 4
}
checkHashable(
values,
equalityOracle: equalityOracle,
hashEqualityOracle: hashEqualityOracle,
allowBrokenTransitivity: true)
} }
AnyHashableTests.test("AnyHashable(Set)/Hashable") { AnyHashableTests.test("AnyHashable(Set)/Hashable") {
let values: [AnyHashable] = [ let groups: [[AnyHashable]] = [
Set([1, 2, 3]), [
NSSet(set: [1, 2, 3]), Set([1, 2, 3]),
Set([2, 3, 4]), Set([1, 2, 3] as [Int8]),
NSSet(set: [2, 3, 4]), Set([1, 2, 3] as [Float]),
Set([Set([1, 2]), Set([3, 4])]), NSSet(set: [1, 2, 3]),
NSSet(set: [NSSet(set: [1, 2]), NSSet(set: [3, 4])]), ],
Set([Set([1, 3]), Set([2, 4])]), [
NSSet(set: [NSSet(set: [1, 3]), NSSet(set: [2, 4])]), Set([2, 3, 4]),
NSSet(set: [2, 3, 4]),
],
[
Set([Set([1, 2]), Set([3, 4])]),
NSSet(set: [NSSet(set: [1, 2]), NSSet(set: [3, 4])]),
],
[
Set([Set([1, 3]), Set([2, 4])]),
NSSet(set: [NSSet(set: [1, 3]), NSSet(set: [2, 4])]),
],
] ]
checkHashableGroups(groups)
func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool {
switch (lhs, rhs) {
case (0...1, 0...1): return true
case (2...3, 2...3): return true
case (4...5, 4...5): return true
case (6...7, 6...7): return true
default: return false
}
}
checkHashable(
values,
equalityOracle: equalityOracle,
allowBrokenTransitivity: true)
} }
AnyHashableTests.test("AnyHashable(Array)/Hashable") { AnyHashableTests.test("AnyHashable(Array)/Hashable") {
let values: [AnyHashable] = [ let groups: [[AnyHashable]] = [
[1, 2, 3], [
NSArray(array: [1, 2, 3]), [1, 2, 3],
[3, 2, 1], [1, 2, 3] as [Int8],
NSArray(array: [3, 2, 1]), [1, 2, 3] as [Double],
[[1, 2], [3, 4]], NSArray(array: [1, 2, 3]),
NSArray(array: [NSArray(array: [1, 2]), NSArray(array: [3, 4])]), ],
[[3, 4], [1, 2]], [
NSArray(array: [NSArray(array: [3, 4]), NSArray(array: [1, 2])]), [3, 2, 1],
[3, 2, 1] as [AnyHashable],
NSArray(array: [3, 2, 1]),
],
[
[[1, 2], [3, 4]],
NSArray(array: [NSArray(array: [1, 2]), NSArray(array: [3, 4])]),
],
[
[[3, 4], [1, 2]],
NSArray(array: [NSArray(array: [3, 4]), NSArray(array: [1, 2])]),
]
] ]
checkHashableGroups(groups)
func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool {
switch (lhs, rhs) {
case (0...1, 0...1): return true
case (2...3, 2...3): return true
case (4...5, 4...5): return true
case (6...7, 6...7): return true
default: return false
}
}
checkHashable(values, equalityOracle: equalityOracle,
allowBrokenTransitivity: true)
} }
AnyHashableTests.test("AnyHashable(Dictionary)/Hashable") { AnyHashableTests.test("AnyHashable(Dictionary)/Hashable") {
let values: [AnyHashable] = [ let groups: [[AnyHashable]] = [
["hello": 1, "world": 2], [
NSDictionary(dictionary: ["hello": 1, "world": 2]), ["hello": 1, "world": 2] as [String: Int],
["hello": 2, "world": 1], ["hello": 1, "world": 2] as [String: Int16],
NSDictionary(dictionary: ["hello": 2, "world": 1]), ["hello": 1, "world": 2] as [String: Float],
["hello": ["foo": 1, "bar": 2], NSDictionary(dictionary: ["hello": 1, "world": 2]),
"world": ["foo": 2, "bar": 1]], ],
NSDictionary(dictionary: [ [
"hello": ["foo": 1, "bar": 2], ["hello": 2, "world": 1],
"world": ["foo": 2, "bar": 1]]), NSDictionary(dictionary: ["hello": 2, "world": 1]),
["hello": ["foo": 2, "bar": 1], ],
"world": ["foo": 1, "bar": 2]], [
NSDictionary(dictionary: [ ["hello": ["foo": 1, "bar": 2],
"hello": ["foo": 2, "bar": 1], "world": ["foo": 2, "bar": 1]],
"world": ["foo": 1, "bar": 2]]), NSDictionary(dictionary: [
"hello": ["foo": 1, "bar": 2],
"world": ["foo": 2, "bar": 1]]),
],
[
["hello": ["foo": 2, "bar": 1],
"world": ["foo": 1, "bar": 2]],
NSDictionary(dictionary: [
"hello": ["foo": 2, "bar": 1],
"world": ["foo": 1, "bar": 2]]),
],
] ]
checkHashableGroups(groups)
func equalityOracle(_ lhs: Int, _ rhs: Int) -> Bool {
switch (lhs, rhs) {
case (0...1, 0...1): return true
case (2...3, 2...3): return true
case (4...5, 4...5): return true
case (6...7, 6...7): return true
default: return false
}
}
checkHashable(values, equalityOracle: equalityOracle,
allowBrokenTransitivity: true)
} }
AnyHashableTests.test("AnyHashable(_SwiftNativeNSError(MinimalHashablePODSwiftError))/Hashable") { AnyHashableTests.test("AnyHashable(_SwiftNativeNSError(MinimalHashablePODSwiftError))/Hashable") {

View File

@@ -6,26 +6,73 @@ import StdlibUnittest
var DictionaryTests = TestSuite("Dictionary") var DictionaryTests = TestSuite("Dictionary")
DictionaryTests.test("index<Hashable>(forKey:)") { DictionaryTests.test("index<Hashable>(forKey:)") {
let d: [AnyHashable : Int] = [ let a = AnyHashable(10 as UInt16)
AnyHashable(10) : 1010, let b = AnyHashable(20)
AnyHashable(20) : 2020, let c = AnyHashable(30.0)
AnyHashable(30.0) : 3030, let d: [AnyHashable: Int] = [
a: 1010,
b: 2020,
c: 3030,
] ]
expectEqual(1010, d[d.index(forKey: 10)!].value) for (key, k, value) in [(a, 10, 1010), (b, 20, 2020), (c, 30, 3030)] {
expectEqual(2020, d[d.index(forKey: 20)!].value) let index = d.index(forKey: key)!
expectEqual(3030, d[d.index(forKey: 30.0)!].value) expectEqual(value, d[index].value)
// We must be able to look up the same number in any representation.
expectEqual(index, d.index(forKey: UInt8(k)))
expectEqual(index, d.index(forKey: UInt16(k)))
expectEqual(index, d.index(forKey: UInt32(k)))
expectEqual(index, d.index(forKey: UInt64(k)))
expectEqual(index, d.index(forKey: UInt(k)))
expectEqual(index, d.index(forKey: Int8(k)))
expectEqual(index, d.index(forKey: Int16(k)))
expectEqual(index, d.index(forKey: Int32(k)))
expectEqual(index, d.index(forKey: Int64(k)))
expectEqual(index, d.index(forKey: Int(k)))
expectEqual(index, d.index(forKey: Float(k)))
expectEqual(index, d.index(forKey: Double(k)))
expectNil(d.index(forKey: 10.0)) expectNil(d.index(forKey: String(k)))
expectNil(d.index(forKey: 20.0)) }
expectNil(d.index(forKey: 30))
} }
DictionaryTests.test("subscript<Hashable>(_:)") { DictionaryTests.test("subscript<Hashable>(_:)") {
var d: [AnyHashable : Int] = [ let a = AnyHashable(10 as UInt16)
AnyHashable(10) : 1010, let b = AnyHashable(20)
AnyHashable(20) : 2020, let c = AnyHashable(30.0)
AnyHashable(30.0) : 3030, let d: [AnyHashable: Int] = [
a: 1010,
b: 2020,
c: 3030,
]
for (key, k, value) in [(a, 10, 1010), (b, 20, 2020), (c, 30, 3030)] {
let index = d.index(forKey: key)!
expectEqual(value, d[key])
// We must be able to look up the same number in any representation.
expectEqual(value, d[UInt8(k)])
expectEqual(value, d[UInt16(k)])
expectEqual(value, d[UInt32(k)])
expectEqual(value, d[UInt64(k)])
expectEqual(value, d[UInt(k)])
expectEqual(value, d[Int8(k)])
expectEqual(value, d[Int16(k)])
expectEqual(value, d[Int32(k)])
expectEqual(value, d[Int64(k)])
expectEqual(value, d[Int(k)])
expectEqual(value, d[Float(k)])
expectEqual(value, d[Double(k)])
expectNil(d[String(k)])
}
}
DictionaryTests.test("subscript<Hashable>(_:)/2") {
var d: [AnyHashable: Int] = [
AnyHashable(10): 1010,
AnyHashable(20): 2020,
AnyHashable(30.0): 3030,
] ]
expectEqual(1010, d[10]) expectEqual(1010, d[10])
@@ -100,57 +147,61 @@ DictionaryTests.test("updateValue<Hashable>(_:forKey:)") {
expectEqual(expected, d) expectEqual(expected, d)
} }
expectNil(d.updateValue(4040, forKey: 10.0)) expectEqual(101010, d.updateValue(4040, forKey: 10.0))
do { do {
let expected: [AnyHashable : Int] = [ let expected: [AnyHashable : Int] = [
AnyHashable(10) : 101010, AnyHashable(10) : 4040,
AnyHashable(20) : 202020, AnyHashable(20) : 202020,
AnyHashable(30.0) : 303030, AnyHashable(30.0) : 303030,
AnyHashable(10.0) : 4040,
] ]
expectEqual(expected, d) expectEqual(expected, d)
} }
expectNil(d.updateValue(5050, forKey: 20.0)) expectEqual(202020, d.updateValue(5050, forKey: 20.0))
do { do {
let expected: [AnyHashable : Int] = [ let expected: [AnyHashable : Int] = [
AnyHashable(10) : 101010, AnyHashable(10) : 4040,
AnyHashable(20) : 202020, AnyHashable(20) : 5050,
AnyHashable(30.0) : 303030, AnyHashable(30.0) : 303030,
AnyHashable(10.0) : 4040,
AnyHashable(20.0) : 5050,
] ]
expectEqual(expected, d) expectEqual(expected, d)
} }
expectNil(d.updateValue(6060, forKey: 30)) expectEqual(303030, d.updateValue(6060, forKey: 30))
do { do {
let expected: [AnyHashable : Int] = [ let expected: [AnyHashable : Int] = [
AnyHashable(10) : 101010, AnyHashable(10) : 4040,
AnyHashable(20) : 202020, AnyHashable(20) : 5050,
AnyHashable(30.0) : 303030, AnyHashable(30.0) : 6060,
AnyHashable(10.0) : 4040,
AnyHashable(20.0) : 5050,
AnyHashable(30) : 6060,
] ]
expectEqual(expected, d) expectEqual(expected, d)
} }
} }
DictionaryTests.test("removeValue<Hashable>(forKey:)") { DictionaryTests.test("removeValue<Hashable>(forKey:)") {
var d: [AnyHashable : Int] = [ let d: [AnyHashable : Int] = [
AnyHashable(10) : 1010, AnyHashable(10 as UInt8) : 1010,
AnyHashable(20) : 2020, AnyHashable(20) : 2020,
AnyHashable(30.0) : 3030, AnyHashable(30.0) : 3030,
] ]
expectNil(d.removeValue(forKey: 10.0)) for (key, value) in [(10, 1010), (20, 2020), (30, 3030)] {
expectNil(d.removeValue(forKey: 20.0)) var dd = d
expectNil(d.removeValue(forKey: 30)) expectEqual(value, dd.removeValue(forKey: UInt8(key)))
dd = d; expectEqual(value, dd.removeValue(forKey: UInt16(key)))
dd = d; expectEqual(value, dd.removeValue(forKey: UInt32(key)))
dd = d; expectEqual(value, dd.removeValue(forKey: UInt64(key)))
dd = d; expectEqual(value, dd.removeValue(forKey: UInt(key)))
dd = d; expectEqual(value, dd.removeValue(forKey: Int8(key)))
dd = d; expectEqual(value, dd.removeValue(forKey: Int16(key)))
dd = d; expectEqual(value, dd.removeValue(forKey: Int32(key)))
dd = d; expectEqual(value, dd.removeValue(forKey: Int64(key)))
dd = d; expectEqual(value, dd.removeValue(forKey: Int(key)))
dd = d; expectEqual(value, dd.removeValue(forKey: Float(key)))
dd = d; expectEqual(value, dd.removeValue(forKey: Double(key)))
expectEqual(1010, d.removeValue(forKey: 10)!) dd = d; expectNil(dd.removeValue(forKey: String(key)))
expectEqual(2020, d.removeValue(forKey: 20)!) }
expectEqual(3030, d.removeValue(forKey: 30.0)!)
} }
runAllTests() runAllTests()

View File

@@ -233,22 +233,26 @@ hash_value_test_template = gyb.parse_template("hash_value",
% for self_ty in all_integer_types(word_bits): % for self_ty in all_integer_types(word_bits):
% Self = self_ty.stdlib_name % Self = self_ty.stdlib_name
FixedPoint.test("${Self}.hashValue") { FixedPoint.test("${Self}.hash(into:)") {
% for bit_pattern in test_bit_patterns: % for bit_pattern in test_bit_patterns:
do { do {
% input = prepare_bit_pattern(bit_pattern, self_ty.bits, self_ty.is_signed) % input = prepare_bit_pattern(bit_pattern, self_ty.bits, self_ty.is_signed)
let input = get${Self}(${input}) let input = get${Self}(${input})
let output = getInt(input.hashValue) var hasher = Hasher()
input.hash(into: &hasher)
let output = getInt(hasher.finalize())
var hasher = _SipHash13(_seed: Hasher._seed) % reference = prepare_bit_pattern(bit_pattern, self_ty.bits, False)
% if prepare_bit_pattern(input, word_bits, self_ty.is_signed) == input: % if self_ty.bits == 64:
hasher._combine(UInt(truncatingIfNeeded: ${input} as ${"" if self_ty.is_signed else "U"}Int)) let expected = Hasher._hash(seed: Hasher._seed, ${reference} as UInt64)
% else: % else:
hasher._combine(UInt64(truncatingIfNeeded: input)) let expected = Hasher._hash(
seed: Hasher._seed,
bytes: ${reference},
count: ${self_ty.bits / 8})
% end % end
let expected = getInt(Int(truncatingIfNeeded: hasher.finalize()))
expectEqual(expected, output, "input: \(input)") expectEqual(expected, output, "input: \(input)")
} }

View File

@@ -28,28 +28,45 @@ var SetTests = TestSuite("Set")
SetTests.test("contains<Hashable>(_:)") { SetTests.test("contains<Hashable>(_:)") {
let s: Set<AnyHashable> = [ let s: Set<AnyHashable> = [
AnyHashable(1010), AnyHashable(2020), AnyHashable(3030.0) AnyHashable(1010 as UInt16), AnyHashable(2020), AnyHashable(3030.0)
] ]
expectTrue(s.contains(1010)) for i in [1010, 2020, 3030] {
expectTrue(s.contains(2020)) // We must be able to look up the same number in any representation.
expectTrue(s.contains(3030.0)) expectTrue(s.contains(UInt16(i)))
expectTrue(s.contains(UInt32(i)))
expectTrue(s.contains(UInt64(i)))
expectTrue(s.contains(UInt(i)))
expectTrue(s.contains(Int16(i)))
expectTrue(s.contains(Int32(i)))
expectTrue(s.contains(Int64(i)))
expectTrue(s.contains(Int(i)))
expectTrue(s.contains(Float(i)))
expectTrue(s.contains(Double(i)))
expectFalse(s.contains(1010.0)) expectFalse(s.contains(String(i)))
expectFalse(s.contains(2020.0)) }
expectFalse(s.contains(3030))
} }
SetTests.test("index<Hashable>(of:)") { SetTests.test("index<Hashable>(of:)") {
let s: Set<AnyHashable> = [ let a = AnyHashable(1010 as UInt16)
AnyHashable(1010), AnyHashable(2020), AnyHashable(3030.0) let b = AnyHashable(2020)
] let c = AnyHashable(3030.0)
expectEqual(AnyHashable(1010), s[s.firstIndex(of: 1010)!]) let s: Set<AnyHashable> = [a, b, c]
expectEqual(AnyHashable(2020), s[s.firstIndex(of: 2020)!]) for (element, i) in [(a, 1010), (b, 2020), (c, 3030)] {
expectEqual(AnyHashable(3030.0), s[s.firstIndex(of: 3030.0)!]) let index = s.firstIndex(of: element)!
expectNil(s.firstIndex(of: 1010.0)) // We must be able to look up the same number in any representation.
expectNil(s.firstIndex(of: 2020.0)) expectEqual(index, s.firstIndex(of: UInt16(i)))
expectNil(s.firstIndex(of: 3030)) expectEqual(index, s.firstIndex(of: UInt32(i)))
expectEqual(index, s.firstIndex(of: UInt64(i)))
expectEqual(index, s.firstIndex(of: UInt(i)))
expectEqual(index, s.firstIndex(of: Int16(i)))
expectEqual(index, s.firstIndex(of: Int32(i)))
expectEqual(index, s.firstIndex(of: Int64(i)))
expectEqual(index, s.firstIndex(of: Int(i)))
expectEqual(index, s.firstIndex(of: Float(i)))
expectEqual(index, s.firstIndex(of: Double(i)))
}
} }
SetTests.test("insert<Hashable>(_:)") { SetTests.test("insert<Hashable>(_:)") {
@@ -230,5 +247,28 @@ SetTests.test("remove<Hashable>(_:)/CastTrap")
s.remove(TestHashableDerivedB(2020, identity: 2)) s.remove(TestHashableDerivedB(2020, identity: 2))
} }
SetTests.test("Hashable/Conversions") {
let input: [Set<AnyHashable>] = [
[10 as UInt8, 20 as UInt8, 30 as UInt8],
[10 as UInt16, 20 as UInt16, 30 as UInt16],
[10 as UInt32, 20 as UInt32, 30 as UInt32],
[10 as UInt64, 20 as UInt64, 30 as UInt64],
[10 as UInt, 20 as UInt, 30 as UInt],
[10 as Int8, 20 as Int8, 30 as Int8],
[10 as Int16, 20 as Int16, 30 as Int16],
[10 as Int32, 20 as Int32, 30 as Int32],
[10 as Int64, 20 as Int64, 30 as Int64],
[10 as Int, 20 as Int, 30 as Int],
[10 as Float, 20 as Float, 30 as Float],
[10 as Double, 20 as Double, 30 as Double],
[[1, 2, 3] as Set<Int>, [2, 3, 4] as Set<UInt8>, [3, 4, 5] as Set<Float>],
[[1, 2, 3] as Set<Int8>, [2, 3, 4] as Set<Double>, [3, 4, 5] as Set<Int32>],
[[1, 2, 3] as Set<UInt32>, [2, 3, 4] as Set<Int16>, [3, 4, 5] as Set<UInt>],
]
checkHashable(input, equalityOracle: { ($0 < 12) == ($1 < 12) })
}
runAllTests() runAllTests()