@@ -106,42 +106,38 @@ extension String.UnicodeScalarView: BidirectionalCollection {
106
106
/// - Precondition: The next location exists.
107
107
@inlinable @inline ( __always)
108
108
public func index( after i: Index ) -> Index {
109
- // TODO(String performance): isASCII fast-path
110
-
111
- // TODO(lorentey): Review index validation
112
- _precondition ( i < endIndex, " String index is out of bounds " )
113
- let i = _guts. scalarAlign ( i)
109
+ let i = _guts. validateScalarIndex ( i)
110
+ return _uncheckedIndex ( after: i)
111
+ }
114
112
113
+ @_alwaysEmitIntoClient
114
+ @inline ( __always)
115
+ internal func _uncheckedIndex( after i: Index ) -> Index {
116
+ // TODO(String performance): isASCII fast-path
115
117
if _fastPath ( _guts. isFastUTF8) {
116
118
let len = _guts. fastUTF8ScalarLength ( startingAt: i. _encodedOffset)
117
119
return i. encoded ( offsetBy: len) . _scalarAligned. _knownUTF8
118
120
}
119
-
120
121
return _foreignIndex ( after: i)
121
122
}
122
123
123
- @_alwaysEmitIntoClient // Swift 5.1 bug fix
124
- public func distance( from start: Index , to end: Index ) -> Int {
125
- // TODO(lorentey): Review index validation
126
- return _distance ( from: _guts. scalarAlign ( start) , to: _guts. scalarAlign ( end) )
127
- }
128
-
129
124
/// Returns the previous consecutive location before `i`.
130
125
///
131
126
/// - Precondition: The previous location exists.
132
127
@inlinable @inline ( __always)
133
128
public func index( before i: Index ) -> Index {
134
- // TODO(lorentey): Review index validation
135
- // TODO(String performance): isASCII fast-path
136
-
137
- // Note: bounds checking in `index(before:)` is tricky as scalar aligning an
138
- // index may need to access storage, but it may also move it closer towards
139
- // the `startIndex`. Therefore, we must check against the `endIndex` before
140
- // aligning, but we need to delay the `i > startIndex` check until after.
141
- _precondition ( i <= endIndex, " String index is out of bounds " )
142
- let i = _guts. scalarAlign ( i)
129
+ let i = _guts. validateInclusiveScalarIndex ( i)
130
+ // Note: Aligning an index may move it closer towards the `startIndex`, so
131
+ // the `i > startIndex` check needs to come after rounding.
143
132
_precondition ( i > startIndex, " String index is out of bounds " )
144
133
134
+ return _uncheckedIndex ( before: i)
135
+ }
136
+
137
+ @_alwaysEmitIntoClient
138
+ @inline ( __always)
139
+ internal func _uncheckedIndex( before i: Index ) -> Index {
140
+ // TODO(String performance): isASCII fast-path
145
141
if _fastPath ( _guts. isFastUTF8) {
146
142
let len = _guts. withFastUTF8 { utf8 in
147
143
_utf8ScalarLength ( utf8, endingAt: i. _encodedOffset)
@@ -171,11 +167,80 @@ extension String.UnicodeScalarView: BidirectionalCollection {
171
167
/// must be less than the view's end index.
172
168
@inlinable @inline ( __always)
173
169
public subscript( position: Index ) -> Unicode . Scalar {
174
- // TODO(lorentey): Review index validation
175
- String ( _guts) . _boundsCheck ( position)
176
- let i = _guts. scalarAlign ( position)
170
+ let i = _guts. validateScalarIndex ( position)
177
171
return _guts. errorCorrectedScalar ( startingAt: i. _encodedOffset) . 0
178
172
}
173
+
174
+ @_alwaysEmitIntoClient // Swift 5.1 bug fix
175
+ public func distance( from start: Index , to end: Index ) -> Int {
176
+ let start = _guts. validateInclusiveScalarIndex ( start)
177
+ let end = _guts. validateInclusiveScalarIndex ( end)
178
+
179
+ var i = start
180
+ var count = 0
181
+ if i < end {
182
+ while i < end {
183
+ count += 1
184
+ i = _uncheckedIndex ( after: i)
185
+ }
186
+ }
187
+ else if i > end {
188
+ while i > end {
189
+ count -= 1
190
+ i = _uncheckedIndex ( before: i)
191
+ }
192
+ }
193
+ return count
194
+ }
195
+
196
+ @_alwaysEmitIntoClient
197
+ public func index( _ i: Index , offsetBy distance: Int ) -> Index {
198
+ var i = _guts. validateInclusiveScalarIndex ( i)
199
+
200
+ if distance >= 0 {
201
+ for _ in stride ( from: 0 , to: distance, by: 1 ) {
202
+ _precondition ( i. _encodedOffset < _guts. count, " String index is out of bounds " )
203
+ i = _uncheckedIndex ( after: i)
204
+ }
205
+ } else {
206
+ for _ in stride ( from: 0 , to: distance, by: - 1 ) {
207
+ _precondition ( i. _encodedOffset > 0 , " String index is out of bounds " )
208
+ i = _uncheckedIndex ( before: i)
209
+ }
210
+ }
211
+ return _guts. markEncoding ( i)
212
+ }
213
+
214
+ @_alwaysEmitIntoClient
215
+ public func index(
216
+ _ i: Index , offsetBy distance: Int , limitedBy limit: Index
217
+ ) -> Index ? {
218
+ // Note: `limit` is intentionally not scalar aligned to ensure our behavior
219
+ // exactly matches the documentation above. We do need to ensure it has a
220
+ // matching encoding, though. The same goes for `start`, which is used to
221
+ // determine whether the limit applies at all.
222
+ let limit = _guts. ensureMatchingEncoding ( limit)
223
+ let start = _guts. ensureMatchingEncoding ( i)
224
+
225
+ var i = _guts. validateInclusiveScalarIndex ( i)
226
+
227
+ if distance >= 0 {
228
+ for _ in stride ( from: 0 , to: distance, by: 1 ) {
229
+ guard limit < start || i < limit else { return nil }
230
+ _precondition ( i. _encodedOffset < _guts. count, " String index is out of bounds " )
231
+ i = _uncheckedIndex ( after: i)
232
+ }
233
+ guard limit < start || i <= limit else { return nil }
234
+ } else {
235
+ for _ in stride ( from: 0 , to: distance, by: - 1 ) {
236
+ guard limit > start || i > limit else { return nil }
237
+ _precondition ( i. _encodedOffset > 0 , " String index is out of bounds " )
238
+ i = _uncheckedIndex ( before: i)
239
+ }
240
+ guard limit > start || i >= limit else { return nil }
241
+ }
242
+ return _guts. markEncoding ( i)
243
+ }
179
244
}
180
245
181
246
extension String . UnicodeScalarView {
@@ -318,9 +383,8 @@ extension String.UnicodeScalarView: RangeReplaceableCollection {
318
383
_ bounds: Range < Index > ,
319
384
with newElements: C
320
385
) where C: Collection , C. Element == Unicode . Scalar {
321
- // TODO(lorentey): Review index validation
322
386
// TODO(String performance): Skip extra String and Array allocation
323
-
387
+ let bounds = _guts . validateScalarRange ( bounds )
324
388
let utf8Replacement = newElements. flatMap { String ( $0) . utf8 }
325
389
let replacement = utf8Replacement. withUnsafeBufferPointer {
326
390
return String . _uncheckedFromUTF8 ( $0)
@@ -423,9 +487,8 @@ extension String.UnicodeScalarView {
423
487
424
488
@available ( swift, introduced: 4 )
425
489
public subscript( r: Range < Index > ) -> String . UnicodeScalarView . SubSequence {
426
- // TODO(lorentey): Review index validation
427
- _failEarlyRangeCheck ( r, bounds: startIndex..< endIndex)
428
- return String . UnicodeScalarView. SubSequence ( self , _bounds: r)
490
+ let r = _guts. validateScalarRange ( r)
491
+ return SubSequence ( _unchecked: self , bounds: r)
429
492
}
430
493
}
431
494
0 commit comments