diff --git a/CHANGELOG.md b/CHANGELOG.md index a3928c93936f6..f7bfa6eeb8a1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,15 @@ Swift 5.0 Swift 4.1 --------- +* [SE-0143][] + + The standard library types `Optional`, `Array`, `ArraySlice`, + `ContiguousArray`, `Dictionary`, `DictionaryLiteral`, `Range`, `ClosedRange`, + `AnyCollection`, and `CollectionOfOne` now conform to the `Hashable` protocol + when their element or bound types (as the case may be) conform to `Hashable`. + This makes synthesized `Hashable` implementations available for types that + include stored properties of these types. + * [SE-0189][] If an initializer is declared in a different module from a struct, it must @@ -74,12 +83,12 @@ Swift 4.1 s[keyPath: p] // "H" ``` -* [SE-0143][] The standard library types `Optional`, `Array`, and - `Dictionary` now conform to the `Equatable` protocol when their element types - conform to `Equatable`. This allows the `==` operator to compose (e.g., one - can compare two values of type `[Int : [Int?]?]` with `==`), as well as use - various algorthims defined for `Equatable` element types, such as - `index(of:)`. +* [SE-0143][] The standard library types `Optional`, `Array`, `ArraySlice`, + `ContiguousArray`, and `Dictionary` now conform to the `Equatable` protocol + when their element types conform to `Equatable`. This allows the `==` operator + to compose (e.g., one can compare two values of type `[Int : [Int?]?]` with + `==`), as well as use various algorthims defined for `Equatable` element + types, such as `index(of:)`. * [SE-0157][] is implemented. Associated types can now declare "recursive" constraints, which require that the associated type conform to the enclosing diff --git a/lib/ClangImporter/ImportType.cpp b/lib/ClangImporter/ImportType.cpp index 00c7b69c4212f..251c03360c9be 100644 --- a/lib/ClangImporter/ImportType.cpp +++ b/lib/ClangImporter/ImportType.cpp @@ -998,7 +998,15 @@ namespace { if (unboundDecl == Impl.SwiftContext.getDictionaryDecl() || unboundDecl == Impl.SwiftContext.getSetDecl()) { auto &keyType = importedTypeArgs[0]; - if (!Impl.matchesHashableBound(keyType)) { + auto keyStructDecl = keyType->getStructOrBoundGenericStruct(); + if (!Impl.matchesHashableBound(keyType) || + // Dictionary and Array conditionally conform to Hashable, + // but the conformance doesn't necessarily apply with the + // imported versions of their type arguments. + // FIXME: Import their non-Hashable type parameters as + // AnyHashable in this context. + keyStructDecl == Impl.SwiftContext.getDictionaryDecl() || + keyStructDecl == Impl.SwiftContext.getArrayDecl()) { if (auto anyHashable = Impl.SwiftContext.getAnyHashableDecl()) keyType = anyHashable->getDeclaredType(); else diff --git a/stdlib/public/SDK/Foundation/NSArray.swift b/stdlib/public/SDK/Foundation/NSArray.swift index adecca6c4bf72..b5825b13f49cb 100644 --- a/stdlib/public/SDK/Foundation/NSArray.swift +++ b/stdlib/public/SDK/Foundation/NSArray.swift @@ -106,6 +106,14 @@ extension Array : _ObjectiveCBridgeable { } } +extension NSArray : _HasCustomAnyHashableRepresentation { + // Must be @nonobjc to avoid infinite recursion during bridging + @nonobjc + public func _toCustomAnyHashable() -> AnyHashable? { + return AnyHashable(self as! Array) + } +} + extension NSArray : Sequence { /// Return an *iterator* over the elements of this *sequence*. /// diff --git a/stdlib/public/SDK/Foundation/NSDictionary.swift b/stdlib/public/SDK/Foundation/NSDictionary.swift index 57eb4cdf40926..eeb3de3d6c94d 100644 --- a/stdlib/public/SDK/Foundation/NSDictionary.swift +++ b/stdlib/public/SDK/Foundation/NSDictionary.swift @@ -138,6 +138,14 @@ extension Dictionary : _ObjectiveCBridgeable { } } +extension NSDictionary : _HasCustomAnyHashableRepresentation { + // Must be @nonobjc to avoid infinite recursion during bridging + @nonobjc + public func _toCustomAnyHashable() -> AnyHashable? { + return AnyHashable(self as! Dictionary) + } +} + extension NSDictionary : Sequence { // FIXME: A class because we can't pass a struct with class fields through an // [objc] interface without prematurely destroying the references. diff --git a/stdlib/public/core/Arrays.swift.gyb b/stdlib/public/core/Arrays.swift.gyb index 00aeab8401f0b..24c1bd4d54abe 100644 --- a/stdlib/public/core/Arrays.swift.gyb +++ b/stdlib/public/core/Arrays.swift.gyb @@ -2250,6 +2250,18 @@ extension ${Self} : Equatable where Element : Equatable { } } +extension ${Self} : Hashable where Element : Hashable { + @_inlineable // FIXME(sil-serialize-all) + public var hashValue: Int { + // FIXME(ABI)#177: Issue applies to Array too + var result = 0 + for element in self { + result = _combineHashValues(result, element.hashValue) + } + return result + } +} + extension ${Self} { /// Calls the given closure with a pointer to the underlying bytes of the /// array's mutable contiguous storage. diff --git a/stdlib/public/core/ClosedRange.swift b/stdlib/public/core/ClosedRange.swift index bd75d05d438ee..89cef2232d728 100644 --- a/stdlib/public/core/ClosedRange.swift +++ b/stdlib/public/core/ClosedRange.swift @@ -167,8 +167,9 @@ extension ClosedRange.Index : Comparable { } } -extension ClosedRange.Index: Hashable -where Bound: Strideable, Bound.Stride: SignedInteger, Bound: Hashable { +extension ClosedRange.Index: Hashable + where Bound: Strideable, Bound.Stride: SignedInteger, Bound: Hashable { + @_inlineable // FIXME(sil-serialize-all) public var hashValue: Int { switch self { case .inRange(let value): @@ -376,9 +377,19 @@ extension ClosedRange: Equatable { } } +extension ClosedRange : Hashable where Bound : Hashable { + @_inlineable // FIXME(sil-serialize-all) + public var hashValue: Int { + var result = 0 + result = _combineHashValues(result, lowerBound.hashValue) + result = _combineHashValues(result, upperBound.hashValue) + return result + } +} + extension ClosedRange : CustomStringConvertible { /// A textual representation of the range. - @_inlineable // FIXME(sil-serialize-all)...\( + @_inlineable // FIXME(sil-serialize-all) public var description: String { return "\(lowerBound)...\(upperBound)" } diff --git a/stdlib/public/core/CollectionOfOne.swift b/stdlib/public/core/CollectionOfOne.swift index ba345fa59fd49..b4c8d45f2cf44 100644 --- a/stdlib/public/core/CollectionOfOne.swift +++ b/stdlib/public/core/CollectionOfOne.swift @@ -136,6 +136,20 @@ extension CollectionOfOne: RandomAccessCollection, MutableCollection { } } +extension CollectionOfOne : Equatable, _Equatable where Element : Equatable { + @_inlineable // FIXME(sil-serialize-all) + public func _isEqual(to other: CollectionOfOne) -> Bool { + return _element == other._element + } +} + +extension CollectionOfOne : Hashable where Element : Hashable { + @_inlineable // FIXME(sil-serialize-all) + public var hashValue: Int { + return _element.hashValue + } +} + extension CollectionOfOne : CustomDebugStringConvertible { /// A textual representation of `self`, suitable for debugging. @_inlineable // FIXME(sil-serialize-all) diff --git a/stdlib/public/core/DropWhile.swift.gyb b/stdlib/public/core/DropWhile.swift.gyb index c15e285fcc09b..e9d223a56a569 100644 --- a/stdlib/public/core/DropWhile.swift.gyb +++ b/stdlib/public/core/DropWhile.swift.gyb @@ -141,6 +141,7 @@ public struct LazyDropWhileIndex : Comparable { } extension LazyDropWhileIndex : Hashable where Base.Index : Hashable { + @_inlineable // FIXME(sil-serialize-all) public var hashValue: Int { return base.hashValue } diff --git a/stdlib/public/core/EmptyCollection.swift b/stdlib/public/core/EmptyCollection.swift index 4fb6d8d737aa3..ca85ff26fd009 100644 --- a/stdlib/public/core/EmptyCollection.swift +++ b/stdlib/public/core/EmptyCollection.swift @@ -173,5 +173,12 @@ extension EmptyCollection : Equatable { } } +extension EmptyCollection : Hashable { + @_inlineable // FIXME(sil-serialize-all) + public var hashValue: Int { + return 0 + } +} + // @available(*, deprecated, renamed: "EmptyCollection.Iterator") public typealias EmptyIterator = EmptyCollection.Iterator diff --git a/stdlib/public/core/Equatable.swift b/stdlib/public/core/Equatable.swift index e8dbe6fa11d30..ebaec7964f49e 100644 --- a/stdlib/public/core/Equatable.swift +++ b/stdlib/public/core/Equatable.swift @@ -195,6 +195,46 @@ extension Equatable { } } +// FIXME(ABI): Remove this protocol when `@_implements` is supported in +// conditional conformances. +// +// Ordinarily, we don't have to worry about adding new methods to types in the +// standard library. If end users have added identically named methods in their +// own extensions, those will seamlessly shadow our methods without any source +// breakage. +// +// However, if we add new methods that are spelled as _operators_ to types in +// the standard library, then any identically named methods implemented by end +// users will cause ambiguity at the call site. This prevents the addition of +// conditional conformance to `Equatable` to types where users may have already +// defined `==` for themselves. +// +// The underscored attribute `@_implements` was created to work around these +// limitations for derived conformances. However, at present, we can't use the +// same attribute for conditional conformances because the attribute doesn't +// work inside extensions (SR-NNNN). +// +// What _does_ work, however, is a conceptually similar design where we add only +// methods that aren't spelled as operators to the concrete type. Meanwhile, the +// protocol itself would provide a default implementation of the method that +// _is_ spelled as an operator, which in turn calls the non-operator method. +// With such a design, any operator method of the same name defined by the end +// user is selected preferentially over the protocol's default implementation. +// +// Such is the purpose of the following underscored protocol. + +public protocol _Equatable: Equatable { + func _isEqual(to other: Self) -> Bool +} + +extension _Equatable { + @_inlineable // FIXME(sil-serialize-all) + @_transparent + public static func == (lhs: Self, rhs: Self) -> Bool { + return lhs._isEqual(to: rhs) + } +} + //===----------------------------------------------------------------------===// // Reference comparison //===----------------------------------------------------------------------===// diff --git a/stdlib/public/core/ExistentialCollection.swift.gyb b/stdlib/public/core/ExistentialCollection.swift.gyb index 49fd2b0485464..3cfc64191a4b9 100644 --- a/stdlib/public/core/ExistentialCollection.swift.gyb +++ b/stdlib/public/core/ExistentialCollection.swift.gyb @@ -1243,3 +1243,23 @@ extension ${Self}: _AnyCollectionProtocol { } } % end + +extension AnyCollection : Equatable, _Equatable where Element : Equatable { + @_inlineable // FIXME(sil-serialize-all) + public func _isEqual(to other: AnyCollection) -> Bool { + if count != other.count { return false } + return elementsEqual(other) + } +} + +extension AnyCollection : Hashable where Element : Hashable { + @_inlineable // FIXME(sil-serialize-all) + public var hashValue: Int { + // FIXME(ABI)#177: Cache AnyCollection hashValue + var result = 0 + for element in self { + result = _combineHashValues(result, element.hashValue) + } + return result + } +} diff --git a/stdlib/public/core/Flatten.swift b/stdlib/public/core/Flatten.swift index a3c806c65365a..1b16fd16dec03 100644 --- a/stdlib/public/core/Flatten.swift +++ b/stdlib/public/core/Flatten.swift @@ -232,8 +232,12 @@ extension FlattenCollection.Index : Comparable { extension FlattenCollection.Index : Hashable where Base.Index : Hashable, Base.Element.Index : Hashable { + @_inlineable // FIXME(sil-serialize-all) public var hashValue: Int { - return _mixInt(_inner?.hashValue ?? 0) ^ _outer.hashValue + var result = 0 + result = _combineHashValues(result, _inner.hashValue) + result = _combineHashValues(result, _outer.hashValue) + return result } } diff --git a/stdlib/public/core/HashedCollections.swift.gyb b/stdlib/public/core/HashedCollections.swift.gyb index 8045b6a43d565..068adca0cbd30 100644 --- a/stdlib/public/core/HashedCollections.swift.gyb +++ b/stdlib/public/core/HashedCollections.swift.gyb @@ -37,9 +37,9 @@ import SwiftShims // // Dictionary (a struct) // +------------------------------------------------+ -// | _VariantDictionaryBuffer (an enum) | +// | _VariantDictionaryBuffer (an enum) | // | +--------------------------------------------+ | -// | | [_NativeDictionaryBuffer (a struct)] | | +// | | [_NativeDictionaryBuffer (a struct)] | | // | +---|----------------------------------------+ | // +----/-------------------------------------------+ // / @@ -61,9 +61,9 @@ import SwiftShims // // Dictionary (a struct) // +----------------------------------------------+ -// | _VariantDictionaryBuffer (an enum) | +// | _VariantDictionaryBuffer (an enum) | // | +----------------------------------------+ | -// | | [ _CocoaDictionaryBuffer (a struct) ] | | +// | | [ _CocoaDictionaryBuffer (a struct) ] | | // | +---|------------------------------------+ | // +-----|----------------------------------------+ // | @@ -2786,6 +2786,25 @@ extension Dictionary : Equatable where Value : Equatable { } } +extension Dictionary : Hashable where Value : Hashable { + /// The hash value for the dictionary. + /// + /// Two dictionaries that are equal will always have equal hash values. + /// + /// Hash values are not guaranteed to be equal across different executions of + /// your program. Do not save hash values to use during a future execution. + @_inlineable // FIXME(sil-serialize-all) + public var hashValue: Int { + // FIXME(ABI)#177: Cache Dictionary hashValue + var result = 0 + for (k, v) in self { + let combined = _combineHashValues(k.hashValue, v.hashValue) + result ^= _mixInt(combined) + } + return result + } +} + extension Dictionary : CustomStringConvertible, CustomDebugStringConvertible { @_inlineable // FIXME(sil-serialize-all) @_versioned // FIXME(sil-serialize-all) diff --git a/stdlib/public/core/Mirror.swift b/stdlib/public/core/Mirror.swift index a8aee9408fa0b..1ba9424eb8e1e 100644 --- a/stdlib/public/core/Mirror.swift +++ b/stdlib/public/core/Mirror.swift @@ -905,6 +905,29 @@ extension DictionaryLiteral : RandomAccessCollection { } } +extension DictionaryLiteral : Equatable, _Equatable + where Key : Equatable, Value : Equatable { + @_inlineable // FIXME(sil-serialize-all) + public func _isEqual(to other: DictionaryLiteral) -> Bool { + if count != other.count { return false } + return elementsEqual(other, by: ==) + } +} + +extension DictionaryLiteral : Hashable where Key : Hashable, Value : Hashable { + @_inlineable // FIXME(sil-serialize-all) + public var hashValue: Int { + // FIXME(ABI)#177: Issue applies to DictionaryLiteral too + var result = 0 + for element in self { + let elementHashValue = + _combineHashValues(element.key.hashValue, element.value.hashValue) + result = _combineHashValues(result, elementHashValue) + } + return result + } +} + extension String { /// Creates a string representing the given value. /// diff --git a/stdlib/public/core/Optional.swift b/stdlib/public/core/Optional.swift index 459b249ad2822..00279a2f9b931 100644 --- a/stdlib/public/core/Optional.swift +++ b/stdlib/public/core/Optional.swift @@ -409,6 +409,27 @@ extension Optional : Equatable where Wrapped : Equatable { } } +extension Optional : Hashable where Wrapped : Hashable { + /// The hash value for the optional instance. + /// + /// Two optionals that are equal will always have equal hash values. + /// + /// Hash values are not guaranteed to be equal across different executions of + /// your program. Do not save hash values to use during a future execution. + @_inlineable // FIXME(sil-serialize-all) + public var hashValue: Int { + var result: Int + switch self { + case .none: + result = 0 + case .some(let wrapped): + result = 1 + result = _combineHashValues(result, wrapped.hashValue) + } + return result + } +} + // Enable pattern matching against the nil literal, even if the element type // isn't equatable. @_fixed_layout diff --git a/stdlib/public/core/Range.swift b/stdlib/public/core/Range.swift index fbd59ba9e9f78..c925d3decea04 100644 --- a/stdlib/public/core/Range.swift +++ b/stdlib/public/core/Range.swift @@ -358,6 +358,16 @@ extension Range: Equatable { } } +extension Range : Hashable where Bound : Hashable { + @_inlineable // FIXME(sil-serialize-all) + public var hashValue: Int { + var result = 0 + result = _combineHashValues(result, lowerBound.hashValue) + result = _combineHashValues(result, upperBound.hashValue) + return result + } +} + /// A partial half-open interval up to, but not including, an upper bound. /// /// You create `PartialRangeUpTo` instances by using the prefix half-open range diff --git a/stdlib/public/core/Reverse.swift b/stdlib/public/core/Reverse.swift index ad08943de8241..60f72c4393513 100644 --- a/stdlib/public/core/Reverse.swift +++ b/stdlib/public/core/Reverse.swift @@ -194,6 +194,7 @@ extension ReversedCollection.Index: Comparable { } extension ReversedCollection.Index: Hashable where Base.Index: Hashable { + @_inlineable // FIXME(sil-serialize-all) public var hashValue: Int { return base.hashValue } diff --git a/test/stdlib/DictionaryLiteral.swift b/test/stdlib/DictionaryLiteral.swift index 8fc3c8875060d..ceb2c0f18a647 100644 --- a/test/stdlib/DictionaryLiteral.swift +++ b/test/stdlib/DictionaryLiteral.swift @@ -54,3 +54,12 @@ expectType(DictionaryLiteral.self, &hetero1) var hetero2: DictionaryLiteral = ["a": 1 as NSNumber, "b": "Foo" as NSString] expectType(DictionaryLiteral.self, &hetero2) + +let instances: [DictionaryLiteral] = [ + [1: "a", 1: "a", 2: "b"], + [1: "a", 2: "b", 1: "a"], + [2: "b", 1: "a", 1: "a"], + [1: "a", 1: "a", 1: "a"] +] +checkEquatable(instances, oracle: { $0 == $1 }) +checkHashable(instances, equalityOracle: { $0 == $1 }) diff --git a/test/stdlib/Optional.swift b/test/stdlib/Optional.swift index 30c4b94270e0d..5b714716695a6 100644 --- a/test/stdlib/Optional.swift +++ b/test/stdlib/Optional.swift @@ -69,6 +69,19 @@ OptionalTests.test("Equatable") { expectEqual([false, true, true, true, true, false], testRelation(!=)) } +OptionalTests.test("Hashable") { + let o1: Optional = .some(1010) + let o2: Optional = .some(2020) + let o3: Optional = .none + checkHashable([o1, o2, o3], equalityOracle: { $0 == $1 }) + + let oo1: Optional> = .some(.some(1010)) + let oo2: Optional> = .some(.some(2010)) + let oo3: Optional> = .some(.none) + let oo4: Optional> = .none + checkHashable([oo1, oo2, oo3, oo4], equalityOracle: { $0 == $1 }) +} + OptionalTests.test("CustomReflectable") { // Test with a non-refcountable type. do { diff --git a/validation-test/stdlib/AnyHashable.swift.gyb b/validation-test/stdlib/AnyHashable.swift.gyb index 4a5ee24863f57..fcdefb0876b7a 100644 --- a/validation-test/stdlib/AnyHashable.swift.gyb +++ b/validation-test/stdlib/AnyHashable.swift.gyb @@ -767,6 +767,90 @@ AnyHashableTests.test("AnyHashable(Wrappers)/Hashable") { allowBrokenTransitivity: true) } +AnyHashableTests.test("AnyHashable(Set)/Hashable") { + let values: [AnyHashable] = [ + Set([1, 2, 3]), + NSSet(set: [1, 2, 3]), + 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])]), + ] + + 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") { + let values: [AnyHashable] = [ + [1, 2, 3], + NSArray(array: [1, 2, 3]), + [3, 2, 1], + 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])]), + ] + + 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") { + let values: [AnyHashable] = [ + ["hello": 1, "world": 2], + NSDictionary(dictionary: ["hello": 1, "world": 2]), + ["hello": 2, "world": 1], + NSDictionary(dictionary: ["hello": 2, "world": 1]), + ["hello": ["foo": 1, "bar": 2], + "world": ["foo": 2, "bar": 1]], + 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]]), + ] + + 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") { let swiftErrors: [MinimalHashablePODSwiftError] = [ .caseA, .caseA, diff --git a/validation-test/stdlib/AnyHashableDiagnostics.swift b/validation-test/stdlib/AnyHashableDiagnostics.swift deleted file mode 100644 index be5bb5e4615e9..0000000000000 --- a/validation-test/stdlib/AnyHashableDiagnostics.swift +++ /dev/null @@ -1,13 +0,0 @@ -// RUN: %target-typecheck-verify-swift - -// If this test fails, the following types started to conditionally conform to -// `Hashable`. When that happens, please add a custom `AnyHashable` -// representation to corresponding Objective-C types. - -func isHashable(_: T.Type) {} - -isHashable(Int.self) // no-error // Test that `isHashable(_:)` works. - -isHashable(Array.self) // expected-error {{'Array' does not conform to expected type 'Hashable'}} -isHashable(Dictionary.self) // expected-error {{'Dictionary' does not conform to expected type 'Hashable'}} - diff --git a/validation-test/stdlib/Arrays.swift.gyb b/validation-test/stdlib/Arrays.swift.gyb index ea11358d1299f..48ad8bb6d2916 100644 --- a/validation-test/stdlib/Arrays.swift.gyb +++ b/validation-test/stdlib/Arrays.swift.gyb @@ -189,6 +189,25 @@ ArrayTestSuite.test("init(repeating:count:)") { } } +ArrayTestSuite.test("Hashable") { + let a1: Array = [1, 2, 3] + let a2: Array = [1, 3, 2] + let a3: Array = [3, 1, 2] + let a4: Array = [1, 2] + let a5: Array = [1] + let a6: Array = [] + let a7: Array = [1, 1, 1] + checkHashable([a1, a2, a3, a4, a5, a6, a7], equalityOracle: { $0 == $1 }) + + let aa1: Array> = [[], [1], [1, 2], [2, 1]] + let aa2: Array> = [[], [1], [2, 1], [2, 1]] + let aa3: Array> = [[1], [], [2, 1], [2, 1]] + let aa4: Array> = [[1], [], [2, 1], [2]] + let aa5: Array> = [[1], [], [2, 1]] + checkHashable([aa1, aa2, aa3, aa4, aa5], equalityOracle: { $0 == $1 }) +} + + #if _runtime(_ObjC) //===----------------------------------------------------------------------===// // NSArray -> Array bridging tests. diff --git a/validation-test/stdlib/Dictionary4.swift b/validation-test/stdlib/Dictionary4.swift new file mode 100644 index 0000000000000..aba0cc5977cb0 --- /dev/null +++ b/validation-test/stdlib/Dictionary4.swift @@ -0,0 +1,39 @@ +// RUN: rm -rf %t ; mkdir -p %t +// RUN: %target-build-swift %s -o %t/a.out -swift-version 4 && %target-run %t/a.out + +// REQUIRES: executable_test + +import StdlibUnittest +import StdlibCollectionUnittest + +var DictionaryTestSuite = TestSuite("Dictionary4") + +DictionaryTestSuite.test("Hashable") { + let d1: Dictionary = [1: "meow", 2: "meow", 3: "meow"] + let d2: Dictionary = [1: "meow", 2: "meow", 3: "mooo"] + let d3: Dictionary = [1: "meow", 2: "meow", 4: "meow"] + let d4: Dictionary = [1: "meow", 2: "meow", 4: "mooo"] + checkHashable([d1, d2, d3, d4], equalityOracle: { $0 == $1 }) + + let dd1: Dictionary> = [1: [2: "meow"]] + let dd2: Dictionary> = [2: [1: "meow"]] + let dd3: Dictionary> = [2: [2: "meow"]] + let dd4: Dictionary> = [1: [1: "meow"]] + let dd5: Dictionary> = [2: [2: "mooo"]] + let dd6: Dictionary> = [2: [:]] + let dd7: Dictionary> = [:] + checkHashable( + [dd1, dd2, dd3, dd4, dd5, dd6, dd7], + equalityOracle: { $0 == $1 }) + + // Check that hash is equal even though dictionary is traversed differently + var d5: Dictionary = + [1: "meow", 2: "meow", 3: "mooo", 4: "woof", 5: "baah", 6: "mooo"] + let expected = d5.hashValue + for capacity in [4, 8, 16, 32, 64, 128, 256] { + d5.reserveCapacity(capacity) + expectEqual(d5.hashValue, expected) + } +} + +runAllTests() diff --git a/validation-test/stdlib/Lazy.swift.gyb b/validation-test/stdlib/Lazy.swift.gyb index 44b29ac5f7ed4..4ce172f47897e 100644 --- a/validation-test/stdlib/Lazy.swift.gyb +++ b/validation-test/stdlib/Lazy.swift.gyb @@ -204,6 +204,20 @@ LazyTestSuite.test("CollectionOfOne/{CustomDebugStringConvertible,CustomReflecta c) } +LazyTestSuite.test("CollectionOfOne/Equatable") { + let c = CollectionOfOne(42) + let d = CollectionOfOne(43) + let instances = [ c, d ] + checkEquatable(instances, oracle: { $0 == $1 }) +} + +LazyTestSuite.test("CollectionOfOne/Hashable") { + let c = CollectionOfOne(42) + let d = CollectionOfOne(43) + let instances = [ c, d ] + checkHashable(instances, equalityOracle: { $0 == $1 }) +} + //===----------------------------------------------------------------------===// // EmptyCollection //===----------------------------------------------------------------------===// @@ -238,6 +252,11 @@ LazyTestSuite.test("EmptyCollection/Equatable") { checkEquatable(instances, oracle: { $0 == $1 }) } +LazyTestSuite.test("EmptyCollection/Equatable") { + let instances = [ EmptyCollection>() ] + checkHashable(instances, equalityOracle: { $0 == $1 }) +} + LazyTestSuite.test("EmptyCollection/AssociatedTypes") { typealias Subject = EmptyCollection> expectRandomAccessCollectionAssociatedTypes(