Skip to content

Commit b41c939

Browse files
authored
Merge pull request #12777 from natecook1000/nc-index-hashable
2 parents de7a517 + f7c55d2 commit b41c939

12 files changed

+144
-2
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ CHANGELOG
2222
Swift 4.1
2323
---------
2424

25+
* [SE-0188][]
26+
27+
Index types for most standard library collections now conform to `Hashable`.
28+
These indices can now be used in key-path subscripts and hashed collections:
29+
30+
```swift
31+
let s = "Hashable"
32+
let p = \String.[s.startIndex]
33+
s[keyPath: p] // "H"
34+
```
35+
2536
* [SE-0143][] The standard library types `Optional`, `Array`, and
2637
`Dictionary` now conform to the `Equatable` protocol when their element types
2738
conform to `Equatable`. This allows the `==` operator to compose (e.g., one
@@ -6810,3 +6821,4 @@ Swift 1.0
68106821
[SE-0184]: <https://github.com/apple/swift-evolution/blob/master/proposals/0184-unsafe-pointers-add-missing.md>
68116822
[SE-0185]: <https://github.com/apple/swift-evolution/blob/master/proposals/0185-synthesize-equatable-hashable.md>
68126823
[SE-0186]: <https://github.com/apple/swift-evolution/blob/master/proposals/0186-remove-ownership-keyword-support-in-protocols.md>
6824+
[SE-0188]: <https://github.com/apple/swift-evolution/blob/master/proposals/0188-stdlib-index-types-hashable.md>

stdlib/public/core/ClosedRange.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,17 @@ extension ClosedRangeIndex : Comparable {
8585
}
8686
}
8787

88+
extension ClosedRangeIndex : Hashable where Bound : Hashable {
89+
public var hashValue: Int {
90+
switch _value {
91+
case .inRange(let value):
92+
return value.hashValue
93+
case .pastEnd:
94+
return .max
95+
}
96+
}
97+
}
98+
8899
/// A closed range that forms a collection of consecutive values.
89100
///
90101
/// You create a `CountableClosedRange` instance by using the closed range

stdlib/public/core/DropWhile.swift.gyb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ public struct LazyDropWhileIndex<Base : Collection> : Comparable {
140140
}
141141
}
142142

143+
extension LazyDropWhileIndex : Hashable where Base.Index : Hashable {
144+
public var hashValue: Int {
145+
return base.hashValue
146+
}
147+
}
148+
143149
% for Traversal in ['Forward', 'Bidirectional']:
144150
% Collection = collectionForTraversal(Traversal)
145151
% Self = "LazyDropWhile" + Collection

stdlib/public/core/Flatten.swift.gyb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,14 @@ extension ${Index} : Comparable {
220220
}
221221
}
222222

223+
extension ${Index} : Hashable
224+
where BaseElements.Index : Hashable, BaseElements.Element.Index : Hashable
225+
{
226+
public var hashValue: Int {
227+
return _mixInt(_inner?.hashValue ?? 0) ^ _outer.hashValue
228+
}
229+
}
230+
223231
/// A flattened view of a base collection of collections.
224232
///
225233
/// The elements of this view are a concatenation of the elements of

stdlib/public/core/HashedCollections.swift.gyb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5967,7 +5967,7 @@ elif Self == 'Dictionary':
59675967

59685968
${SubscriptingWithIndexDoc}
59695969
@_fixed_layout // FIXME(sil-serialize-all)
5970-
public struct Index : Comparable {
5970+
public struct Index : Comparable, Hashable {
59715971
// Index for native buffer is efficient. Index for bridged NS${Self} is
59725972
// not, because neither NSEnumerator nor fast enumeration support moving
59735973
// backwards. Even if they did, there is another issue: NSEnumerator does
@@ -6089,6 +6089,22 @@ extension ${Self}.Index {
60896089
#endif
60906090
}
60916091
}
6092+
6093+
@_inlineable // FIXME(sil-serialize-all)
6094+
public var hashValue: Int {
6095+
if _fastPath(_guaranteedNative) {
6096+
return _nativeIndex.offset
6097+
}
6098+
6099+
switch _value {
6100+
case ._native(let nativeIndex):
6101+
return nativeIndex.offset
6102+
#if _runtime(_ObjC)
6103+
case ._cocoa(let cocoaIndex):
6104+
return cocoaIndex.currentKeyIndex
6105+
#endif
6106+
}
6107+
}
60926108
}
60936109

60946110
#if _runtime(_ObjC)

stdlib/public/core/PrefixWhile.swift.gyb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,17 @@ public struct LazyPrefixWhileIndex<Base : Collection> : Comparable {
161161
}
162162
}
163163

164+
extension LazyPrefixWhileIndex : Hashable where Base.Index : Hashable {
165+
public var hashValue: Int {
166+
switch _value {
167+
case .index(let value):
168+
return value.hashValue
169+
case .pastEnd:
170+
return .max
171+
}
172+
}
173+
}
174+
164175
% for Traversal in ['Forward', 'Bidirectional']:
165176
% Collection = collectionForTraversal(Traversal)
166177
% Self = "LazyPrefixWhile" + Collection

stdlib/public/core/Reverse.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ public struct ReversedIndex<Base : Collection> : Comparable {
149149
}
150150
}
151151

152+
extension ReversedIndex : Hashable where Base.Index : Hashable {
153+
public var hashValue: Int {
154+
return base.hashValue
155+
}
156+
}
157+
152158
/// A collection that presents the elements of its base collection
153159
/// in reverse order.
154160
///
@@ -349,6 +355,12 @@ public struct ReversedRandomAccessIndex<
349355
}
350356
}
351357

358+
extension ReversedRandomAccessIndex : Hashable where Base.Index : Hashable {
359+
public var hashValue: Int {
360+
return base.hashValue
361+
}
362+
}
363+
352364
/// A collection that presents the elements of its base collection
353365
/// in reverse order.
354366
///

stdlib/public/core/StringIndex.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ extension String.Index : Comparable {
6767
}
6868
}
6969

70+
extension String.Index : Hashable {
71+
@_inlineable // FIXME(sil-serialize-all)
72+
public var hashValue: Int {
73+
return _compoundOffset.hashValue
74+
}
75+
}
76+
7077
extension String.Index {
7178
internal typealias _Self = String.Index
7279

validation-test/stdlib/Dictionary.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ DictionaryTestSuite.test("sizeof") {
7777
#endif
7878
}
7979

80+
DictionaryTestSuite.test("Index.Hashable") {
81+
let d = [1: "meow", 2: "meow", 3: "meow"]
82+
let e = Dictionary(uniqueKeysWithValues: zip(d.indices, d))
83+
expectEqual(d.count, e.count)
84+
expectNotNil(e[d.startIndex])
85+
}
86+
8087
DictionaryTestSuite.test("valueDestruction") {
8188
var d1 = Dictionary<Int, TestValueTy>()
8289
for i in 100...110 {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
4+
import StdlibUnittest
5+
6+
var HashTests = TestSuite("HashableIndices")
7+
8+
HashTests.test("ClosedRangeIndex") {
9+
let a = 1...10
10+
checkHashable(a.indices, equalityOracle: { $0 == $1 })
11+
}
12+
13+
HashTests.test("FlattenIndex") {
14+
let a = [1...10, 11...20, 21...30].joined()
15+
checkHashable(a.indices, equalityOracle: { $0 == $1 })
16+
}
17+
18+
HashTests.test("LazyDropWhileIndex") {
19+
let a = (1...10).lazy.drop(while: { $0 < 5 })
20+
checkHashable(a.indices, equalityOracle: { $0 == $1 })
21+
}
22+
23+
HashTests.test("LazyPrefixWhileIndex") {
24+
let a = (1...10).lazy.prefix(while: { $0 < 5 })
25+
checkHashable(a.indices, equalityOracle: { $0 == $1 })
26+
}
27+
28+
HashTests.test("ReversedIndex") {
29+
let a = (1...10).lazy.filter({ $0 > 3 }).reversed()
30+
checkHashable(a.indices, equalityOracle: { $0 == $1 })
31+
}
32+
33+
HashTests.test("ReversedRandomAccessIndex") {
34+
let a = (1...10).reversed()
35+
checkHashable(a.indices, equalityOracle: { $0 == $1 })
36+
}
37+
38+
runAllTests()

validation-test/stdlib/Set.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,13 @@ SetTestSuite.test("sizeof") {
331331
#endif
332332
}
333333

334+
SetTestSuite.test("Index.Hashable") {
335+
let s: Set = [1, 2, 3, 4, 5]
336+
let t = Set(s.indices)
337+
expectEqual(s.count, t.count)
338+
expectTrue(t.contains(s.startIndex))
339+
}
340+
334341
SetTestSuite.test("COW.Smoke") {
335342
var s1 = Set<TestKeyTy>(minimumCapacity: 10)
336343
for i in [1010, 2020, 3030]{ s1.insert(TestKeyTy(i)) }

validation-test/stdlib/String.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ StringTests.test("unicodeScalars") {
148148
checkUnicodeScalarViewIteration([ 0x10ffff ], "\u{0010ffff}")
149149
}
150150

151-
StringTests.test("indexComparability") {
151+
StringTests.test("Index/Comparable") {
152152
let empty = ""
153153
expectTrue(empty.startIndex == empty.endIndex)
154154
expectFalse(empty.startIndex != empty.endIndex)
@@ -166,6 +166,13 @@ StringTests.test("indexComparability") {
166166
expectTrue(nonEmpty.startIndex < nonEmpty.endIndex)
167167
}
168168

169+
StringTests.test("Index/Hashable") {
170+
let s = "abcdef"
171+
let t = Set(s.indices)
172+
expectEqual(s.count, t.count)
173+
expectTrue(t.contains(s.startIndex))
174+
}
175+
169176
StringTests.test("ForeignIndexes/Valid") {
170177
// It is actually unclear what the correct behavior is. This test is just a
171178
// change detector.

0 commit comments

Comments
 (0)