Skip to content

Commit 1e6b758

Browse files
authored
Merge pull request #38509 from glessard/sr14850
[stdlib] prevent MutableCollections from inappropriately inheriting a Slice<Self> subscript
2 parents 880a143 + 63aef41 commit 1e6b758

File tree

5 files changed

+112
-5
lines changed

5 files changed

+112
-5
lines changed

stdlib/public/core/MutableCollection.swift

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ extension MutableCollection {
238238
/// the range must be valid indices of the collection.
239239
///
240240
/// - Complexity: O(1)
241+
@available(*, unavailable)
241242
@inlinable
242243
public subscript(bounds: Range<Index>) -> Slice<Self> {
243244
get {
@@ -249,6 +250,19 @@ extension MutableCollection {
249250
}
250251
}
251252

253+
// This unavailable default implementation of `subscript(bounds: Range<_>)`
254+
// prevents incomplete MutableCollection implementations from satisfying the
255+
// protocol through the use of the generic convenience implementation
256+
// `subscript<R: RangeExpression>(r: R)`. If that were the case, at
257+
// runtime the generic implementation would call itself
258+
// in an infinite recursion due to the absence of a better option.
259+
@available(*, unavailable)
260+
@_alwaysEmitIntoClient
261+
public subscript(bounds: Range<Index>) -> SubSequence {
262+
get { fatalError() }
263+
set { fatalError() }
264+
}
265+
252266
/// Exchanges the values at the specified indices of the collection.
253267
///
254268
/// Both parameters must be valid indices of the collection that are not
@@ -269,6 +283,45 @@ extension MutableCollection {
269283
}
270284
}
271285

286+
extension MutableCollection where SubSequence == Slice<Self> {
287+
288+
/// Accesses a contiguous subrange of the collection's elements.
289+
///
290+
/// The accessed slice uses the same indices for the same elements as the
291+
/// original collection. Always use the slice's `startIndex` property
292+
/// instead of assuming that its indices start at a particular value.
293+
///
294+
/// This example demonstrates getting a slice of an array of strings, finding
295+
/// the index of one of the strings in the slice, and then using that index
296+
/// in the original array.
297+
///
298+
/// var streets = ["Adams", "Bryant", "Channing", "Douglas", "Evarts"]
299+
/// let streetsSlice = streets[2 ..< streets.endIndex]
300+
/// print(streetsSlice)
301+
/// // Prints "["Channing", "Douglas", "Evarts"]"
302+
///
303+
/// let index = streetsSlice.firstIndex(of: "Evarts") // 4
304+
/// streets[index!] = "Eustace"
305+
/// print(streets[index!])
306+
/// // Prints "Eustace"
307+
///
308+
/// - Parameter bounds: A range of the collection's indices. The bounds of
309+
/// the range must be valid indices of the collection.
310+
///
311+
/// - Complexity: O(1)
312+
@inlinable
313+
@_alwaysEmitIntoClient
314+
public subscript(bounds: Range<Index>) -> Slice<Self> {
315+
get {
316+
_failEarlyRangeCheck(bounds, bounds: startIndex..<endIndex)
317+
return Slice(base: self, bounds: bounds)
318+
}
319+
set {
320+
_writeBackMutableSlice(&self, bounds: bounds, slice: newValue)
321+
}
322+
}
323+
}
324+
272325
//===----------------------------------------------------------------------===//
273326
// _rotate(in:shiftingToStart:)
274327
//===----------------------------------------------------------------------===//

stdlib/public/core/SmallString.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,17 @@ extension _SmallString: RandomAccessCollection, MutableCollection {
191191

192192
@inlinable @inline(__always)
193193
internal subscript(_ bounds: Range<Index>) -> SubSequence {
194-
// TODO(String performance): In-vector-register operation
195-
return self.withUTF8 { utf8 in
196-
let rebased = UnsafeBufferPointer(rebasing: utf8[bounds])
197-
return _SmallString(rebased)._unsafelyUnwrappedUnchecked
194+
get {
195+
// TODO(String performance): In-vector-register operation
196+
return self.withUTF8 { utf8 in
197+
let rebased = UnsafeBufferPointer(rebasing: utf8[bounds])
198+
return _SmallString(rebased)._unsafelyUnwrappedUnchecked
199+
}
198200
}
201+
// This setter is required for _SmallString to be a valid MutableCollection.
202+
// Since _SmallString is internal and this setter unused, we cheat.
203+
@_alwaysEmitIntoClient set { fatalError() }
204+
@_alwaysEmitIntoClient _modify { fatalError() }
199205
}
200206
}
201207

test/Constraints/members.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,6 @@ func rdar_50467583_and_50909555() {
622622
// expected-note@-2 {{found candidate with type '(Int) -> Int'}}
623623
// expected-note@-3 {{found candidate with type '(Range<Int>) -> ArraySlice<Int>'}}
624624
// expected-note@-4 {{found candidate with type '((UnboundedRange_) -> ()) -> ArraySlice<Int>'}}
625-
// expected-note@-5 {{found candidate with type '(Range<Array<Int>.Index>) -> Slice<[Int]>' (aka '(Range<Int>) -> Slice<Array<Int>>')}}
626625
}
627626

628627
// rdar://problem/50909555
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
Accessor MutableCollection.subscript(_:).Get() has generic signature change from <Self where Self : Swift.MutableCollection> to <Self where Self : Swift.MutableCollection, Self.SubSequence == Swift.Slice<Self>>
2+
Accessor MutableCollection.subscript(_:).Set() has generic signature change from <Self where Self : Swift.MutableCollection> to <Self where Self : Swift.MutableCollection, Self.SubSequence == Swift.Slice<Self>>
13
Protocol CodingKey has added inherited protocol Sendable
24
Protocol CodingKey has generic signature change from <Self : Swift.CustomDebugStringConvertible, Self : Swift.CustomStringConvertible> to <Self : Swift.CustomDebugStringConvertible, Self : Swift.CustomStringConvertible, Self : Swift.Sendable>
35
Protocol Error has added inherited protocol Sendable
46
Protocol Error has generic signature change from to <Self : Swift.Sendable>
7+
Subscript MutableCollection.subscript(_:) has generic signature change from <Self where Self : Swift.MutableCollection> to <Self where Self : Swift.MutableCollection, Self.SubSequence == Swift.Slice<Self>>

validation-test/stdlib/CollectionDiagnostics.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,52 @@ struct CollectionWithNonDefaultSubSequence: Collection {
154154
public subscript(position: Int) -> Int { position }
155155
}
156156

157+
// expected-error@+2 {{type 'MutableCollectionWithNonDefaultSubSequence' does not conform to protocol 'MutableCollection'}}
158+
// expected-error@+1 {{unavailable subscript 'subscript(_:)' was used to satisfy a requirement of protocol 'MutableCollection'}}
159+
struct MutableCollectionWithNonDefaultSubSequence: MutableCollection {
160+
public var startIndex: Int
161+
public var endIndex: Int
162+
163+
public typealias SubSequence = Self
164+
165+
public func index(after i: Int) -> Int { i+1 }
166+
public subscript(position: Int) -> Int {
167+
get { position }
168+
set { _ = newValue }
169+
}
170+
171+
public subscript(bounds: Range<Index>) -> Self {
172+
Self(startIndex: bounds.startIndex, endIndex: bounds.endIndex)
173+
}
174+
}
175+
176+
struct MutableCollectionWithDefaultSubSequence: MutableCollection {
177+
public var startIndex: Int
178+
public var endIndex: Int
179+
180+
public func index(after i: Int) -> Int { i+1 }
181+
public subscript(position: Int) -> Int {
182+
get { position }
183+
set { _ = newValue }
184+
}
185+
}
186+
187+
func subscriptMutableCollectionIgnored() {
188+
let cs: MutableCollectionWithNonDefaultSubSequence
189+
cs = .init(startIndex: 0, endIndex: 10)
190+
191+
let badSlice: Slice<MutableCollectionWithNonDefaultSubSequence>
192+
badSlice = cs[0..<2] // expected-error {{'subscript(_:)' is unavailable}}
193+
cs[3..<5] = badSlice // expected-error {{'subscript(_:)' is unavailable}}
194+
195+
let ds: MutableCollectionWithDefaultSubSequence
196+
ds = .init(startIndex: 0, endIndex: 10)
197+
198+
let goodSlice: Slice<MutableCollectionWithDefaultSubSequence>
199+
goodSlice = ds[0..<2]
200+
ds[3..<5] = goodSlice
201+
}
202+
157203
// FIXME: Remove -verify-ignore-unknown.
158204
// <unknown>:0: error: unexpected note produced: possibly intended match
159205
// <unknown>:0: error: unexpected note produced: possibly intended match

0 commit comments

Comments
 (0)