Files
swift-mirror/stdlib/public/core/Slice.swift
Doug Gregor 1a1f79c0de Introduce safety checkin for ConcurrentValue conformance.
Introduce checking of ConcurrentValue conformances:
- For structs, check that each stored property conforms to ConcurrentValue
- For enums, check that each associated value conforms to ConcurrentValue
- For classes, check that each stored property is immutable and conforms
  to ConcurrentValue

Because all of the stored properties / associated values need to be
visible for this check to work, limit ConcurrentValue conformances to
be in the same source file as the type definition.

This checking can be disabled by conforming to a new marker protocol,
UnsafeConcurrentValue, that refines ConcurrentValue.
UnsafeConcurrentValue otherwise his no specific meaning. This allows
both "I know what I'm doing" for types that manage concurrent access
themselves as well as enabling retroactive conformance, both of which
are fundamentally unsafe but also quite necessary.

The bulk of this change ended up being to the standard library, because
all conformances of standard library types to the ConcurrentValue
protocol needed to be sunk down into the standard library so they
would benefit from the checking above. There were numerous little
mistakes in the initial pass through the stsandard library types that
have now been corrected.
2021-02-04 03:45:09 -08:00

513 lines
19 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
/// A view into a subsequence of elements of another collection.
///
/// A slice stores a base collection and the start and end indices of the view.
/// It does not copy the elements from the collection into separate storage.
/// Thus, creating a slice has O(1) complexity.
///
/// Slices Share Indices
/// --------------------
///
/// Indices of a slice can be used interchangeably with indices of the base
/// collection. An element of a slice is located under the same index in the
/// slice and in the base collection, as long as neither the collection nor
/// the slice has been mutated since the slice was created.
///
/// For example, suppose you have an array holding the number of absences from
/// each class during a session.
///
/// var absences = [0, 2, 0, 4, 0, 3, 1, 0]
///
/// You're tasked with finding the day with the most absences in the second
/// half of the session. To find the index of the day in question, follow
/// these steps:
///
/// 1) Create a slice of the `absences` array that holds the second half of the
/// days.
/// 2) Use the `max(by:)` method to determine the index of the day with the
/// most absences.
/// 3) Print the result using the index found in step 2 on the original
/// `absences` array.
///
/// Here's an implementation of those steps:
///
/// let secondHalf = absences.suffix(absences.count / 2)
/// if let i = secondHalf.indices.max(by: { secondHalf[$0] < secondHalf[$1] }) {
/// print("Highest second-half absences: \(absences[i])")
/// }
/// // Prints "Highest second-half absences: 3"
///
/// Slices Inherit Semantics
/// ------------------------
///
/// A slice inherits the value or reference semantics of its base collection.
/// That is, if a `Slice` instance is wrapped around a mutable collection that
/// has value semantics, such as an array, mutating the original collection
/// would trigger a copy of that collection, and not affect the base
/// collection stored inside of the slice.
///
/// For example, if you update the last element of the `absences` array from
/// `0` to `2`, the `secondHalf` slice is unchanged.
///
/// absences[7] = 2
/// print(absences)
/// // Prints "[0, 2, 0, 4, 0, 3, 1, 2]"
/// print(secondHalf)
/// // Prints "[0, 3, 1, 0]"
///
/// Use slices only for transient computation. A slice may hold a reference to
/// the entire storage of a larger collection, not just to the portion it
/// presents, even after the base collection's lifetime ends. Long-term
/// storage of a slice may therefore prolong the lifetime of elements that are
/// no longer otherwise accessible, which can erroneously appear to be memory
/// leakage.
///
/// - Note: Using a `Slice` instance with a mutable collection requires that
/// the base collection's `subscript(_: Index)` setter does not invalidate
/// indices. If mutations need to invalidate indices in your custom
/// collection type, don't use `Slice` as its subsequence type. Instead,
/// define your own subsequence type that takes your index invalidation
/// requirements into account.
@frozen // generic-performance
public struct Slice<Base: Collection> {
public var _startIndex: Base.Index
public var _endIndex: Base.Index
@usableFromInline // generic-performance
internal var _base: Base
/// Creates a view into the given collection that allows access to elements
/// within the specified range.
///
/// It is unusual to need to call this method directly. Instead, create a
/// slice of a collection by using the collection's range-based subscript or
/// by using methods that return a subsequence.
///
/// let singleDigits = 0...9
/// let subSequence = singleDigits.dropFirst(5)
/// print(Array(subSequence))
/// // Prints "[5, 6, 7, 8, 9]"
///
/// In this example, the expression `singleDigits.dropFirst(5))` is
/// equivalent to calling this initializer with `singleDigits` and a
/// range covering the last five items of `singleDigits.indices`.
///
/// - Parameters:
/// - base: The collection to create a view into.
/// - bounds: The range of indices to allow access to in the new slice.
@inlinable // generic-performance
public init(base: Base, bounds: Range<Base.Index>) {
self._base = base
self._startIndex = bounds.lowerBound
self._endIndex = bounds.upperBound
}
/// The underlying collection of the slice.
///
/// You can use a slice's `base` property to access its base collection. The
/// following example declares `singleDigits`, a range of single digit
/// integers, and then drops the first element to create a slice of that
/// range, `singleNonZeroDigits`. The `base` property of the slice is equal
/// to `singleDigits`.
///
/// let singleDigits = 0..<10
/// let singleNonZeroDigits = singleDigits.dropFirst()
/// // singleNonZeroDigits is a Slice<Range<Int>>
///
/// print(singleNonZeroDigits.count)
/// // Prints "9"
/// print(singleNonZeroDigits.base.count)
/// // Prints "10"
/// print(singleDigits == singleNonZeroDigits.base)
/// // Prints "true"
@inlinable // generic-performance
public var base: Base {
return _base
}
}
extension Slice: Collection {
public typealias Index = Base.Index
public typealias Indices = Base.Indices
public typealias Element = Base.Element
public typealias SubSequence = Slice<Base>
public typealias Iterator = IndexingIterator<Slice<Base>>
@inlinable // generic-performance
public var startIndex: Index {
return _startIndex
}
@inlinable // generic-performance
public var endIndex: Index {
return _endIndex
}
@inlinable // generic-performance
public subscript(index: Index) -> Base.Element {
get {
_failEarlyRangeCheck(index, bounds: startIndex..<endIndex)
return _base[index]
}
}
@inlinable // generic-performance
public subscript(bounds: Range<Index>) -> Slice<Base> {
get {
_failEarlyRangeCheck(bounds, bounds: startIndex..<endIndex)
return Slice(base: _base, bounds: bounds)
}
}
public var indices: Indices {
return _base.indices[_startIndex..<_endIndex]
}
@inlinable // generic-performance
public func index(after i: Index) -> Index {
// FIXME: swift-3-indexing-model: range check.
return _base.index(after: i)
}
@inlinable // generic-performance
public func formIndex(after i: inout Index) {
// FIXME: swift-3-indexing-model: range check.
_base.formIndex(after: &i)
}
@inlinable // generic-performance
public func index(_ i: Index, offsetBy n: Int) -> Index {
// FIXME: swift-3-indexing-model: range check.
return _base.index(i, offsetBy: n)
}
@inlinable // generic-performance
public func index(
_ i: Index, offsetBy n: Int, limitedBy limit: Index
) -> Index? {
// FIXME: swift-3-indexing-model: range check.
return _base.index(i, offsetBy: n, limitedBy: limit)
}
@inlinable // generic-performance
public func distance(from start: Index, to end: Index) -> Int {
// FIXME: swift-3-indexing-model: range check.
return _base.distance(from: start, to: end)
}
@inlinable // generic-performance
public func _failEarlyRangeCheck(_ index: Index, bounds: Range<Index>) {
_base._failEarlyRangeCheck(index, bounds: bounds)
}
@inlinable // generic-performance
public func _failEarlyRangeCheck(_ range: Range<Index>, bounds: Range<Index>) {
_base._failEarlyRangeCheck(range, bounds: bounds)
}
@_alwaysEmitIntoClient @inlinable
public func withContiguousStorageIfAvailable<R>(
_ body: (UnsafeBufferPointer<Element>) throws -> R
) rethrows -> R? {
try _base.withContiguousStorageIfAvailable { buffer in
let start = _base.distance(from: _base.startIndex, to: _startIndex)
let count = _base.distance(from: _startIndex, to: _endIndex)
let slice = UnsafeBufferPointer(rebasing: buffer[start ..< start + count])
return try body(slice)
}
}
}
extension Slice: BidirectionalCollection where Base: BidirectionalCollection {
@inlinable // generic-performance
public func index(before i: Index) -> Index {
// FIXME: swift-3-indexing-model: range check.
return _base.index(before: i)
}
@inlinable // generic-performance
public func formIndex(before i: inout Index) {
// FIXME: swift-3-indexing-model: range check.
_base.formIndex(before: &i)
}
}
extension Slice: MutableCollection where Base: MutableCollection {
@inlinable // generic-performance
public subscript(index: Index) -> Base.Element {
get {
_failEarlyRangeCheck(index, bounds: startIndex..<endIndex)
return _base[index]
}
set {
_failEarlyRangeCheck(index, bounds: startIndex..<endIndex)
_base[index] = newValue
// MutableSlice requires that the underlying collection's subscript
// setter does not invalidate indices, so our `startIndex` and `endIndex`
// continue to be valid.
}
}
@inlinable // generic-performance
public subscript(bounds: Range<Index>) -> Slice<Base> {
get {
_failEarlyRangeCheck(bounds, bounds: startIndex..<endIndex)
return Slice(base: _base, bounds: bounds)
}
set {
_writeBackMutableSlice(&self, bounds: bounds, slice: newValue)
}
}
@_alwaysEmitIntoClient @inlinable
public mutating func withContiguousMutableStorageIfAvailable<R>(
_ body: (inout UnsafeMutableBufferPointer<Element>) throws -> R
) rethrows -> R? {
// We're calling `withContiguousMutableStorageIfAvailable` twice here so
// that we don't calculate index distances unless we know we'll use them.
// The expectation here is that the base collection will make itself
// contiguous on the first try and the second call will be relatively cheap.
guard _base.withContiguousMutableStorageIfAvailable({ _ in }) != nil
else {
return nil
}
let start = _base.distance(from: _base.startIndex, to: _startIndex)
let count = _base.distance(from: _startIndex, to: _endIndex)
return try _base.withContiguousMutableStorageIfAvailable { buffer in
var slice = UnsafeMutableBufferPointer(
rebasing: buffer[start ..< start + count])
let copy = slice
defer {
_precondition(
slice.baseAddress == copy.baseAddress &&
slice.count == copy.count,
"Slice.withUnsafeMutableBufferPointer: replacing the buffer is not allowed")
}
return try body(&slice)
}
}
}
extension Slice: RandomAccessCollection where Base: RandomAccessCollection { }
extension Slice: RangeReplaceableCollection
where Base: RangeReplaceableCollection {
@inlinable // generic-performance
public init() {
self._base = Base()
self._startIndex = _base.startIndex
self._endIndex = _base.endIndex
}
@inlinable // generic-performance
public init(repeating repeatedValue: Base.Element, count: Int) {
self._base = Base(repeating: repeatedValue, count: count)
self._startIndex = _base.startIndex
self._endIndex = _base.endIndex
}
@inlinable // generic-performance
public init<S>(_ elements: S) where S: Sequence, S.Element == Base.Element {
self._base = Base(elements)
self._startIndex = _base.startIndex
self._endIndex = _base.endIndex
}
@inlinable // generic-performance
public mutating func replaceSubrange<C>(
_ subRange: Range<Index>, with newElements: C
) where C: Collection, C.Element == Base.Element {
// FIXME: swift-3-indexing-model: range check.
let sliceOffset =
_base.distance(from: _base.startIndex, to: _startIndex)
let newSliceCount =
_base.distance(from: _startIndex, to: subRange.lowerBound)
+ _base.distance(from: subRange.upperBound, to: _endIndex)
+ newElements.count
_base.replaceSubrange(subRange, with: newElements)
_startIndex = _base.index(_base.startIndex, offsetBy: sliceOffset)
_endIndex = _base.index(_startIndex, offsetBy: newSliceCount)
}
@inlinable // generic-performance
public mutating func insert(_ newElement: Base.Element, at i: Index) {
// FIXME: swift-3-indexing-model: range check.
let sliceOffset = _base.distance(from: _base.startIndex, to: _startIndex)
let newSliceCount = count + 1
_base.insert(newElement, at: i)
_startIndex = _base.index(_base.startIndex, offsetBy: sliceOffset)
_endIndex = _base.index(_startIndex, offsetBy: newSliceCount)
}
@inlinable // generic-performance
public mutating func insert<S>(contentsOf newElements: S, at i: Index)
where S: Collection, S.Element == Base.Element {
// FIXME: swift-3-indexing-model: range check.
let sliceOffset = _base.distance(from: _base.startIndex, to: _startIndex)
let newSliceCount = count + newElements.count
_base.insert(contentsOf: newElements, at: i)
_startIndex = _base.index(_base.startIndex, offsetBy: sliceOffset)
_endIndex = _base.index(_startIndex, offsetBy: newSliceCount)
}
@inlinable // generic-performance
public mutating func remove(at i: Index) -> Base.Element {
// FIXME: swift-3-indexing-model: range check.
let sliceOffset = _base.distance(from: _base.startIndex, to: _startIndex)
let newSliceCount = count - 1
let result = _base.remove(at: i)
_startIndex = _base.index(_base.startIndex, offsetBy: sliceOffset)
_endIndex = _base.index(_startIndex, offsetBy: newSliceCount)
return result
}
@inlinable // generic-performance
public mutating func removeSubrange(_ bounds: Range<Index>) {
// FIXME: swift-3-indexing-model: range check.
let sliceOffset = _base.distance(from: _base.startIndex, to: _startIndex)
let newSliceCount =
count - distance(from: bounds.lowerBound, to: bounds.upperBound)
_base.removeSubrange(bounds)
_startIndex = _base.index(_base.startIndex, offsetBy: sliceOffset)
_endIndex = _base.index(_startIndex, offsetBy: newSliceCount)
}
}
extension Slice
where Base: RangeReplaceableCollection, Base: BidirectionalCollection {
@inlinable // generic-performance
public mutating func replaceSubrange<C>(
_ subRange: Range<Index>, with newElements: C
) where C: Collection, C.Element == Base.Element {
// FIXME: swift-3-indexing-model: range check.
if subRange.lowerBound == _base.startIndex {
let newSliceCount =
_base.distance(from: _startIndex, to: subRange.lowerBound)
+ _base.distance(from: subRange.upperBound, to: _endIndex)
+ newElements.count
_base.replaceSubrange(subRange, with: newElements)
_startIndex = _base.startIndex
_endIndex = _base.index(_startIndex, offsetBy: newSliceCount)
} else {
let shouldUpdateStartIndex = subRange.lowerBound == _startIndex
let lastValidIndex = _base.index(before: subRange.lowerBound)
let newEndIndexOffset =
_base.distance(from: subRange.upperBound, to: _endIndex)
+ newElements.count + 1
_base.replaceSubrange(subRange, with: newElements)
if shouldUpdateStartIndex {
_startIndex = _base.index(after: lastValidIndex)
}
_endIndex = _base.index(lastValidIndex, offsetBy: newEndIndexOffset)
}
}
@inlinable // generic-performance
public mutating func insert(_ newElement: Base.Element, at i: Index) {
// FIXME: swift-3-indexing-model: range check.
if i == _base.startIndex {
let newSliceCount = count + 1
_base.insert(newElement, at: i)
_startIndex = _base.startIndex
_endIndex = _base.index(_startIndex, offsetBy: newSliceCount)
} else {
let shouldUpdateStartIndex = i == _startIndex
let lastValidIndex = _base.index(before: i)
let newEndIndexOffset = _base.distance(from: i, to: _endIndex) + 2
_base.insert(newElement, at: i)
if shouldUpdateStartIndex {
_startIndex = _base.index(after: lastValidIndex)
}
_endIndex = _base.index(lastValidIndex, offsetBy: newEndIndexOffset)
}
}
@inlinable // generic-performance
public mutating func insert<S>(contentsOf newElements: S, at i: Index)
where S: Collection, S.Element == Base.Element {
// FIXME: swift-3-indexing-model: range check.
if i == _base.startIndex {
let newSliceCount = count + newElements.count
_base.insert(contentsOf: newElements, at: i)
_startIndex = _base.startIndex
_endIndex = _base.index(_startIndex, offsetBy: newSliceCount)
} else {
let shouldUpdateStartIndex = i == _startIndex
let lastValidIndex = _base.index(before: i)
let newEndIndexOffset =
_base.distance(from: i, to: _endIndex)
+ newElements.count + 1
_base.insert(contentsOf: newElements, at: i)
if shouldUpdateStartIndex {
_startIndex = _base.index(after: lastValidIndex)
}
_endIndex = _base.index(lastValidIndex, offsetBy: newEndIndexOffset)
}
}
@inlinable // generic-performance
public mutating func remove(at i: Index) -> Base.Element {
// FIXME: swift-3-indexing-model: range check.
if i == _base.startIndex {
let newSliceCount = count - 1
let result = _base.remove(at: i)
_startIndex = _base.startIndex
_endIndex = _base.index(_startIndex, offsetBy: newSliceCount)
return result
} else {
let shouldUpdateStartIndex = i == _startIndex
let lastValidIndex = _base.index(before: i)
let newEndIndexOffset = _base.distance(from: i, to: _endIndex)
let result = _base.remove(at: i)
if shouldUpdateStartIndex {
_startIndex = _base.index(after: lastValidIndex)
}
_endIndex = _base.index(lastValidIndex, offsetBy: newEndIndexOffset)
return result
}
}
@inlinable // generic-performance
public mutating func removeSubrange(_ bounds: Range<Index>) {
// FIXME: swift-3-indexing-model: range check.
if bounds.lowerBound == _base.startIndex {
let newSliceCount =
count - _base.distance(from: bounds.lowerBound, to: bounds.upperBound)
_base.removeSubrange(bounds)
_startIndex = _base.startIndex
_endIndex = _base.index(_startIndex, offsetBy: newSliceCount)
} else {
let shouldUpdateStartIndex = bounds.lowerBound == _startIndex
let lastValidIndex = _base.index(before: bounds.lowerBound)
let newEndIndexOffset =
_base.distance(from: bounds.lowerBound, to: _endIndex)
- _base.distance(from: bounds.lowerBound, to: bounds.upperBound)
+ 1
_base.removeSubrange(bounds)
if shouldUpdateStartIndex {
_startIndex = _base.index(after: lastValidIndex)
}
_endIndex = _base.index(lastValidIndex, offsetBy: newEndIndexOffset)
}
}
}
extension Slice: ConcurrentValue
where Base: ConcurrentValue, Base.Index: ConcurrentValue { }