Skip to content

[stdlib] Make standard library index types Hashable #12777

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 4 commits into from
Nov 28, 2017
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ CHANGELOG
Swift 4.1
---------

* [SE-0188][]

Index types for most standard library collections now conform to `Hashable`.
These indices can now be used in key-path subscripts and hashed collections:

```swift
let s = "Hashable"
let p = \String.[s.startIndex]
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
Expand Down Expand Up @@ -6810,3 +6821,4 @@ Swift 1.0
[SE-0184]: <https://github.com/apple/swift-evolution/blob/master/proposals/0184-unsafe-pointers-add-missing.md>
[SE-0185]: <https://github.com/apple/swift-evolution/blob/master/proposals/0185-synthesize-equatable-hashable.md>
[SE-0186]: <https://github.com/apple/swift-evolution/blob/master/proposals/0186-remove-ownership-keyword-support-in-protocols.md>
[SE-0188]: <https://github.com/apple/swift-evolution/blob/master/proposals/0188-stdlib-index-types-hashable.md>
11 changes: 11 additions & 0 deletions stdlib/public/core/ClosedRange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ extension ClosedRangeIndex : Comparable {
}
}

extension ClosedRangeIndex : Hashable where Bound : Hashable {
public var hashValue: Int {
switch _value {
case .inRange(let value):
return value.hashValue
case .pastEnd:
return .max
}
}
}

/// A closed range that forms a collection of consecutive values.
///
/// You create a `CountableClosedRange` instance by using the closed range
Expand Down
6 changes: 6 additions & 0 deletions stdlib/public/core/DropWhile.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ public struct LazyDropWhileIndex<Base : Collection> : Comparable {
}
}

extension LazyDropWhileIndex : Hashable where Base.Index : Hashable {
public var hashValue: Int {
return base.hashValue
}
}

% for Traversal in ['Forward', 'Bidirectional']:
% Collection = collectionForTraversal(Traversal)
% Self = "LazyDropWhile" + Collection
Expand Down
8 changes: 8 additions & 0 deletions stdlib/public/core/Flatten.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,14 @@ extension ${Index} : Comparable {
}
}

extension ${Index} : Hashable
where BaseElements.Index : Hashable, BaseElements.Element.Index : Hashable
{
public var hashValue: Int {
return _mixInt(_inner?.hashValue ?? 0) ^ _outer.hashValue
}
}

/// A flattened view of a base collection of collections.
///
/// The elements of this view are a concatenation of the elements of
Expand Down
18 changes: 17 additions & 1 deletion stdlib/public/core/HashedCollections.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -5967,7 +5967,7 @@ elif Self == 'Dictionary':

${SubscriptingWithIndexDoc}
@_fixed_layout // FIXME(sil-serialize-all)
public struct Index : Comparable {
public struct Index : Comparable, Hashable {
// Index for native buffer is efficient. Index for bridged NS${Self} is
// not, because neither NSEnumerator nor fast enumeration support moving
// backwards. Even if they did, there is another issue: NSEnumerator does
Expand Down Expand Up @@ -6089,6 +6089,22 @@ extension ${Self}.Index {
#endif
}
}

@_inlineable // FIXME(sil-serialize-all)
public var hashValue: Int {
if _fastPath(_guaranteedNative) {
return _nativeIndex.offset
}

switch _value {
case ._native(let nativeIndex):
return nativeIndex.offset
#if _runtime(_ObjC)
case ._cocoa(let cocoaIndex):
return cocoaIndex.currentKeyIndex
#endif
}
}
}

#if _runtime(_ObjC)
Expand Down
11 changes: 11 additions & 0 deletions stdlib/public/core/PrefixWhile.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,17 @@ public struct LazyPrefixWhileIndex<Base : Collection> : Comparable {
}
}

extension LazyPrefixWhileIndex : Hashable where Base.Index : Hashable {
public var hashValue: Int {
switch _value {
case .index(let value):
return value.hashValue
case .pastEnd:
return .max
}
}
}

% for Traversal in ['Forward', 'Bidirectional']:
% Collection = collectionForTraversal(Traversal)
% Self = "LazyPrefixWhile" + Collection
Expand Down
12 changes: 12 additions & 0 deletions stdlib/public/core/Reverse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ public struct ReversedIndex<Base : Collection> : Comparable {
}
}

extension ReversedIndex : Hashable where Base.Index : Hashable {
public var hashValue: Int {
return base.hashValue
}
}

/// A collection that presents the elements of its base collection
/// in reverse order.
///
Expand Down Expand Up @@ -349,6 +355,12 @@ public struct ReversedRandomAccessIndex<
}
}

extension ReversedRandomAccessIndex : Hashable where Base.Index : Hashable {
public var hashValue: Int {
return base.hashValue
}
}

/// A collection that presents the elements of its base collection
/// in reverse order.
///
Expand Down
7 changes: 7 additions & 0 deletions stdlib/public/core/StringIndex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ extension String.Index : Comparable {
}
}

extension String.Index : Hashable {
@_inlineable // FIXME(sil-serialize-all)
public var hashValue: Int {
return _compoundOffset.hashValue
}
}

extension String.Index {
internal typealias _Self = String.Index

Expand Down
7 changes: 7 additions & 0 deletions validation-test/stdlib/Dictionary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ DictionaryTestSuite.test("sizeof") {
#endif
}

DictionaryTestSuite.test("Index.Hashable") {
let d = [1: "meow", 2: "meow", 3: "meow"]
let e = Dictionary(uniqueKeysWithValues: zip(d.indices, d))
expectEqual(d.count, e.count)
expectNotNil(e[d.startIndex])
}

DictionaryTestSuite.test("valueDestruction") {
var d1 = Dictionary<Int, TestValueTy>()
for i in 100...110 {
Expand Down
38 changes: 38 additions & 0 deletions validation-test/stdlib/HashableIndices.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// RUN: %target-run-simple-swift
// REQUIRES: executable_test

import StdlibUnittest

var HashTests = TestSuite("HashableIndices")

HashTests.test("ClosedRangeIndex") {
let a = 1...10
checkHashable(a.indices, equalityOracle: { $0 == $1 })
}

HashTests.test("FlattenIndex") {
let a = [1...10, 11...20, 21...30].joined()
checkHashable(a.indices, equalityOracle: { $0 == $1 })
}

HashTests.test("LazyDropWhileIndex") {
let a = (1...10).lazy.drop(while: { $0 < 5 })
checkHashable(a.indices, equalityOracle: { $0 == $1 })
}

HashTests.test("LazyPrefixWhileIndex") {
let a = (1...10).lazy.prefix(while: { $0 < 5 })
checkHashable(a.indices, equalityOracle: { $0 == $1 })
}

HashTests.test("ReversedIndex") {
let a = (1...10).lazy.filter({ $0 > 3 }).reversed()
checkHashable(a.indices, equalityOracle: { $0 == $1 })
}

HashTests.test("ReversedRandomAccessIndex") {
let a = (1...10).reversed()
checkHashable(a.indices, equalityOracle: { $0 == $1 })
}

runAllTests()
7 changes: 7 additions & 0 deletions validation-test/stdlib/Set.swift
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,13 @@ SetTestSuite.test("sizeof") {
#endif
}

SetTestSuite.test("Index.Hashable") {
let s: Set = [1, 2, 3, 4, 5]
let t = Set(s.indices)
expectEqual(s.count, t.count)
expectTrue(t.contains(s.startIndex))
}

SetTestSuite.test("COW.Smoke") {
var s1 = Set<TestKeyTy>(minimumCapacity: 10)
for i in [1010, 2020, 3030]{ s1.insert(TestKeyTy(i)) }
Expand Down
9 changes: 8 additions & 1 deletion validation-test/stdlib/String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ StringTests.test("unicodeScalars") {
checkUnicodeScalarViewIteration([ 0x10ffff ], "\u{0010ffff}")
}

StringTests.test("indexComparability") {
StringTests.test("Index/Comparable") {
let empty = ""
expectTrue(empty.startIndex == empty.endIndex)
expectFalse(empty.startIndex != empty.endIndex)
Expand All @@ -166,6 +166,13 @@ StringTests.test("indexComparability") {
expectTrue(nonEmpty.startIndex < nonEmpty.endIndex)
}

StringTests.test("Index/Hashable") {
let s = "abcdef"
let t = Set(s.indices)
expectEqual(s.count, t.count)
expectTrue(t.contains(s.startIndex))
}

StringTests.test("ForeignIndexes/Valid") {
// It is actually unclear what the correct behavior is. This test is just a
// change detector.
Expand Down