diff --git a/Package.swift b/Package.swift index 613dd7478..ece0e0f30 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.7 +// swift-tools-version:6.2 //===----------------------------------------------------------------------===// // // This source file is part of the Swift Collections open source project @@ -52,7 +52,37 @@ var defines: [String] = [ // "COLLECTIONS_SINGLE_MODULE", ] -var _settings: [SwiftSetting] = defines.map { .define($0) } +let availabilityMacros: KeyValuePairs = [ + "SwiftStdlib 6.0": "macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0", + "SwiftStdlib 6.1": "macOS 15.4, iOS 18.4, watchOS 11.4, tvOS 18.4, visionOS 2.4", + "SwiftStdlib 6.2": "macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999", +] + +let extraSettings: [SwiftSetting] = [ + .enableExperimentalFeature("AllowUnsafeAttribute"), + .enableExperimentalFeature("BuiltinModule"), + .enableExperimentalFeature("LifetimeDependence"), + .enableExperimentalFeature("SuppressedAssociatedTypes"), + .enableUpcomingFeature("StrictMemorySafety"), + .enableExperimentalFeature("InoutLifetimeDependence"), + .enableExperimentalFeature("AddressableParameters"), + .enableExperimentalFeature("AddressableTypes"), + .enableUpcomingFeature("MemberImportVisibility"), + //.unsafeFlags(["-Xfrontend", "-strict-memory-safety"]), +] + +let _sharedSettings: [SwiftSetting] = ( + defines.map { .define($0) } + + availabilityMacros.map { name, value in + .enableExperimentalFeature("AvailabilityMacro=\(name): \(value)") + } + + extraSettings +) + + +let _settings: [SwiftSetting] = _sharedSettings + [] + +let _testSettings: [SwiftSetting] = _sharedSettings + [] struct CustomTarget { enum Kind { @@ -67,6 +97,8 @@ struct CustomTarget { var dependencies: [Target.Dependency] var directory: String var exclude: [String] + var sources: [String]? + var settings: [SwiftSetting] } extension CustomTarget.Kind { @@ -91,14 +123,18 @@ extension CustomTarget { name: String, dependencies: [Target.Dependency] = [], directory: String? = nil, - exclude: [String] = [] + exclude: [String] = [], + sources: [String]? = nil, + settings: [SwiftSetting]? = nil ) -> CustomTarget { CustomTarget( kind: kind, name: name, dependencies: dependencies, directory: directory ?? name, - exclude: exclude) + exclude: exclude, + sources: sources, + settings: settings ?? (kind.isTest ? _testSettings : _settings)) } func toTarget() -> Target { @@ -114,7 +150,8 @@ extension CustomTarget { dependencies: dependencies, path: kind.path(for: directory), exclude: exclude, - swiftSettings: _settings, + sources: sources, + swiftSettings: settings, linkerSettings: linkerSettings) case .test: return Target.testTarget( @@ -122,7 +159,7 @@ extension CustomTarget { dependencies: dependencies, path: kind.path(for: directory), exclude: exclude, - swiftSettings: _settings, + swiftSettings: settings, linkerSettings: linkerSettings) } } @@ -169,7 +206,7 @@ extension Array where Element == CustomTarget { t.exclude.map { "\(t.name)/\($0)" } }, sources: targets.map { "\($0.name)" }, - swiftSettings: _settings, + swiftSettings: _testSettings, linkerSettings: linkerSettings) } } @@ -178,7 +215,7 @@ let targets: [CustomTarget] = [ .target( kind: .testSupport, name: "_CollectionsTestSupport", - dependencies: ["InternalCollectionsUtilities"]), + dependencies: ["InternalCollectionsUtilities", "Future"]), .target( kind: .test, name: "CollectionsTestSupportTests", @@ -188,9 +225,6 @@ let targets: [CustomTarget] = [ name: "InternalCollectionsUtilities", exclude: [ "CMakeLists.txt", - "Compatibility/UnsafeMutableBufferPointer+SE-0370.swift.gyb", - "Compatibility/UnsafeMutablePointer+SE-0370.swift.gyb", - "Compatibility/UnsafeRawPointer extensions.swift.gyb", "Debugging.swift.gyb", "Descriptions.swift.gyb", "IntegerTricks/FixedWidthInteger+roundUpToPowerOfTwo.swift.gyb", @@ -198,7 +232,6 @@ let targets: [CustomTarget] = [ "IntegerTricks/UInt+first and last set bit.swift.gyb", "IntegerTricks/UInt+reversed.swift.gyb", "RandomAccessCollection+Offsets.swift.gyb", - "Specialize.swift.gyb", "UnsafeBitSet/_UnsafeBitSet+Index.swift.gyb", "UnsafeBitSet/_UnsafeBitSet+_Word.swift.gyb", "UnsafeBitSet/_UnsafeBitSet.swift.gyb", @@ -206,6 +239,19 @@ let targets: [CustomTarget] = [ "UnsafeMutableBufferPointer+Extras.swift.gyb", ]), + .target( + kind: .exported, + name: "Future", + dependencies: ["InternalCollectionsUtilities"], + exclude: ["CMakeLists.txt"], + settings: _sharedSettings, + ), + .target( + kind: .test, + name: "FutureTests", + dependencies: ["Future", "_CollectionsTestSupport"] + ), + .target( kind: .exported, name: "BitCollections", @@ -221,7 +267,7 @@ let targets: [CustomTarget] = [ .target( kind: .exported, name: "DequeModule", - dependencies: ["InternalCollectionsUtilities"], + dependencies: ["InternalCollectionsUtilities", "Future"], exclude: ["CMakeLists.txt"]), .target( kind: .test, @@ -263,7 +309,9 @@ let targets: [CustomTarget] = [ name: "_RopeModule", dependencies: ["InternalCollectionsUtilities"], directory: "RopeModule", - exclude: ["CMakeLists.txt"]), + exclude: ["CMakeLists.txt"], + // FIXME: _modify accessors in RopeModule seem to be broken in Swift 6 mode + settings: _sharedSettings + [.swiftLanguageMode(.v5)]), .target( kind: .test, name: "RopeModuleTests", @@ -291,7 +339,7 @@ let targets: [CustomTarget] = [ "_RopeModule", "SortedCollections", ], - exclude: ["CMakeLists.txt"]) + exclude: ["CMakeLists.txt"]), ] var _products: [Product] = [] diff --git a/Sources/BitCollections/BitArray/BitArray+BitwiseOperations.swift b/Sources/BitCollections/BitArray/BitArray+BitwiseOperations.swift index 2db5d9615..aab2137c4 100644 --- a/Sources/BitCollections/BitArray/BitArray+BitwiseOperations.swift +++ b/Sources/BitCollections/BitArray/BitArray+BitwiseOperations.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + #if false // FIXME: Bitwise operators disabled for now. I have two concerns: // 1. We need to support bitwise operations over slices of bit arrays, not just diff --git a/Sources/BitCollections/BitArray/BitArray+Codable.swift b/Sources/BitCollections/BitArray/BitArray+Codable.swift index 18c765ede..a7b5d9d0a 100644 --- a/Sources/BitCollections/BitArray/BitArray+Codable.swift +++ b/Sources/BitCollections/BitArray/BitArray+Codable.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + extension BitArray: Codable { /// Encodes this bit array into the given encoder. /// diff --git a/Sources/BitCollections/BitArray/BitArray+Initializers.swift b/Sources/BitCollections/BitArray/BitArray+Initializers.swift index 4bf604b41..71a2be785 100644 --- a/Sources/BitCollections/BitArray/BitArray+Initializers.swift +++ b/Sources/BitCollections/BitArray/BitArray+Initializers.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + extension BitArray { /// Initialize a bit array from a bit set. /// diff --git a/Sources/BitCollections/BitArray/BitArray+RandomBits.swift b/Sources/BitCollections/BitArray/BitArray+RandomBits.swift index f59ef36aa..eaf5d2837 100644 --- a/Sources/BitCollections/BitArray/BitArray+RandomBits.swift +++ b/Sources/BitCollections/BitArray/BitArray+RandomBits.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + extension BitArray { /// Create and return a new bit array consisting of `count` random bits, /// using the system random number generator. diff --git a/Sources/BitCollections/BitArray/BitArray+Testing.swift b/Sources/BitCollections/BitArray/BitArray+Testing.swift index bf0bcbf5b..73e4766e7 100644 --- a/Sources/BitCollections/BitArray/BitArray+Testing.swift +++ b/Sources/BitCollections/BitArray/BitArray+Testing.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + extension BitArray { @_spi(Testing) public var _capacity: Int { diff --git a/Sources/BitCollections/BitSet/BitSet+BidirectionalCollection.swift b/Sources/BitCollections/BitSet/BitSet+BidirectionalCollection.swift index 30a57cceb..df7c69dd2 100644 --- a/Sources/BitCollections/BitSet/BitSet+BidirectionalCollection.swift +++ b/Sources/BitCollections/BitSet/BitSet+BidirectionalCollection.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + extension BitSet: Sequence { /// The type representing the bit set's elements. /// Bit sets are collections of nonnegative integers. diff --git a/Sources/BitCollections/BitSet/BitSet+Random.swift b/Sources/BitCollections/BitSet/BitSet+Random.swift index 5e7977920..d76c835e2 100644 --- a/Sources/BitCollections/BitSet/BitSet+Random.swift +++ b/Sources/BitCollections/BitSet/BitSet+Random.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + extension BitSet { public static func random(upTo limit: Int) -> BitSet { var rng = SystemRandomNumberGenerator() diff --git a/Sources/BitCollections/BitSet/BitSet+SetAlgebra basics.swift b/Sources/BitCollections/BitSet/BitSet+SetAlgebra basics.swift index a7a0f344d..fbe6461c9 100644 --- a/Sources/BitCollections/BitSet/BitSet+SetAlgebra basics.swift +++ b/Sources/BitCollections/BitSet/BitSet+SetAlgebra basics.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import InternalCollectionsUtilities + extension BitSet { /// Returns a Boolean value that indicates whether the given element exists /// in the set. diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index ad39dd9be..2ada2ac20 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -56,6 +56,7 @@ else() endif() if(NOT COLLECTIONS_FOUNDATION_TOOLCHAIN_MODULE) + add_subdirectory(Future) add_subdirectory(BitCollections) add_subdirectory(DequeModule) add_subdirectory(HashTreeCollections) diff --git a/Sources/DequeModule/CMakeLists.txt b/Sources/DequeModule/CMakeLists.txt index 07364b66e..2e5cd3915 100644 --- a/Sources/DequeModule/CMakeLists.txt +++ b/Sources/DequeModule/CMakeLists.txt @@ -14,7 +14,8 @@ else() add_library(DequeModule ${COLLECTIONS_DEQUE_SOURCES}) target_link_libraries(DequeModule PRIVATE - InternalCollectionsUtilities) + InternalCollectionsUtilities + Future) set_target_properties(DequeModule PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) @@ -31,13 +32,12 @@ target_sources(${module_name} PRIVATE "Deque+ExpressibleByArrayLiteral.swift" "Deque+Extras.swift" "Deque+Hashable.swift" - "Deque+Sendable.swift" "Deque+Testing.swift" "Deque._Storage.swift" - "Deque._UnsafeHandle.swift" "Deque.swift" - "_DequeBuffer.swift" - "_DequeBufferHeader.swift" + "DynamicDeque.swift" + "RigidDeque.swift" "_DequeSlot.swift" - "_UnsafeWrappedBuffer.swift" + "_UnsafeDequeHandle.swift" + "_UnsafeDequeSegments.swift" ) diff --git a/Sources/DequeModule/Deque+Codable.swift b/Sources/DequeModule/Deque+Codable.swift index abef341ea..ee214b8cd 100644 --- a/Sources/DequeModule/Deque+Codable.swift +++ b/Sources/DequeModule/Deque+Codable.swift @@ -35,11 +35,11 @@ extension Deque: Decodable where Element: Decodable { /// - Parameter decoder: The decoder to read data from. @inlinable public init(from decoder: Decoder) throws { - self.init() - var container = try decoder.unkeyedContainer() if let count = container.count { - self.reserveCapacity(count) + self.init(minimumCapacity: count) + } else { + self.init() } while !container.isAtEnd { let element = try container.decode(Element.self) diff --git a/Sources/DequeModule/Deque+Collection.swift b/Sources/DequeModule/Deque+Collection.swift index b56772cb4..f8198a5d6 100644 --- a/Sources/DequeModule/Deque+Collection.swift +++ b/Sources/DequeModule/Deque+Collection.swift @@ -11,9 +11,10 @@ #if !COLLECTIONS_SINGLE_MODULE import InternalCollectionsUtilities +import Future #endif -extension Deque: Sequence { +extension Deque { // Implementation note: we could also use the default `IndexingIterator` here. // This custom implementation performs direct storage access to eliminate any // and all index validation overhead. It also optimizes away repeated @@ -21,9 +22,9 @@ extension Deque: Sequence { /// An iterator over the members of a deque. @frozen - public struct Iterator: IteratorProtocol { + public struct Iterator { @usableFromInline - internal var _storage: Deque._Storage + internal var _base: Deque @usableFromInline internal var _nextSlot: _Slot @@ -32,68 +33,74 @@ extension Deque: Sequence { internal var _endSlot: _Slot @inlinable - internal init(_storage: Deque._Storage, start: _Slot, end: _Slot) { - self._storage = _storage + internal init(_base: Deque, start: _Slot, end: _Slot) { + self._base = _base self._nextSlot = start self._endSlot = end } @inlinable internal init(_base: Deque) { - self = _base._storage.read { handle in + self = _base._read { handle in let start = handle.startSlot let end = Swift.min(start.advanced(by: handle.count), handle.limSlot) - return Self(_storage: _base._storage, start: start, end: end) + return Self(_base: _base, start: start, end: end) } } @inlinable internal init(_base: Deque, from index: Int) { - self = _base._storage.read { handle in + self = _base._read { handle in assert(index >= 0 && index <= handle.count) let start = handle.slot(forOffset: index) if index == handle.count { - return Self(_storage: _base._storage, start: start, end: start) + return Self(_base: _base, start: start, end: start) } var end = handle.endSlot if start >= end { end = handle.limSlot } - return Self(_storage: _base._storage, start: start, end: end) + return Self(_base: _base, start: start, end: end) } } + } +} - @inlinable - @inline(never) - internal mutating func _swapSegment() -> Bool { - assert(_nextSlot == _endSlot) - return _storage.read { handle in - let end = handle.endSlot - if end == .zero || end == _nextSlot { - return false - } - _endSlot = end - _nextSlot = .zero - return true +extension Deque.Iterator: Sendable where Element: Sendable {} + +extension Deque.Iterator: IteratorProtocol { + @inlinable + @inline(never) + internal mutating func _swapSegment() -> Bool { + assert(_nextSlot == _endSlot) + return _base._read { handle in + let end = handle.endSlot + if end == .zero || end == _nextSlot { + return false } + _endSlot = end + _nextSlot = .zero + return true } + } - /// Advances to the next element and returns it, or `nil` if no next element - /// exists. - /// - /// Once `nil` has been returned, all subsequent calls return `nil`. - @inlinable - public mutating func next() -> Element? { - if _nextSlot == _endSlot { - guard _swapSegment() else { return nil } - } - assert(_nextSlot < _endSlot) - let slot = _nextSlot - _nextSlot = _nextSlot.advanced(by: 1) - return _storage.read { handle in - return handle.ptr(at: slot).pointee - } + /// Advances to the next element and returns it, or `nil` if no next element + /// exists. + /// + /// Once `nil` has been returned, all subsequent calls return `nil`. + @inlinable + public mutating func next() -> Element? { + if _nextSlot == _endSlot { + guard _swapSegment() else { return nil } + } + assert(_nextSlot < _endSlot) + let slot = _nextSlot + _nextSlot = _nextSlot.advanced(by: 1) + return _base._read { handle in + return handle.ptr(at: slot).pointee } } +} +extension Deque: Sequence { /// Returns an iterator over the elements of the deque. /// /// - Complexity: O(1) @@ -104,8 +111,8 @@ extension Deque: Sequence { @inlinable public __consuming func _copyToContiguousArray() -> ContiguousArray { - ContiguousArray(unsafeUninitializedCapacity: _storage.count) { target, count in - _storage.read { source in + ContiguousArray(unsafeUninitializedCapacity: count) { target, count in + _read { source in let segments = source.segments() let c = segments.first.count target[.. ) -> (Iterator, UnsafeMutableBufferPointer.Index) { - _storage.read { source in + _read { source in let segments = source.segments() let c1 = Swift.min(segments.first.count, target.count) target[..( _ body: (UnsafeBufferPointer) throws -> R ) rethrows -> R? { - return try _storage.read { handle in + return try _read { handle in + guard handle.count > 0 else { return nil } let endSlot = handle.startSlot.advanced(by: handle.count) guard endSlot.position <= handle.capacity else { return nil } return try body(handle.buffer(for: handle.startSlot ..< endSlot)) @@ -164,8 +172,6 @@ extension Deque: Sequence { } } -extension Deque.Iterator: Sendable where Element: Sendable {} - extension Deque: RandomAccessCollection { public typealias Index = Int public typealias SubSequence = Slice @@ -176,7 +182,7 @@ extension Deque: RandomAccessCollection { /// - Complexity: O(1) @inlinable @inline(__always) - public var count: Int { _storage.count } + public var count: Int { _storage.value.count } /// The position of the first element in a nonempty deque. /// @@ -353,45 +359,14 @@ extension Deque: RandomAccessCollection { /// writing is O(`count`). @inlinable public subscript(index: Int) -> Element { - get { - precondition(index >= 0 && index < count, "Index out of bounds") - return _storage.read { $0.ptr(at: $0.slot(forOffset: index)).pointee } + @lifetime(borrow self) + unsafeAddress { + _storage.value.borrowElement(at: index)._pointer } - set { - precondition(index >= 0 && index < count, "Index out of bounds") - _storage.ensureUnique() - _storage.update { handle in - let slot = handle.slot(forOffset: index) - handle.ptr(at: slot).pointee = newValue - } - } - @inline(__always) // https://github.com/apple/swift-collections/issues/164 - _modify { - precondition(index >= 0 && index < count, "Index out of bounds") - var (slot, value) = _prepareForModify(at: index) - defer { - _finalizeModify(slot, value) - } - yield &value - } - } - - @inlinable - internal mutating func _prepareForModify(at index: Int) -> (_Slot, Element) { - _storage.ensureUnique() - // We technically aren't supposed to escape storage pointers out of a - // managed buffer, so we escape a `(slot, value)` pair instead, leaving - // the corresponding slot temporarily uninitialized. - return _storage.update { handle in - let slot = handle.slot(forOffset: index) - return (slot, handle.ptr(at: slot).move()) - } - } - - @inlinable - internal mutating func _finalizeModify(_ slot: _Slot, _ value: Element) { - _storage.update { handle in - handle.ptr(at: slot).initialize(to: value) + @lifetime(&self) + unsafeMutableAddress { + _ensureUnique() + return _storage.value.mutateElement(at: index)._pointer } } @@ -434,8 +409,8 @@ extension Deque: MutableCollection { public mutating func swapAt(_ i: Int, _ j: Int) { precondition(i >= 0 && i < count, "Index out of bounds") precondition(j >= 0 && j < count, "Index out of bounds") - _storage.ensureUnique() - _storage.update { handle in + _ensureUnique() + _update { handle in let slot1 = handle.slot(forOffset: i) let slot2 = handle.slot(forOffset: j) handle.mutableBuffer.swapAt(slot1.position, slot2.position) @@ -466,8 +441,8 @@ extension Deque: MutableCollection { public mutating func withContiguousMutableStorageIfAvailable( _ body: (inout UnsafeMutableBufferPointer) throws -> R ) rethrows -> R? { - _storage.ensureUnique() - return try _storage.update { handle in + _ensureUnique() + return try _update { handle in let endSlot = handle.startSlot.advanced(by: handle.count) guard endSlot.position <= handle.capacity else { // FIXME: Rotate storage such that it becomes contiguous. @@ -506,7 +481,8 @@ extension Deque: RangeReplaceableCollection { /// - Complexity: O(1) @inlinable public init() { - _storage = _Storage() + // FIXME: Can we do empty singletons in this world? Should _storage become optional? + self.init(minimumCapacity: 0) } /// Reserves enough space to store the specified number of elements. @@ -522,7 +498,7 @@ extension Deque: RangeReplaceableCollection { /// - Complexity: O(`count`) @inlinable public mutating func reserveCapacity(_ minimumCapacity: Int) { - _storage.ensureUnique(minimumCapacity: minimumCapacity, linearGrowth: true) + _ensureUnique(minimumCapacity: minimumCapacity, linearGrowth: true) } /// Replaces a range of elements with the elements in the specified @@ -552,13 +528,13 @@ extension Deque: RangeReplaceableCollection { let removalCount = subrange.count let insertionCount = newElements.count let deltaCount = insertionCount - removalCount - _storage.ensureUnique(minimumCapacity: count + deltaCount) let replacementCount = Swift.min(removalCount, insertionCount) let targetCut = subrange.lowerBound + replacementCount let sourceCut = newElements.index(newElements.startIndex, offsetBy: replacementCount) - _storage.update { target in + _ensureUnique(minimumCapacity: count + deltaCount) + _update { target in target.uncheckedReplaceInPlace( inOffsets: subrange.lowerBound ..< targetCut, with: newElements[..= 0) self.init(minimumCapacity: count) - _storage.update { handle in + _update { handle in assert(handle.startSlot == .zero) if count > 0 { - handle.ptr(at: .zero).initialize(repeating: repeatedValue, count: count) + handle.mutablePtr(at: .zero).initialize( + repeating: repeatedValue, count: count) } handle.count = count } @@ -605,7 +582,7 @@ extension Deque: RangeReplaceableCollection { /// - Complexity: O(*n*), where *n* is the number of elements in the sequence. @inlinable public init(_ elements: some Sequence) { - self.init() + self.init(minimumCapacity: elements.underestimatedCount) self.append(contentsOf: elements) } @@ -618,9 +595,9 @@ extension Deque: RangeReplaceableCollection { @inlinable public init(_ elements: some Collection) { let c = elements.count - guard c > 0 else { _storage = _Storage(); return } - self._storage = _Storage(minimumCapacity: c) - _storage.update { handle in + guard c > 0 else { self.init(); return } + self.init(minimumCapacity: c) + _update { handle in assert(handle.startSlot == .zero) let target = handle.mutableBuffer(for: .zero ..< _Slot(at: c)) let done: Void? = elements.withContiguousStorageIfAvailable { source in @@ -633,6 +610,18 @@ extension Deque: RangeReplaceableCollection { } } + @inlinable + public init(_ elements: UnsafeBufferPointer) { + guard elements.count > 0 else { self.init(); return } + self.init(minimumCapacity: elements.count) + _update { handle in + assert(handle.startSlot == .zero) + let target = handle.mutableBuffer(for: .zero ..< _Slot(at: elements.count)) + target.initializeAll(fromContentsOf: elements) + handle.count = elements.count + } + } + /// Adds a new element at the end of the deque. /// /// Use this method to append a single element to the end of a deque. @@ -658,8 +647,8 @@ extension Deque: RangeReplaceableCollection { /// - SeeAlso: `prepend(_:)` @inlinable public mutating func append(_ newElement: Element) { - _storage.ensureUnique(minimumCapacity: count + 1) - _storage.update { + _ensureUnique(minimumCapacity: count + 1) + _update { $0.uncheckedAppend(newElement) } } @@ -681,24 +670,24 @@ extension Deque: RangeReplaceableCollection { @inlinable public mutating func append(contentsOf newElements: some Sequence) { let done: Void? = newElements.withContiguousStorageIfAvailable { source in - _storage.ensureUnique(minimumCapacity: count + source.count) - _storage.update { $0.uncheckedAppend(contentsOf: source) } + _ensureUnique(minimumCapacity: count + source.count) + _update { $0.uncheckedAppend(contentsOf: source) } } if done != nil { return } let underestimatedCount = newElements.underestimatedCount - _storage.ensureUnique(minimumCapacity: count + underestimatedCount) - var it = _storage.update { target in + _ensureUnique(minimumCapacity: count + underestimatedCount) + var it = _update { target in let gaps = target.availableSegments() let (it, copied) = gaps.initialize(fromSequencePrefix: newElements) target.count += copied return it } while let next = it.next() { - _storage.ensureUnique(minimumCapacity: count + 1) - _storage.update { target in + _ensureUnique(minimumCapacity: count + 1) + _update { target in target.uncheckedAppend(next) let gaps = target.availableSegments() target.count += gaps.initialize(fromPrefixOf: &it) @@ -725,15 +714,15 @@ extension Deque: RangeReplaceableCollection { contentsOf newElements: some Collection ) { let done: Void? = newElements.withContiguousStorageIfAvailable { source in - _storage.ensureUnique(minimumCapacity: count + source.count) - _storage.update { $0.uncheckedAppend(contentsOf: source) } + _ensureUnique(minimumCapacity: count + source.count) + _update { $0.uncheckedAppend(contentsOf: source) } } guard done == nil else { return } let c = newElements.count guard c > 0 else { return } - _storage.ensureUnique(minimumCapacity: count + c) - _storage.update { target in + _ensureUnique(minimumCapacity: count + c) + _update { target in let gaps = target.availableSegments().prefix(c) gaps.initialize(from: newElements) target.count += c @@ -759,19 +748,9 @@ extension Deque: RangeReplaceableCollection { public mutating func insert(_ newElement: Element, at index: Int) { precondition(index >= 0 && index <= count, "Can't insert element at invalid index") - _storage.ensureUnique(minimumCapacity: count + 1) - _storage.update { target in - if index == 0 { - target.uncheckedPrepend(newElement) - return - } - if index == count { - target.uncheckedAppend(newElement) - return - } - let gap = target.openGap(ofSize: 1, atOffset: index) - assert(gap.first.count == 1) - gap.first.baseAddress!.initialize(to: newElement) + _ensureUnique(minimumCapacity: count + 1) + _update { target in + target.uncheckedInsert(newElement, at: index) } } @@ -800,8 +779,8 @@ extension Deque: RangeReplaceableCollection { precondition(index >= 0 && index <= count, "Can't insert elements at an invalid index") let newCount = newElements.count - _storage.ensureUnique(minimumCapacity: count + newCount) - _storage.update { target in + _ensureUnique(minimumCapacity: count + newCount) + _update { target in target.uncheckedInsert(contentsOf: newElements, count: newCount, atOffset: index) } } @@ -826,11 +805,10 @@ extension Deque: RangeReplaceableCollection { public mutating func remove(at index: Int) -> Element { precondition(index >= 0 && index < self.count, "Index out of bounds") // FIXME: Implement storage shrinking - _storage.ensureUnique() - return _storage.update { target in - // FIXME: Add direct implementation & see if it makes a difference - let result = self[index] - target.uncheckedRemove(offsets: index ..< index + 1) + _ensureUnique() + return _update { target in + let result = target.mutablePtr(at: target.slot(forOffset: index)).move() + target.closeGap(offsets: index ..< index + 1) return result } } @@ -852,23 +830,23 @@ extension Deque: RangeReplaceableCollection { public mutating func removeSubrange(_ bounds: Range) { precondition(bounds.lowerBound >= 0 && bounds.upperBound <= self.count, "Index range out of bounds") - _storage.ensureUnique() - _storage.update { $0.uncheckedRemove(offsets: bounds) } + _ensureUnique() + _update { $0.uncheckedRemove(offsets: bounds) } } @inlinable public mutating func _customRemoveLast() -> Element? { precondition(!isEmpty, "Cannot remove last element of an empty Deque") - _storage.ensureUnique() - return _storage.update { $0.uncheckedRemoveLast() } + _ensureUnique() + return _update { $0.uncheckedRemoveLast() } } @inlinable public mutating func _customRemoveLast(_ n: Int) -> Bool { precondition(n >= 0, "Can't remove a negative number of elements") precondition(n <= count, "Can't remove more elements than there are in the Collection") - _storage.ensureUnique() - _storage.update { $0.uncheckedRemoveLast(n) } + _ensureUnique() + _update { $0.uncheckedRemoveLast(n) } return true } @@ -884,8 +862,8 @@ extension Deque: RangeReplaceableCollection { @discardableResult public mutating func removeFirst() -> Element { precondition(!isEmpty, "Cannot remove first element of an empty Deque") - _storage.ensureUnique() - return _storage.update { $0.uncheckedRemoveFirst() } + _ensureUnique() + return _update { $0.uncheckedRemoveFirst() } } /// Removes the specified number of elements from the beginning of the deque. @@ -900,8 +878,8 @@ extension Deque: RangeReplaceableCollection { public mutating func removeFirst(_ n: Int) { precondition(n >= 0, "Can't remove a negative number of elements") precondition(n <= count, "Can't remove more elements than there are in the Collection") - _storage.ensureUnique() - return _storage.update { $0.uncheckedRemoveFirst(n) } + _ensureUnique() + return _update { $0.uncheckedRemoveFirst(n) } } /// Removes all elements from the deque. @@ -913,8 +891,8 @@ extension Deque: RangeReplaceableCollection { @inlinable public mutating func removeAll(keepingCapacity keepCapacity: Bool = false) { if keepCapacity { - _storage.ensureUnique() - _storage.update { $0.uncheckedRemoveAll() } + _ensureUnique() + _update { $0.uncheckedRemoveAll() } } else { self = Deque() } diff --git a/Sources/DequeModule/Deque+Container.swift b/Sources/DequeModule/Deque+Container.swift new file mode 100644 index 000000000..9cedc3514 --- /dev/null +++ b/Sources/DequeModule/Deque+Container.swift @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +#if !COLLECTIONS_SINGLE_MODULE +import Future +#endif + +#if false // FIXME: Follow up on compiler crash when deserializing this conformance +extension Deque: RandomAccessContainer { + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + _storage.value.borrowElement(at: index) + } + + @available(SwiftStdlib 6.2, *) + @lifetime(borrow self) + public func nextSpan(after index: inout Int) -> Span { + _storage.value.nextSpan(after: &index) + } + + @available(SwiftStdlib 6.2, *) + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + _storage.value.previousSpan(before: &index) + } +} +#endif diff --git a/Sources/DequeModule/Deque+Equatable.swift b/Sources/DequeModule/Deque+Equatable.swift index 4a068d7fa..eb0f4709e 100644 --- a/Sources/DequeModule/Deque+Equatable.swift +++ b/Sources/DequeModule/Deque+Equatable.swift @@ -9,6 +9,8 @@ // //===----------------------------------------------------------------------===// +import Future + extension Deque: Equatable where Element: Equatable { /// Returns a Boolean value indicating whether two values are equal. Two /// deques are considered equal if they contain the same elements in the same @@ -17,16 +19,10 @@ extension Deque: Equatable where Element: Equatable { /// - Complexity: O(`min(left.count, right.count)`) @inlinable public static func ==(left: Self, right: Self) -> Bool { - let lhsCount = left.count - if lhsCount != right.count { - return false - } - - // Test referential equality. - if lhsCount == 0 || left._storage.isIdentical(to: right._storage) { - return true - } - + let count = left.count + guard count == right.count else { return false } + if count == 0 { return true } + if left._storage.isIdentical(to: right._storage) { return true } return left.elementsEqual(right) } } diff --git a/Sources/DequeModule/Deque+Extras.swift b/Sources/DequeModule/Deque+Extras.swift index 850e493fe..8f4e6fb8a 100644 --- a/Sources/DequeModule/Deque+Extras.swift +++ b/Sources/DequeModule/Deque+Extras.swift @@ -45,8 +45,8 @@ extension Deque { initializingWith initializer: (inout UnsafeMutableBufferPointer, inout Int) throws -> Void ) rethrows { - self._storage = .init(minimumCapacity: capacity) - try _storage.update { handle in + self.init(minimumCapacity: capacity) + try _update { handle in handle.startSlot = .zero var count = 0 var buffer = handle.mutableBuffer(for: .zero ..< _Slot(at: capacity)) @@ -76,8 +76,8 @@ extension Deque { // FIXME: Add this to the stdlib on BidirectionalCollection // where Self == Self.SubSequence guard count > 0 else { return nil } - _storage.ensureUnique() - return _storage.update { + _ensureUnique() + return _update { $0.uncheckedRemoveFirst() } } @@ -111,8 +111,8 @@ extension Deque { /// - SeeAlso: `append(_:)` @inlinable public mutating func prepend(_ newElement: Element) { - _storage.ensureUnique(minimumCapacity: count + 1) - return _storage.update { + _ensureUnique(minimumCapacity: count + 1) + return _update { $0.uncheckedPrepend(newElement) } } @@ -138,15 +138,15 @@ extension Deque { contentsOf newElements: some Collection ) { let done: Void? = newElements.withContiguousStorageIfAvailable { source in - _storage.ensureUnique(minimumCapacity: count + source.count) - _storage.update { $0.uncheckedPrepend(contentsOf: source) } + _ensureUnique(minimumCapacity: count + source.count) + _update { $0.uncheckedPrepend(contentsOf: source) } } guard done == nil else { return } let c = newElements.count guard c > 0 else { return } - _storage.ensureUnique(minimumCapacity: count + c) - _storage.update { target in + _ensureUnique(minimumCapacity: count + c) + _update { target in let gaps = target.availableSegments().suffix(c) gaps.initialize(from: newElements) target.count += c @@ -173,8 +173,8 @@ extension Deque { @inlinable public mutating func prepend(contentsOf newElements: some Sequence) { let done: Void? = newElements.withContiguousStorageIfAvailable { source in - _storage.ensureUnique(minimumCapacity: count + source.count) - _storage.update { $0.uncheckedPrepend(contentsOf: source) } + _ensureUnique(minimumCapacity: count + source.count) + _update { $0.uncheckedPrepend(contentsOf: source) } } guard done == nil else { return } @@ -182,7 +182,7 @@ extension Deque { self.append(contentsOf: newElements) let newCount = self.count let c = newCount - originalCount - _storage.update { target in + _update { target in target.startSlot = target.slot(forOffset: originalCount) target.count = target.capacity target.closeGap(offsets: c ..< c + (target.capacity - newCount)) diff --git a/Sources/DequeModule/Deque+Testing.swift b/Sources/DequeModule/Deque+Testing.swift index 0136886b9..e2b8ed440 100644 --- a/Sources/DequeModule/Deque+Testing.swift +++ b/Sources/DequeModule/Deque+Testing.swift @@ -11,6 +11,7 @@ #if !COLLECTIONS_SINGLE_MODULE import InternalCollectionsUtilities +import Future #endif // This file contains exported but non-public entry points to support clear box @@ -37,8 +38,8 @@ extension Deque { /// This property isn't intended to be used outside of `Deque`'s own test /// target. @_spi(Testing) - public var _capacity: Int { - _storage.capacity + public var _unstableCapacity: Int { + _capacity } /// The number of the storage slot in this deque that holds the first element. @@ -48,8 +49,8 @@ extension Deque { /// This property isn't intended to be used outside of `Deque`'s own test /// target. @_spi(Testing) - public var _startSlot: Int { - _storage.startSlot.position + public var _unstableStartSlot: Int { + _storage.value._handle.startSlot.position } /// Constructs a deque instance of the specified contents and layout. Exposed @@ -62,29 +63,14 @@ extension Deque { contents: some Sequence ) { let contents = ContiguousArray(contents) - precondition(capacity >= 0) - precondition(startSlot >= 0 && (startSlot < capacity || (capacity == 0 && startSlot == 0))) - precondition(contents.count <= capacity) - let startSlot = _Slot(at: startSlot) - let buffer = _DequeBuffer.create(minimumCapacity: capacity) { _ in - _DequeBufferHeader(capacity: capacity, count: contents.count, startSlot: startSlot) - } - let storage = Deque._Storage(unsafeDowncast(buffer, to: _DequeBuffer.self)) - if contents.count > 0 { - contents.withUnsafeBufferPointer { source in - storage.update { target in - let segments = target.mutableSegments() - let c = segments.first.count - segments.first.initializeAll(fromContentsOf: source.prefix(c)) - if let second = segments.second { - second.initializeAll(fromContentsOf: source.dropFirst(c)) - } - } - } - } - self.init(_storage: storage) - assert(self._capacity == capacity) - assert(self._startSlot == startSlot.position) + let d = RigidDeque( + _capacity: capacity, + startSlot: startSlot, + count: contents.count + ) { contents[$0] } + self.init(_storage: d) + assert(self._unstableCapacity == capacity) + assert(self._unstableStartSlot == startSlot) assert(self.count == contents.count) } } diff --git a/Sources/DequeModule/Deque._Storage.swift b/Sources/DequeModule/Deque._Storage.swift deleted file mode 100644 index 307801806..000000000 --- a/Sources/DequeModule/Deque._Storage.swift +++ /dev/null @@ -1,226 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2021 - 2024 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 -// -//===----------------------------------------------------------------------===// - -extension Deque { - @frozen - @usableFromInline - struct _Storage { - @usableFromInline - internal typealias _Buffer = ManagedBufferPointer<_DequeBufferHeader, Element> - - @usableFromInline - internal var _buffer: _Buffer - - @inlinable - @inline(__always) - internal init(_buffer: _Buffer) { - self._buffer = _buffer - } - } -} - -extension Deque._Storage: CustomStringConvertible { - @usableFromInline - internal var description: String { - "Deque<\(Element.self)>._Storage\(_buffer.header)" - } -} - -extension Deque._Storage { - @inlinable - internal init() { - self.init(_buffer: _Buffer(unsafeBufferObject: _emptyDequeStorage)) - } - - @inlinable - internal init(_ object: _DequeBuffer) { - self.init(_buffer: _Buffer(unsafeBufferObject: object)) - } - - @inlinable - internal init(minimumCapacity: Int) { - let object = _DequeBuffer.create( - minimumCapacity: minimumCapacity, - makingHeaderWith: { - #if os(OpenBSD) - let capacity = minimumCapacity - #else - let capacity = $0.capacity - #endif - return _DequeBufferHeader(capacity: capacity, count: 0, startSlot: .zero) - }) - self.init(_buffer: _Buffer(unsafeBufferObject: object)) - } -} - -extension Deque._Storage { - #if COLLECTIONS_INTERNAL_CHECKS - @usableFromInline @inline(never) @_effects(releasenone) - internal func _checkInvariants() { - _buffer.withUnsafeMutablePointerToHeader { $0.pointee._checkInvariants() } - } - #else - @inlinable @inline(__always) - internal func _checkInvariants() {} - #endif // COLLECTIONS_INTERNAL_CHECKS -} - -extension Deque._Storage { - @inlinable - @inline(__always) - internal var identity: AnyObject { _buffer.buffer } - - - @inlinable - @inline(__always) - internal var capacity: Int { - _buffer.withUnsafeMutablePointerToHeader { $0.pointee.capacity } - } - - @inlinable - @inline(__always) - internal var count: Int { - _buffer.withUnsafeMutablePointerToHeader { $0.pointee.count } - } - - @inlinable - @inline(__always) - internal var startSlot: _DequeSlot { - _buffer.withUnsafeMutablePointerToHeader { $0.pointee.startSlot - } - } -} - -extension Deque._Storage { - @usableFromInline - internal typealias Index = Int - - @usableFromInline - internal typealias _UnsafeHandle = Deque._UnsafeHandle - - @inlinable - @inline(__always) - internal func read(_ body: (_UnsafeHandle) throws -> R) rethrows -> R { - try _buffer.withUnsafeMutablePointers { header, elements in - let handle = _UnsafeHandle(header: header, - elements: elements, - isMutable: false) - return try body(handle) - } - } - - @inlinable - @inline(__always) - internal func update(_ body: (_UnsafeHandle) throws -> R) rethrows -> R { - try _buffer.withUnsafeMutablePointers { header, elements in - let handle = _UnsafeHandle(header: header, - elements: elements, - isMutable: true) - return try body(handle) - } - } -} - -extension Deque._Storage { - /// Return a boolean indicating whether this storage instance is known to have - /// a single unique reference. If this method returns true, then it is safe to - /// perform in-place mutations on the deque. - @inlinable - @inline(__always) - internal mutating func isUnique() -> Bool { - _buffer.isUniqueReference() - } - - /// Ensure that this storage refers to a uniquely held buffer by copying - /// elements if necessary. - @inlinable - @inline(__always) - internal mutating func ensureUnique() { - if isUnique() { return } - self._makeUniqueCopy() - } - - @inlinable - @inline(never) - internal mutating func _makeUniqueCopy() { - self = self.read { $0.copyElements() } - } - - /// The growth factor to use to increase storage size to make place for an - /// insertion. - @inlinable - @inline(__always) - internal static var growthFactor: Double { 1.5 } - - @usableFromInline - internal func _growCapacity( - to minimumCapacity: Int, - linearly: Bool - ) -> Int { - if linearly { return Swift.max(capacity, minimumCapacity) } - return Swift.max(Int((Self.growthFactor * Double(capacity)).rounded(.up)), - minimumCapacity) - } - - /// Ensure that we have a uniquely referenced buffer with enough space to - /// store at least `minimumCapacity` elements. - /// - /// - Parameter minimumCapacity: The minimum number of elements the buffer - /// needs to be able to hold on return. - /// - /// - Parameter linearGrowth: If true, then don't use an exponential growth - /// factor when reallocating the buffer -- just allocate space for the - /// requested number of elements - @inlinable - @inline(__always) - internal mutating func ensureUnique( - minimumCapacity: Int, - linearGrowth: Bool = false - ) { - let unique = isUnique() - if _slowPath(capacity < minimumCapacity || !unique) { - _ensureUnique(isUnique: unique, minimumCapacity: minimumCapacity, linearGrowth: linearGrowth) - } - } - - @inlinable - @inline(never) - internal mutating func _ensureUnique( - isUnique: Bool, - minimumCapacity: Int, - linearGrowth: Bool - ) { - if capacity >= minimumCapacity { - assert(!isUnique) - self = self.read { $0.copyElements() } - return - } - - let minimumCapacity = _growCapacity(to: minimumCapacity, linearly: linearGrowth) - if isUnique { - self = self.update { source in - source.moveElements(minimumCapacity: minimumCapacity) - } - } else { - self = self.read { source in - source.copyElements(minimumCapacity: minimumCapacity) - } - } - } -} - -extension Deque._Storage { - @inlinable - @inline(__always) - internal func isIdentical(to other: Self) -> Bool { - self._buffer.buffer === other._buffer.buffer - } -} diff --git a/Sources/DequeModule/Deque.swift b/Sources/DequeModule/Deque.swift index 9d6667a36..3d33c77c2 100644 --- a/Sources/DequeModule/Deque.swift +++ b/Sources/DequeModule/Deque.swift @@ -9,6 +9,10 @@ // //===----------------------------------------------------------------------===// +#if !COLLECTIONS_SINGLE_MODULE +import Future +#endif + /// A collection implementing a double-ended queue. `Deque` (pronounced "deck") /// implements an ordered random-access collection that supports efficient /// insertions and removals from both ends. @@ -85,11 +89,11 @@ public struct Deque { internal typealias _Slot = _DequeSlot @usableFromInline - internal var _storage: _Storage + internal var _storage: Shared> @inlinable - internal init(_storage: _Storage) { - self._storage = _storage + internal init(_storage: consuming RigidDeque) { + self._storage = Shared(_storage) } /// Creates and empty deque with preallocated space for at least the specified @@ -100,6 +104,135 @@ public struct Deque { /// storage buffer. @inlinable public init(minimumCapacity: Int) { - self._storage = _Storage(minimumCapacity: minimumCapacity) + self.init(_storage: RigidDeque(capacity: minimumCapacity)) + } +} + +extension Deque: @unchecked Sendable where Element: Sendable {} + +extension Deque { + @usableFromInline + internal typealias _UnsafeHandle = _UnsafeDequeHandle + + @inlinable + @inline(__always) + internal func _read( + _ body: (borrowing _UnsafeHandle) throws(E) -> R + ) throws(E) -> R { + return try body(_storage.value._handle) + } + + @inlinable + @inline(__always) + internal mutating func _update( + _ body: (inout _UnsafeHandle) throws(E) -> R + ) throws(E) -> R { + _ensureUnique() + var rigid = _storage.mutate() + defer { extendLifetime(rigid) } + return try body(&rigid[]._handle) + } +} + +extension Deque { + /// Return a boolean indicating whether this storage instance is known to have + /// a single unique reference. If this method returns true, then it is safe to + /// perform in-place mutations on the deque. + @inlinable + internal mutating func _isUnique() -> Bool { + _storage.isUnique() + } + + /// Ensure that this storage refers to a uniquely held buffer by copying + /// elements if necessary. + @inlinable + @inline(__always) + internal mutating func _ensureUnique() { + _storage.ensureUnique(cloner: { $0._copy() }) + } + + /// Copy elements into a new storage instance without changing capacity or + /// layout. + @inlinable + @inline(never) + internal func _makeUniqueCopy() -> Self { + Deque(_storage: _storage.value._copy()) + } + + @inlinable + @inline(never) + internal func _makeUniqueCopy(capacity: Int) -> Self { + Deque(_storage: _storage.value._copy(capacity: capacity)) + } + + @inlinable + internal var _capacity: Int { + _storage.value.capacity + } + + @usableFromInline + internal func _growCapacity( + to minimumCapacity: Int, + linearly: Bool + ) -> Int { + if linearly { + return Swift.max(_capacity, minimumCapacity) + } + let next = (3 * _capacity + 1) / 2 + return Swift.max(next, minimumCapacity) + } + + /// Ensure that we have a uniquely referenced buffer with enough space to + /// store at least `minimumCapacity` elements. + /// + /// - Parameter minimumCapacity: The minimum number of elements the buffer + /// needs to be able to hold on return. + /// + /// - Parameter linearGrowth: If true, then don't use an exponential growth + /// factor when reallocating the buffer -- just allocate space for the + /// requested number of elements + @inlinable + @inline(__always) + internal mutating func _ensureUnique( + minimumCapacity: Int, + linearGrowth: Bool = false + ) { + let unique = _isUnique() + if _slowPath(_capacity < minimumCapacity || !unique) { + __ensureUnique( + isUnique: unique, + minimumCapacity: minimumCapacity, + linearGrowth: linearGrowth) + } + } + + @inlinable + @inline(never) + internal mutating func __ensureUnique( + isUnique: Bool, + minimumCapacity: Int, + linearGrowth: Bool + ) { + if _capacity >= minimumCapacity { + assert(!isUnique) + self = self._makeUniqueCopy() + return + } + + let c = _growCapacity(to: minimumCapacity, linearly: linearGrowth) + if isUnique { + self._storage.value.resize(to: c) + } else { + self = self._makeUniqueCopy(capacity: c) + } } } + +extension Deque { + @inlinable + @inline(__always) + internal func _isIdentical(to other: Self) -> Bool { + self._storage.isIdentical(to: other._storage) + } +} + diff --git a/Sources/DequeModule/DynamicDeque.swift b/Sources/DequeModule/DynamicDeque.swift new file mode 100644 index 000000000..beb8b99c7 --- /dev/null +++ b/Sources/DequeModule/DynamicDeque.swift @@ -0,0 +1,161 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +#if !COLLECTIONS_SINGLE_MODULE +import InternalCollectionsUtilities +import Future +#endif + +@frozen +public struct DynamicDeque: ~Copyable { + @usableFromInline + internal var _storage: RigidDeque + + @inlinable + public init() { + _storage = .init(capacity: 0) + } + + @inlinable + public init(capacity: Int) { + _storage = .init(capacity: capacity) + } +} + +extension DynamicDeque: @unchecked Sendable where Element: Sendable & ~Copyable {} + +extension DynamicDeque: RandomAccessContainer where Element: ~Copyable { + @available(SwiftStdlib 6.2, *) + @lifetime(borrow self) + public func nextSpan(after index: inout Int) -> Span { + _storage.nextSpan(after: &index) + } + + @available(SwiftStdlib 6.2, *) + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + _storage.previousSpan(before: &index) + } +} + +extension DynamicDeque where Element: ~Copyable { + public typealias Index = Int + + @inlinable + public var isEmpty: Bool { _storage.isEmpty } + + @inlinable + public var count: Int { _storage.count } + + @inlinable + public var startIndex: Int { _storage.startIndex } + + @inlinable + public var endIndex: Int { _storage.endIndex } + + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Future.Borrow { + _storage.borrowElement(at: index) + } +} + +extension DynamicDeque where Element: ~Copyable { + @inlinable + internal var _capacity: Int { _storage.capacity } + + @inlinable + internal var _freeCapacity: Int { _storage.freeCapacity } + + @inlinable + internal var _isFull: Bool { _storage.isFull } + + @inlinable + internal mutating func _grow(to minimumCapacity: Int) { + guard minimumCapacity > _capacity else { return } + let c = Swift.max(minimumCapacity, 7 * _capacity / 4) + _storage.resize(to: c) + } + + @inlinable + internal mutating func _ensureFreeCapacity(_ freeCapacity: Int) { + _grow(to: count + freeCapacity) + } +} + +extension DynamicDeque where Element: ~Copyable { + @inlinable + public mutating func append(_ newElement: consuming Element) { + _ensureFreeCapacity(1) + _storage.append(newElement) + } + + @inlinable + public mutating func prepend(_ newElement: consuming Element) { + _ensureFreeCapacity(1) + _storage.prepend(newElement) + } + + @inlinable + public mutating func insert(_ newElement: consuming Element, at index: Int) { + _ensureFreeCapacity(1) + _storage.insert(newElement, at: index) + } +} + +extension DynamicDeque where Element: ~Copyable { + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Element { + _storage.remove(at: index) + } + + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + _storage.removeSubrange(bounds) + } + + @inlinable + @discardableResult + public mutating func removeFirst() -> Element { + _storage.removeFirst() + } + + @inlinable + @discardableResult + public mutating func removeLast() -> Element { + _storage.removeLast() + } + + @inlinable + public mutating func removeFirst(_ n: Int) { + _storage.removeFirst(n) + } + + @inlinable + public mutating func removeLast(_ n: Int) { + _storage.removeLast(n) + } + + @inlinable + public mutating func removeAll() { + _storage.removeAll() + } + + @inlinable + public mutating func popFirst() -> Element? { + _storage.popFirst() + } + + @inlinable + public mutating func popLast() -> Element? { + _storage.popLast() + } +} diff --git a/Sources/DequeModule/RigidDeque+Testing.swift b/Sources/DequeModule/RigidDeque+Testing.swift new file mode 100644 index 000000000..027cc2d2f --- /dev/null +++ b/Sources/DequeModule/RigidDeque+Testing.swift @@ -0,0 +1,51 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 - 2025 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 +// +//===----------------------------------------------------------------------===// + +#if !COLLECTIONS_SINGLE_MODULE +import InternalCollectionsUtilities +#endif + +extension RigidDeque where Element: ~Copyable { + /// True if consistency checking is enabled in the implementation of this + /// type, false otherwise. + /// + /// Documented performance promises are null and void when this property + /// returns true -- for example, operations that are documented to take + /// O(1) time might take O(*n*) time, or worse. + public static var _isConsistencyCheckingEnabled: Bool { + _isCollectionsInternalCheckingEnabled + } + + /// The number of the storage slot in this deque that holds the first element. + /// (Or would hold it after an insertion in case the deque is currently + /// empty.) + /// + /// This property isn't intended to be used outside of `RigidDeque`'s own test + /// target. + package var _unstableStartSlot: Int { + _handle.startSlot.position + } + + /// Constructs a deque instance of the specified contents and layout. Exposed + /// as public to allow exhaustive input/output tests for `RigidDeque`'s members. + /// This isn't intended to be used outside of `RigidDeque`'s own test target. + package init( + _capacity capacity: Int, + startSlot: Int, + count: Int, + generator: (Int) -> Element + ) { + let startSlot = _Slot(at: startSlot) + self.init(_handle: .allocate(capacity: capacity, startSlot: startSlot, count: count, generator: generator)) + assert(self._unstableStartSlot == startSlot.position) + assert(self.count == count) + } +} diff --git a/Sources/DequeModule/RigidDeque.swift b/Sources/DequeModule/RigidDeque.swift new file mode 100644 index 000000000..31b5d8a61 --- /dev/null +++ b/Sources/DequeModule/RigidDeque.swift @@ -0,0 +1,267 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +#if !COLLECTIONS_SINGLE_MODULE +import InternalCollectionsUtilities +import Future +#endif + +@frozen +public struct RigidDeque: ~Copyable { + @usableFromInline + internal typealias _Slot = _DequeSlot + + @usableFromInline + internal typealias _UnsafeHandle = _UnsafeDequeHandle + + @usableFromInline + internal var _handle: _UnsafeHandle + + @inlinable + internal init(_handle: consuming _UnsafeHandle) { + self._handle = _handle + } + + @inlinable + public init(capacity: Int) { + self.init(_handle: .allocate(capacity: capacity)) + } + + deinit { + _handle.dispose() + } +} + +extension RigidDeque: @unchecked Sendable where Element: Sendable & ~Copyable {} + +extension RigidDeque where Element: ~Copyable { +#if COLLECTIONS_INTERNAL_CHECKS + @usableFromInline @inline(never) @_effects(releasenone) + internal func _checkInvariants() { + _handle._checkInvariants() + } +#else + @inlinable @inline(__always) + internal func _checkInvariants() {} +#endif // COLLECTIONS_INTERNAL_CHECKS +} + +extension RigidDeque where Element: ~Copyable { + @usableFromInline + internal var description: String { + _handle.description + } +} + +@available(SwiftStdlib 6.2, *) +extension RigidDeque where Element: ~Copyable { + @inlinable + @lifetime(borrow self) + internal func _span(over slots: Range<_DequeSlot>) -> Span { + let span = Span(_unsafeElements: _handle.buffer(for: slots)) + return _overrideLifetime(span, borrowing: self) + } + + @inlinable + @lifetime(&self) + internal mutating func _mutableSpan(over slots: Range<_DequeSlot>) -> MutableSpan { + let span = MutableSpan(_unsafeElements: _handle.mutableBuffer(for: slots)) + return _overrideLifetime(span, mutating: &self) + } +} + +@available(SwiftStdlib 6.2, *) +extension RigidDeque: RandomAccessContainer, MutableContainer where Element: ~Copyable { + @inlinable + @lifetime(borrow self) + public func nextSpan(after index: inout Int) -> Span { + let slots = _handle.slotRange(following: &index) + return _span(over: slots) + } + + @inlinable + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + let slots = _handle.slotRange(preceding: &index) + return _span(over: slots) + } + + @inlinable + @lifetime(&self) + public mutating func nextMutableSpan( + after index: inout Int + ) -> MutableSpan { + let slots = _handle.slotRange(following: &index) + return _mutableSpan(over: slots) + } + + public mutating func swapAt(_ i: Int, _ j: Int) { + precondition(i >= 0 && i < count, "Index out of bounds") + precondition(j >= 0 && j < count, "Index out of bounds") + let slot1 = _handle.slot(forOffset: i) + let slot2 = _handle.slot(forOffset: j) + unsafe _handle.mutableBuffer.swapAt(slot1.position, slot2.position) + } +} + +extension RigidDeque where Element: ~Copyable { + public typealias Index = Int + + @inlinable + public var isEmpty: Bool { _handle.count == 0 } + + @inlinable + public var count: Int { _handle.count } + + @inlinable + public var startIndex: Int { 0 } + + @inlinable + public var endIndex: Int { _handle.count } + + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + precondition(index >= 0 && index < count, "Index out of bounds") + let slot = _handle.slot(forOffset: index) + return Borrow(unsafeAddress: _handle.ptr(at: slot), borrowing: self) + } + + @lifetime(&self) + public mutating func mutateElement(at index: Int) -> Inout { + precondition(index >= 0 && index < count, "Index out of bounds") + let slot = _handle.slot(forOffset: index) + return Inout(unsafeAddress: _handle.mutablePtr(at: slot), mutating: &self) + } + + @inlinable + public subscript(position: Int) -> Element { + @inline(__always) + _read { + yield _handle[offset: position] + } + @inline(__always) + _modify { + yield &_handle[offset: position] + } + } +} + +extension RigidDeque where Element: ~Copyable { + @inlinable + public var capacity: Int { _handle.capacity } + + @inlinable + public var freeCapacity: Int { capacity - count } + + @inlinable + public var isFull: Bool { count == capacity } + + @inlinable + public mutating func resize(to newCapacity: Int) { + _handle.reallocate(capacity: newCapacity) + } +} + +extension RigidDeque where Element: ~Copyable { + @inlinable + public mutating func append(_ newElement: consuming Element) { + precondition(!isFull, "RigidDeque is full") + _handle.uncheckedAppend(newElement) + } + + @inlinable + public mutating func prepend(_ newElement: consuming Element) { + precondition(!isFull, "RigidDeque is full") + _handle.uncheckedPrepend(newElement) + } + + @inlinable + public mutating func insert(_ newElement: consuming Element, at index: Int) { + precondition(!isFull, "RigidDeque is full") + precondition(index >= 0 && index <= count, + "Can't insert element at invalid index") + _handle.uncheckedInsert(newElement, at: index) + } +} + +extension RigidDeque where Element: ~Copyable { + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Element { + precondition(index >= 0 && index < count, + "Can't remove element at invalid index") + return _handle.uncheckedRemove(at: index) + } + + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + precondition(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Index range out of bounds") + _handle.uncheckedRemove(offsets: bounds) + } + + @inlinable + @discardableResult + public mutating func removeFirst() -> Element { + precondition(!isEmpty, "Cannot remove first element of an empty RigidDeque") + return _handle.uncheckedRemoveFirst() + } + + @inlinable + @discardableResult + public mutating func removeLast() -> Element { + precondition(!isEmpty, "Cannot remove last element of an empty RigidDeque") + return _handle.uncheckedRemoveLast() + } + + @inlinable + public mutating func removeFirst(_ n: Int) { + precondition(n >= 0, "Can't remove a negative number of elements") + precondition(n <= count, "Can't remove more elements than there are in a RigidDeque") + _handle.uncheckedRemoveFirst(n) + } + + @inlinable + public mutating func removeLast(_ n: Int) { + precondition(n >= 0, "Can't remove a negative number of elements") + precondition(n <= count, "Can't remove more elements than there are in a RigidDeque") + _handle.uncheckedRemoveLast(n) + } + + @inlinable + public mutating func removeAll() { + _handle.uncheckedRemoveAll() + } + + @inlinable + public mutating func popFirst() -> Element? { + guard !isEmpty else { return nil } + return _handle.uncheckedRemoveFirst() + } + + @inlinable + public mutating func popLast() -> Element? { + guard !isEmpty else { return nil } + return _handle.uncheckedRemoveLast() + } +} + +extension RigidDeque { + @inlinable + internal func _copy() -> Self { + RigidDeque(_handle: _handle.allocateCopy()) + } + + @inlinable + internal func _copy(capacity: Int) -> Self { + RigidDeque(_handle: _handle.allocateCopy(capacity: capacity)) + } +} diff --git a/Sources/DequeModule/_DequeBuffer.swift b/Sources/DequeModule/_DequeBuffer.swift deleted file mode 100644 index cb17d1dce..000000000 --- a/Sources/DequeModule/_DequeBuffer.swift +++ /dev/null @@ -1,49 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2021 - 2024 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 -// -//===----------------------------------------------------------------------===// - -@_fixed_layout -@usableFromInline -internal final class _DequeBuffer: ManagedBuffer<_DequeBufferHeader, Element> { - @inlinable - deinit { - self.withUnsafeMutablePointers { header, elements in - header.pointee._checkInvariants() - - let capacity = header.pointee.capacity - let count = header.pointee.count - let startSlot = header.pointee.startSlot - - if startSlot.position + count <= capacity { - (elements + startSlot.position).deinitialize(count: count) - } else { - let firstRegion = capacity - startSlot.position - (elements + startSlot.position).deinitialize(count: firstRegion) - elements.deinitialize(count: count - firstRegion) - } - } - } -} - -extension _DequeBuffer: CustomStringConvertible { - @usableFromInline - internal var description: String { - withUnsafeMutablePointerToHeader { "_DequeStorage<\(Element.self)>\($0.pointee)" } - } -} - -/// The type-punned empty singleton storage instance. -@usableFromInline -internal let _emptyDequeStorage = _DequeBuffer.create( - minimumCapacity: 0, - makingHeaderWith: { _ in - _DequeBufferHeader(capacity: 0, count: 0, startSlot: .init(at: 0)) - }) - diff --git a/Sources/DequeModule/_DequeBufferHeader.swift b/Sources/DequeModule/_DequeBufferHeader.swift deleted file mode 100644 index c45f756dd..000000000 --- a/Sources/DequeModule/_DequeBufferHeader.swift +++ /dev/null @@ -1,49 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2021 - 2024 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 -// -//===----------------------------------------------------------------------===// - -@usableFromInline -internal struct _DequeBufferHeader { - @usableFromInline - var capacity: Int - - @usableFromInline - var count: Int - - @usableFromInline - var startSlot: _DequeSlot - - @usableFromInline - init(capacity: Int, count: Int, startSlot: _DequeSlot) { - self.capacity = capacity - self.count = count - self.startSlot = startSlot - _checkInvariants() - } - - #if COLLECTIONS_INTERNAL_CHECKS - @usableFromInline @inline(never) @_effects(releasenone) - internal func _checkInvariants() { - precondition(capacity >= 0) - precondition(count >= 0 && count <= capacity) - precondition(startSlot.position >= 0 && startSlot.position <= capacity) - } - #else - @inlinable @inline(__always) - internal func _checkInvariants() {} - #endif // COLLECTIONS_INTERNAL_CHECKS -} - -extension _DequeBufferHeader: CustomStringConvertible { - @usableFromInline - internal var description: String { - "(capacity: \(capacity), count: \(count), startSlot: \(startSlot))" - } -} diff --git a/Sources/DequeModule/_DequeSlot.swift b/Sources/DequeModule/_DequeSlot.swift index 2d281cb94..d1d8d5f4f 100644 --- a/Sources/DequeModule/_DequeSlot.swift +++ b/Sources/DequeModule/_DequeSlot.swift @@ -69,4 +69,10 @@ extension Range where Bound == _DequeSlot { @inlinable @inline(__always) internal var _count: Int { upperBound.position - lowerBound.position } + + @inlinable + @inline(__always) + internal var _offsets: Range { + Range(uncheckedBounds: (lowerBound.position, upperBound.position)) + } } diff --git a/Sources/DequeModule/Deque._UnsafeHandle.swift b/Sources/DequeModule/_UnsafeDequeHandle.swift similarity index 64% rename from Sources/DequeModule/Deque._UnsafeHandle.swift rename to Sources/DequeModule/_UnsafeDequeHandle.swift index 8302b635f..b63631e27 100644 --- a/Sources/DequeModule/Deque._UnsafeHandle.swift +++ b/Sources/DequeModule/_UnsafeDequeHandle.swift @@ -9,106 +9,131 @@ // //===----------------------------------------------------------------------===// -extension Deque { - @frozen +#if !COLLECTIONS_SINGLE_MODULE +import InternalCollectionsUtilities +#endif + +@frozen +@usableFromInline +internal struct _UnsafeDequeHandle: ~Copyable { @usableFromInline - internal struct _UnsafeHandle { - @usableFromInline - let _header: UnsafeMutablePointer<_DequeBufferHeader> - @usableFromInline - let _elements: UnsafeMutablePointer - #if DEBUG - @usableFromInline - let _isMutable: Bool - #endif - - @inlinable - @inline(__always) - init( - header: UnsafeMutablePointer<_DequeBufferHeader>, - elements: UnsafeMutablePointer, - isMutable: Bool - ) { - self._header = header - self._elements = elements - #if DEBUG - self._isMutable = isMutable - #endif - } - } -} + internal typealias Slot = _DequeSlot -extension Deque._UnsafeHandle { - @inlinable - @inline(__always) - func assertMutable() { - #if DEBUG - assert(_isMutable) - #endif - } -} + @usableFromInline + internal var _buffer: UnsafeMutableBufferPointer -extension Deque._UnsafeHandle { @usableFromInline - internal typealias Slot = _DequeSlot + internal var count: Int + + @usableFromInline + internal var startSlot: Slot @inlinable - @inline(__always) - var header: _DequeBufferHeader { - _header.pointee + internal init( + buffer: UnsafeMutableBufferPointer, + count: Int, + startSlot: _DequeSlot + ) { + self._buffer = buffer + self.count = count + self.startSlot = startSlot } @inlinable - @inline(__always) - var capacity: Int { - _header.pointee.capacity + internal consuming func dispose() { + _checkInvariants() + self.mutableSegments().deinitialize() + _buffer.deallocate() } +} +extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable - @inline(__always) - var count: Int { - get { _header.pointee.count } - nonmutating set { _header.pointee.count = newValue } + internal static var empty: Self { + Self(buffer: ._empty, count: 0, startSlot: .zero) } @inlinable - @inline(__always) - var startSlot: Slot { - get { _header.pointee.startSlot } - nonmutating set { _header.pointee.startSlot = newValue } + internal static func allocate( + capacity: Int + ) -> Self { + Self( + buffer: capacity > 0 ? .allocate(capacity: capacity) : ._empty, + count: 0, + startSlot: .zero) } +} - @inlinable - @inline(__always) - func ptr(at slot: Slot) -> UnsafeMutablePointer { - assert(slot.position >= 0 && slot.position <= capacity) - return _elements + slot.position +extension _UnsafeDequeHandle where Element: ~Copyable { +#if COLLECTIONS_INTERNAL_CHECKS + @usableFromInline @inline(never) @_effects(releasenone) + internal func _checkInvariants() { + precondition(capacity >= 0) + precondition(count >= 0 && count <= capacity) + precondition(startSlot.position >= 0 && startSlot.position <= capacity) + } +#else + @inlinable @inline(__always) + internal func _checkInvariants() {} +#endif // COLLECTIONS_INTERNAL_CHECKS +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @usableFromInline + internal var description: String { + "(capacity: \(capacity), count: \(count), start: \(startSlot))" } } -extension Deque._UnsafeHandle { - @inlinable - @inline(__always) - var mutableBuffer: UnsafeMutableBufferPointer { - assertMutable() - return .init(start: _elements, count: _header.pointee.capacity) +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable @inline(__always) + internal var _baseAddress: UnsafeMutablePointer { + _buffer.baseAddress.unsafelyUnwrapped } @inlinable - internal func buffer(for range: Range) -> UnsafeBufferPointer { - assert(range.upperBound.position <= capacity) - return .init(start: _elements + range.lowerBound.position, count: range._count) + internal var capacity: Int { + _buffer.count } +} - @inlinable - @inline(__always) - internal func mutableBuffer(for range: Range) -> UnsafeMutableBufferPointer { - assertMutable() - return .init(mutating: buffer(for: range)) +// MARK: Test initializer + +extension _UnsafeDequeHandle where Element: ~Copyable { + internal static func allocate( + capacity: Int, + startSlot: Slot, + count: Int, + generator: (Int) -> Element + ) -> Self { + precondition(capacity >= 0) + precondition( + startSlot.position >= 0 && + (startSlot.position < capacity || (capacity == 0 && startSlot.position == 0))) + precondition(count <= capacity) + + var h = Self.allocate(capacity: capacity) + h.count = count + h.startSlot = startSlot + if h.count > 0 { + let segments = h.mutableSegments() + let c = segments.first.count + for i in 0 ..< c { + segments.first.initializeElement(at: i, to: generator(i)) + } + if let second = segments.second { + for i in c ..< h.count { + second.initializeElement(at: i - c, to: generator(i)) + } + } + } + return h } } -extension Deque._UnsafeHandle { +// MARK: Slots + +extension _UnsafeDequeHandle where Element: ~Copyable { /// The slot immediately following the last valid one. (`endSlot` refers to /// the valid slot corresponding to `endIndex`, which is a different thing /// entirely.) @@ -170,9 +195,103 @@ extension Deque._UnsafeHandle { } } -extension Deque._UnsafeHandle { +// MARK: Element Access + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable @inline(__always) + internal func ptr(at slot: Slot) -> UnsafePointer { + assert(slot.position >= 0 && slot.position <= capacity) + return UnsafePointer(_baseAddress + slot.position) + } + + @inlinable @inline(__always) + internal mutating func mutablePtr( + at slot: Slot + ) -> UnsafeMutablePointer { + assert(slot.position >= 0 && slot.position <= capacity) + return _baseAddress + slot.position + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + internal subscript(offset offset: Int) -> Element { + @inline(__always) + _read { + precondition(offset >= 0 && offset < count, "Index out of bounds") + let slot = slot(forOffset: offset) + yield ptr(at: slot).pointee + } + @inline(__always) + _modify { + precondition(offset >= 0 && offset < count, "Index out of bounds") + let slot = slot(forOffset: offset) + yield &mutablePtr(at: slot).pointee + } + } +} + +// MARK: Access to contiguous regions + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable @inline(__always) + internal var mutableBuffer: UnsafeMutableBufferPointer { + mutating get { + _buffer + } + } + + @inlinable + internal func buffer(for range: Range) -> UnsafeBufferPointer { + assert(range.upperBound.position <= capacity) + return .init(_buffer._extracting(unchecked: range._offsets)) + } + + @inlinable @inline(__always) + internal mutating func mutableBuffer(for range: Range) -> UnsafeMutableBufferPointer { + assert(range.upperBound.position <= capacity) + return _buffer._extracting(unchecked: range._offsets) + } +} + +extension _UnsafeDequeHandle { + @inlinable + @discardableResult + internal mutating func initialize( + at start: Slot, + from source: UnsafeBufferPointer + ) -> Slot { + assert(start.position + source.count <= capacity) + guard source.count > 0 else { return start } + mutablePtr(at: start).initialize(from: source.baseAddress!, count: source.count) + return Slot(at: start.position + source.count) + } +} + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + @inline(__always) + @discardableResult + internal mutating func moveInitialize( + at start: Slot, + from source: UnsafeMutableBufferPointer + ) -> Slot { + assert(start.position + source.count <= capacity) + guard source.count > 0 else { return start } + mutablePtr(at: start) + .moveInitialize(from: source.baseAddress!, count: source.count) + return Slot(at: start.position + source.count) + } +} + +// MARK: Access to Segments + +extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable - internal func segments() -> _UnsafeWrappedBuffer { + internal func segments() -> _UnsafeDequeSegments { + guard _buffer.baseAddress != nil else { + return .init(._empty) + } let wrap = capacity - startSlot.position if count <= wrap { return .init(start: ptr(at: startSlot), count: count) @@ -184,8 +303,11 @@ extension Deque._UnsafeHandle { @inlinable internal func segments( forOffsets offsets: Range - ) -> _UnsafeWrappedBuffer { + ) -> _UnsafeDequeSegments { assert(offsets.lowerBound >= 0 && offsets.upperBound <= count) + guard _buffer.baseAddress != nil else { + return .init(._empty) + } let lower = slot(forOffset: offsets.lowerBound) let upper = slot(forOffset: offsets.upperBound) if offsets.count == 0 || lower < upper { @@ -197,201 +319,157 @@ extension Deque._UnsafeHandle { @inlinable @inline(__always) - internal func mutableSegments() -> _UnsafeMutableWrappedBuffer { - assertMutable() - return .init(mutating: segments()) + internal mutating func mutableSegments() -> _UnsafeMutableDequeSegments { + .init(mutating: segments()) } @inlinable @inline(__always) - internal func mutableSegments( + internal mutating func mutableSegments( forOffsets range: Range - ) -> _UnsafeMutableWrappedBuffer { - assertMutable() - return .init(mutating: segments(forOffsets: range)) + ) -> _UnsafeMutableDequeSegments { + .init(mutating: segments(forOffsets: range)) + } + + @inlinable + internal mutating func mutableSegments( + between start: Slot, + and end: Slot + ) -> _UnsafeMutableDequeSegments { + assert(start.position <= capacity) + assert(end.position <= capacity) + if start < end { + return .init( + start: mutablePtr(at: start), + count: end.position - start.position) + } + return .init( + first: mutablePtr(at: start), count: capacity - start.position, + second: mutablePtr(at: .zero), count: end.position) } } -extension Deque._UnsafeHandle { +extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable - internal func availableSegments() -> _UnsafeMutableWrappedBuffer { - assertMutable() + internal mutating func availableSegments() -> _UnsafeMutableDequeSegments { + guard _buffer.baseAddress != nil else { + return .init(._empty) + } let endSlot = self.endSlot - guard count < capacity else { return .init(start: ptr(at: endSlot), count: 0) } + guard count < capacity else { return .init(start: mutablePtr(at: endSlot), count: 0) } if endSlot < startSlot { return .init(mutableBuffer(for: endSlot ..< startSlot)) } return .init(mutableBuffer(for: endSlot ..< limSlot), mutableBuffer(for: .zero ..< startSlot)) } } +// MARK: Wholesale Copying and Reallocation - -extension Deque._UnsafeHandle { +extension _UnsafeDequeHandle { + /// Copy elements in `handle` into a newly allocated handle without changing its + /// capacity or layout. @inlinable - @discardableResult - func initialize( - at start: Slot, - from source: UnsafeBufferPointer - ) -> Slot { - assert(start.position + source.count <= capacity) - guard source.count > 0 else { return start } - ptr(at: start).initialize(from: source.baseAddress!, count: source.count) - return Slot(at: start.position + source.count) + internal borrowing func allocateCopy() -> Self { + var result: _UnsafeDequeHandle = .allocate(capacity: self.capacity) + result.count = self.count + result.startSlot = self.startSlot + let src = self.segments() + result.initialize(at: self.startSlot, from: src.first) + if let second = src.second { + result.initialize(at: .zero, from: second) + } + return result } + /// Copy elements in `handle` into a newly allocated handle with the specified + /// minimum capacity. This operation does not preserve layout. @inlinable - @inline(__always) - @discardableResult - func moveInitialize( - at start: Slot, - from source: UnsafeMutableBufferPointer - ) -> Slot { - assert(start.position + source.count <= capacity) - guard source.count > 0 else { return start } - ptr(at: start).moveInitialize(from: source.baseAddress!, count: source.count) - return Slot(at: start.position + source.count) + internal borrowing func allocateCopy(capacity: Int) -> Self { + precondition(capacity >= self.count) + var result: _UnsafeDequeHandle = .allocate(capacity: capacity) + result.count = self.count + let src = self.segments() + let next = result.initialize(at: .zero, from: src.first) + if let second = src.second { + result.initialize(at: next, from: second) + } + return result } +} +extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable - @inline(__always) - @discardableResult - public func move( - from source: Slot, - to target: Slot, - count: Int - ) -> (source: Slot, target: Slot) { - assert(count >= 0) - assert(source.position + count <= self.capacity) - assert(target.position + count <= self.capacity) - guard count > 0 else { return (source, target) } - ptr(at: target).moveInitialize(from: ptr(at: source), count: count) - return (slot(source, offsetBy: count), slot(target, offsetBy: count)) + internal mutating func reallocate(capacity newCapacity: Int) { + precondition(newCapacity >= count) + guard newCapacity != capacity else { return } + + var new = _UnsafeDequeHandle.allocate(capacity: newCapacity) + let source = self.mutableSegments() + let next = new.moveInitialize(at: .zero, from: source.first) + if let second = source.second { + new.moveInitialize(at: next, from: second) + } + _buffer.deallocate() + _buffer = new._buffer + startSlot = .zero } } +// MARK: Iteration - -extension Deque._UnsafeHandle { - /// Copy elements into a new storage instance without changing capacity or - /// layout. +extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable - internal func copyElements() -> Deque._Storage { - let object = _DequeBuffer.create( - minimumCapacity: capacity, - makingHeaderWith: { _ in header }) - let result = Deque._Storage(_buffer: ManagedBufferPointer(unsafeBufferObject: object)) - guard self.count > 0 else { return result } - result.update { target in - let source = self.segments() - target.initialize(at: startSlot, from: source.first) - if let second = source.second { - target.initialize(at: .zero, from: second) - } - } - return result - } - - /// Copy elements into a new storage instance with the specified minimum - /// capacity. - @inlinable - internal func copyElements(minimumCapacity: Int) -> Deque._Storage { - assert(minimumCapacity >= count) - let object = _DequeBuffer.create( - minimumCapacity: minimumCapacity, - makingHeaderWith: { - #if os(OpenBSD) - let capacity = minimumCapacity - #else - let capacity = $0.capacity - #endif - return _DequeBufferHeader( - capacity: capacity, - count: count, - startSlot: .zero) - }) - let result = Deque._Storage(_buffer: ManagedBufferPointer(unsafeBufferObject: object)) - guard count > 0 else { return result } - result.update { target in - assert(target.count == count && target.startSlot.position == 0) - let source = self.segments() - let next = target.initialize(at: .zero, from: source.first) - if let second = source.second { - target.initialize(at: next, from: second) - } + internal func slotRange(following offset: inout Int) -> Range { + precondition(offset >= 0 && offset <= count, "Index out of bounds") + guard _buffer.baseAddress != nil else { + return Range(uncheckedBounds: (Slot.zero, Slot.zero)) } - return result - } + let wrapOffset = Swift.min(capacity - startSlot.position, count) - /// Move elements into a new storage instance with the specified minimum - /// capacity. Existing indices in `self` won't necessarily be valid in the - /// result. `self` is left empty. - @inlinable - internal func moveElements(minimumCapacity: Int) -> Deque._Storage { - assertMutable() - let count = self.count - assert(minimumCapacity >= count) - let object = _DequeBuffer.create( - minimumCapacity: minimumCapacity, - makingHeaderWith: { - #if os(OpenBSD) - let capacity = minimumCapacity - #else - let capacity = $0.capacity - #endif - return _DequeBufferHeader( - capacity: capacity, - count: count, - startSlot: .zero) - }) - let result = Deque._Storage(_buffer: ManagedBufferPointer(unsafeBufferObject: object)) - guard count > 0 else { return result } - result.update { target in - let source = self.mutableSegments() - let next = target.moveInitialize(at: .zero, from: source.first) - if let second = source.second { - target.moveInitialize(at: next, from: second) - } + if offset < wrapOffset { + defer { offset += wrapOffset - offset } + return Range( + uncheckedBounds: (startSlot.advanced(by: offset), startSlot.advanced(by: wrapOffset))) } - self.count = 0 - return result + let lowerSlot = Slot.zero.advanced(by: offset - wrapOffset) + let upperSlot = lowerSlot.advanced(by: count - wrapOffset) + defer { offset += count - offset } + return Range(uncheckedBounds: (lower: lowerSlot, upper: upperSlot)) } -} -extension Deque._UnsafeHandle { @inlinable - internal func withUnsafeSegment( - startingAt start: Int, - maximumCount: Int?, - _ body: (UnsafeBufferPointer) throws -> R - ) rethrows -> (end: Int, result: R) { - assert(start <= count) - guard start < count else { - return try (count, body(UnsafeBufferPointer(start: nil, count: 0))) + internal func slotRange(preceding offset: inout Int) -> Range { + precondition(offset >= 0 && offset <= count, "Index out of bounds") + guard _buffer.baseAddress != nil else { + return Range(uncheckedBounds: (Slot.zero, Slot.zero)) } - let endSlot = self.endSlot + let wrapOffset = Swift.min(capacity - startSlot.position, count) - let segmentStart = self.slot(forOffset: start) - let segmentEnd = segmentStart < endSlot ? endSlot : limSlot - let count = Swift.min(maximumCount ?? Int.max, segmentEnd.position - segmentStart.position) - let result = try body(UnsafeBufferPointer(start: ptr(at: segmentStart), count: count)) - return (start + count, result) + if offset <= wrapOffset { + defer { offset = 0 } + return Range( + uncheckedBounds: (startSlot, startSlot.advanced(by: offset))) + } + let lowerSlot = Slot.zero + let upperSlot = lowerSlot.advanced(by: offset - wrapOffset) + defer { offset = wrapOffset } + return Range(uncheckedBounds: (lower: lowerSlot, upper: upperSlot)) } } // MARK: Replacement -extension Deque._UnsafeHandle { +extension _UnsafeDequeHandle { /// Replace the elements in `range` with `newElements`. The deque's count must /// not change as a result of calling this function. /// /// This function does not validate its input arguments in release builds. Nor /// does it ensure that the storage buffer is uniquely referenced. @inlinable - internal func uncheckedReplaceInPlace( + internal mutating func uncheckedReplaceInPlace( inOffsets range: Range, with newElements: C ) where C.Element == Element { - assertMutable() assert(range.upperBound <= count) assert(newElements.count == range.count) guard !range.isEmpty else { return } @@ -402,28 +480,28 @@ extension Deque._UnsafeHandle { // MARK: Appending -extension Deque._UnsafeHandle { +extension _UnsafeDequeHandle where Element: ~Copyable { /// Append `element` to this buffer. The buffer must have enough free capacity /// to insert one new element. /// /// This function does not validate its input arguments in release builds. Nor /// does it ensure that the storage buffer is uniquely referenced. @inlinable - internal func uncheckedAppend(_ element: Element) { - assertMutable() + internal mutating func uncheckedAppend(_ element: consuming Element) { assert(count < capacity) - ptr(at: endSlot).initialize(to: element) + mutablePtr(at: endSlot).initialize(to: element) count += 1 } +} +extension _UnsafeDequeHandle { /// Append the contents of `source` to this buffer. The buffer must have /// enough free capacity to insert the new elements. /// /// This function does not validate its input arguments in release builds. Nor /// does it ensure that the storage buffer is uniquely referenced. @inlinable - internal func uncheckedAppend(contentsOf source: UnsafeBufferPointer) { - assertMutable() + internal mutating func uncheckedAppend(contentsOf source: UnsafeBufferPointer) { assert(count + source.count <= capacity) guard source.count > 0 else { return } let c = self.count @@ -435,25 +513,25 @@ extension Deque._UnsafeHandle { // MARK: Prepending -extension Deque._UnsafeHandle { +extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable - internal func uncheckedPrepend(_ element: Element) { - assertMutable() + internal mutating func uncheckedPrepend(_ element: consuming Element) { assert(count < capacity) let slot = self.slot(before: startSlot) - ptr(at: slot).initialize(to: element) + mutablePtr(at: slot).initialize(to: element) startSlot = slot count += 1 } +} +extension _UnsafeDequeHandle { /// Prepend the contents of `source` to this buffer. The buffer must have /// enough free capacity to insert the new elements. /// /// This function does not validate its input arguments in release builds. Nor /// does it ensure that the storage buffer is uniquely referenced. @inlinable - internal func uncheckedPrepend(contentsOf source: UnsafeBufferPointer) { - assertMutable() + internal mutating func uncheckedPrepend(contentsOf source: UnsafeBufferPointer) { assert(count + source.count <= capacity) guard source.count > 0 else { return } let oldStart = startSlot @@ -461,52 +539,29 @@ extension Deque._UnsafeHandle { startSlot = newStart count += source.count - let gap = mutableWrappedBuffer(between: newStart, and: oldStart) + let gap = mutableSegments(between: newStart, and: oldStart) gap.initialize(from: source) } } -// MARK: Insertion +// MARK: Opening and Closing Gaps -extension Deque._UnsafeHandle { - /// Insert all elements from `newElements` into this deque, starting at - /// `offset`. - /// - /// This function does not validate its input arguments in release builds. Nor - /// does it ensure that the storage buffer is uniquely referenced. - /// - /// - Parameter newElements: The elements to insert. - /// - Parameter newCount: Must be equal to `newElements.count`. Used to - /// prevent calling `count` more than once. - /// - Parameter offset: The desired offset from the start at which to place - /// the first element. +extension _UnsafeDequeHandle where Element: ~Copyable { @inlinable - internal func uncheckedInsert( - contentsOf newElements: __owned C, - count newCount: Int, - atOffset offset: Int - ) where C.Element == Element { - assertMutable() - assert(offset <= count) - assert(newElements.count == newCount) - guard newCount > 0 else { return } - let gap = openGap(ofSize: newCount, atOffset: offset) - gap.initialize(from: newElements) - } - - @inlinable - internal func mutableWrappedBuffer( - between start: Slot, - and end: Slot - ) -> _UnsafeMutableWrappedBuffer { - assert(start.position <= capacity) - assert(end.position <= capacity) - if start < end { - return .init(start: ptr(at: start), count: end.position - start.position) - } - return .init( - first: ptr(at: start), count: capacity - start.position, - second: ptr(at: .zero), count: end.position) + @inline(__always) + @discardableResult + internal mutating func move( + from source: Slot, + to target: Slot, + count: Int + ) -> (source: Slot, target: Slot) { + assert(count >= 0) + assert(source.position + count <= self.capacity) + assert(target.position + count <= self.capacity) + guard count > 0 else { return (source, target) } + mutablePtr(at: target) + .moveInitialize(from: mutablePtr(at: source), count: count) + return (slot(source, offsetBy: count), slot(target, offsetBy: count)) } /// Slide elements around so that there is a gap of uninitialized slots of @@ -520,11 +575,10 @@ extension Deque._UnsafeHandle { /// - Parameter offset: The offset from the start at which the uninitialized /// slots should start. @inlinable - internal func openGap( + internal mutating func openGap( ofSize gapSize: Int, atOffset offset: Int - ) -> _UnsafeMutableWrappedBuffer { - assertMutable() + ) -> _UnsafeMutableDequeSegments { assert(offset >= 0 && offset <= self.count) assert(self.count + gapSize <= capacity) assert(gapSize > 0) @@ -586,7 +640,7 @@ extension Deque._UnsafeHandle { move(from: gapStart, to: gapEnd, count: tailCount - gapSize - originalEnd.position) } count += gapSize - return mutableWrappedBuffer(between: gapStart, and: gapEnd.orIfZero(capacity)) + return mutableSegments(between: gapStart, and: gapEnd.orIfZero(capacity)) } // Open the gap by sliding elements to the left. @@ -642,91 +696,17 @@ extension Deque._UnsafeHandle { } startSlot = newStart count += gapSize - return mutableWrappedBuffer(between: gapStart, and: gapEnd.orIfZero(capacity)) - } -} - -// MARK: Removal - -extension Deque._UnsafeHandle { - @inlinable - internal func uncheckedRemoveFirst() -> Element { - assertMutable() - assert(count > 0) - let result = ptr(at: startSlot).move() - startSlot = slot(after: startSlot) - count -= 1 - return result - } - - @inlinable - internal func uncheckedRemoveLast() -> Element { - assertMutable() - assert(count > 0) - let slot = self.slot(forOffset: count - 1) - let result = ptr(at: slot).move() - count -= 1 - return result - } - - @inlinable - internal func uncheckedRemoveFirst(_ n: Int) { - assertMutable() - assert(count >= n) - guard n > 0 else { return } - let target = mutableSegments(forOffsets: 0 ..< n) - target.deinitialize() - startSlot = slot(startSlot, offsetBy: n) - count -= n - } - - @inlinable - internal func uncheckedRemoveLast(_ n: Int) { - assertMutable() - assert(count >= n) - guard n > 0 else { return } - let target = mutableSegments(forOffsets: count - n ..< count) - target.deinitialize() - count -= n - } - - /// Remove all elements stored in this instance, deinitializing their storage. - /// - /// This method does not ensure that the storage buffer is uniquely - /// referenced. - @inlinable - internal func uncheckedRemoveAll() { - assertMutable() - guard count > 0 else { return } - let target = mutableSegments() - target.deinitialize() - count = 0 - startSlot = .zero - } - - /// Remove all elements in `bounds`, deinitializing their storage and sliding - /// remaining elements to close the resulting gap. - /// - /// This function does not validate its input arguments in release builds. Nor - /// does it ensure that the storage buffer is uniquely referenced. - @inlinable - internal func uncheckedRemove(offsets bounds: Range) { - assertMutable() - assert(bounds.lowerBound >= 0 && bounds.upperBound <= self.count) - - // Deinitialize elements in `bounds`. - mutableSegments(forOffsets: bounds).deinitialize() - closeGap(offsets: bounds) + return mutableSegments(between: gapStart, and: gapEnd.orIfZero(capacity)) } /// Close the gap of already uninitialized elements in `bounds`, sliding - /// elements outside of the gap to eliminate it. + /// elements outside of the gap to eliminate it, and updating `count` to + /// reflect the removal. /// /// This function does not validate its input arguments in release builds. Nor /// does it ensure that the storage buffer is uniquely referenced. @inlinable - internal func closeGap(offsets bounds: Range) { - assertMutable() + internal mutating func closeGap(offsets bounds: Range) { assert(bounds.lowerBound >= 0 && bounds.upperBound <= self.count) let gapSize = bounds.count guard gapSize > 0 else { return } @@ -831,3 +811,127 @@ extension Deque._UnsafeHandle { } } } + +// MARK: Insertion + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + internal mutating func uncheckedInsert( + _ newElement: consuming Element, at offset: Int + ) { + assert(count < capacity) + if offset == 0 { + uncheckedPrepend(newElement) + return + } + if offset == count { + uncheckedAppend(newElement) + return + } + let gap = openGap(ofSize: 1, atOffset: offset) + assert(gap.first.count == 1) + gap.first.baseAddress!.initialize(to: newElement) + } +} + +extension _UnsafeDequeHandle { + /// Insert all elements from `newElements` into this deque, starting at + /// `offset`. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + /// + /// - Parameter newElements: The elements to insert. + /// - Parameter newCount: Must be equal to `newElements.count`. Used to + /// prevent calling `count` more than once. + /// - Parameter offset: The desired offset from the start at which to place + /// the first element. + @inlinable + internal mutating func uncheckedInsert( + contentsOf newElements: __owned C, + count newCount: Int, + atOffset offset: Int + ) where C.Element == Element { + assert(offset <= count) + assert(newElements.count == newCount) + guard newCount > 0 else { return } + let gap = openGap(ofSize: newCount, atOffset: offset) + gap.initialize(from: newElements) + } +} + +// MARK: Removal + +extension _UnsafeDequeHandle where Element: ~Copyable { + @inlinable + internal mutating func uncheckedRemove(at offset: Int) -> Element { + let slot = self.slot(forOffset: offset) + let result = mutablePtr(at: slot).move() + closeGap(offsets: Range(uncheckedBounds: (offset, offset + 1))) + return result + } + + @inlinable + internal mutating func uncheckedRemoveFirst() -> Element { + assert(count > 0) + let result = mutablePtr(at: startSlot).move() + startSlot = slot(after: startSlot) + count -= 1 + return result + } + + @inlinable + internal mutating func uncheckedRemoveLast() -> Element { + assert(count > 0) + let slot = self.slot(forOffset: count - 1) + let result = mutablePtr(at: slot).move() + count -= 1 + return result + } + + @inlinable + internal mutating func uncheckedRemoveFirst(_ n: Int) { + assert(count >= n) + guard n > 0 else { return } + let target = mutableSegments(forOffsets: 0 ..< n) + target.deinitialize() + startSlot = slot(startSlot, offsetBy: n) + count -= n + } + + @inlinable + internal mutating func uncheckedRemoveLast(_ n: Int) { + assert(count >= n) + guard n > 0 else { return } + let target = mutableSegments(forOffsets: count - n ..< count) + target.deinitialize() + count -= n + } + + /// Remove all elements stored in this instance, deinitializing their storage. + /// + /// This method does not ensure that the storage buffer is uniquely + /// referenced. + @inlinable + internal mutating func uncheckedRemoveAll() { + guard count > 0 else { return } + let target = mutableSegments() + target.deinitialize() + count = 0 + startSlot = .zero + } + + /// Remove all elements in `bounds`, deinitializing their storage and sliding + /// remaining elements to close the resulting gap. + /// + /// This function does not validate its input arguments in release builds. Nor + /// does it ensure that the storage buffer is uniquely referenced. + @inlinable + internal mutating func uncheckedRemove(offsets bounds: Range) { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= self.count) + + // Deinitialize elements in `bounds`. + mutableSegments(forOffsets: bounds).deinitialize() + closeGap(offsets: bounds) + } +} diff --git a/Sources/DequeModule/_UnsafeWrappedBuffer.swift b/Sources/DequeModule/_UnsafeDequeSegments.swift similarity index 85% rename from Sources/DequeModule/_UnsafeWrappedBuffer.swift rename to Sources/DequeModule/_UnsafeDequeSegments.swift index 1faefacc0..4e5b8eb60 100644 --- a/Sources/DequeModule/_UnsafeWrappedBuffer.swift +++ b/Sources/DequeModule/_UnsafeDequeSegments.swift @@ -11,11 +11,12 @@ #if !COLLECTIONS_SINGLE_MODULE import InternalCollectionsUtilities +import Future #endif @frozen @usableFromInline -internal struct _UnsafeWrappedBuffer { +internal struct _UnsafeDequeSegments { @usableFromInline internal let first: UnsafeBufferPointer @@ -54,11 +55,21 @@ internal struct _UnsafeWrappedBuffer { @inlinable internal var count: Int { first.count + (second?.count ?? 0) } + + @inlinable + internal func isIdentical(to other: Self) -> Bool { + guard self.first.isIdentical(to: other.first) else { return false } + switch (self.second, other.second) { + case (nil, nil): return true + case let (a?, b?): return a.isIdentical(to: b) + default: return false + } + } } @frozen @usableFromInline -internal struct _UnsafeMutableWrappedBuffer { +internal struct _UnsafeMutableDequeSegments { @usableFromInline internal let first: UnsafeMutableBufferPointer @@ -76,24 +87,6 @@ internal struct _UnsafeMutableWrappedBuffer { assert(first.count > 0 || second == nil) } - @inlinable - @inline(__always) - internal init( - _ first: UnsafeMutableBufferPointer.SubSequence, - _ second: UnsafeMutableBufferPointer? = nil - ) { - self.init(UnsafeMutableBufferPointer(rebasing: first), second) - } - - @inlinable - @inline(__always) - internal init( - _ first: UnsafeMutableBufferPointer, - _ second: UnsafeMutableBufferPointer.SubSequence - ) { - self.init(first, UnsafeMutableBufferPointer(rebasing: second)) - } - @inlinable @inline(__always) internal init( @@ -117,13 +110,33 @@ internal struct _UnsafeMutableWrappedBuffer { @inlinable @inline(__always) - internal init(mutating buffer: _UnsafeWrappedBuffer) { + internal init(mutating buffer: _UnsafeDequeSegments) { self.init(.init(mutating: buffer.first), buffer.second.map { .init(mutating: $0) }) } } -extension _UnsafeMutableWrappedBuffer { +extension _UnsafeMutableDequeSegments { + @inlinable + @inline(__always) + internal init( + _ first: UnsafeMutableBufferPointer.SubSequence, + _ second: UnsafeMutableBufferPointer? = nil + ) { + self.init(UnsafeMutableBufferPointer(rebasing: first), second) + } + + @inlinable + @inline(__always) + internal init( + _ first: UnsafeMutableBufferPointer, + _ second: UnsafeMutableBufferPointer.SubSequence + ) { + self.init(first, UnsafeMutableBufferPointer(rebasing: second)) + } +} + +extension _UnsafeMutableDequeSegments where Element: ~Copyable { @inlinable @inline(__always) internal var count: Int { first.count + (second?.count ?? 0) } @@ -135,9 +148,9 @@ extension _UnsafeMutableWrappedBuffer { return self } if n <= first.count { - return Self(first.prefix(n)) + return Self(first._extracting(first: n)) } - return Self(first, second!.prefix(n - first.count)) + return Self(first, second!._extracting(first: n - first.count)) } @inlinable @@ -147,22 +160,24 @@ extension _UnsafeMutableWrappedBuffer { return self } guard let second = second else { - return Self(first.suffix(n)) + return Self(first._extracting(last: n)) } if n <= second.count { - return Self(second.suffix(n)) + return Self(second._extracting(last: n)) } - return Self(first.suffix(n - second.count), second) + return Self(first._extracting(last: n - second.count), second) } } -extension _UnsafeMutableWrappedBuffer { +extension _UnsafeMutableDequeSegments where Element: ~Copyable { @inlinable internal func deinitialize() { first.deinitialize() second?.deinitialize() } +} +extension _UnsafeMutableDequeSegments { @inlinable @discardableResult internal func initialize( diff --git a/Sources/Future/Arrays/DynamicArray.swift b/Sources/Future/Arrays/DynamicArray.swift new file mode 100644 index 000000000..bf91308ad --- /dev/null +++ b/Sources/Future/Arrays/DynamicArray.swift @@ -0,0 +1,1005 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +/// A dynamically self-resizing, heap allocated, noncopyable array +/// of potentially noncopyable elements. +@frozen +public struct DynamicArray: ~Copyable { + @usableFromInline + internal var _storage: RigidArray + + @inlinable + public init() { + _storage = .init(capacity: 0) + } +} + +extension DynamicArray: Sendable where Element: Sendable & ~Copyable {} + +//MARK: - Initializers + +extension DynamicArray where Element: ~Copyable { + @inlinable + public init(consuming storage: consuming RigidArray) { + self._storage = storage + } + + @inlinable + public init(capacity: Int) { + _storage = .init(capacity: capacity) + } + + // FIXME: Remove in favor of an OutputSpan-based initializer + @inlinable + public init(count: Int, initializedWith generator: (Int) -> Element) { + self.init(capacity: count, count: count, initializedWith: generator) + } + + // FIXME: Remove in favor of an OutputSpan-based initializer + @inlinable + public init( + capacity: Int, count: Int, initializedWith generator: (Int) -> Element + ) { + _storage = .init( + capacity: capacity, count: count, initializedWith: generator) + } +} + +extension DynamicArray /*where Element: Copyable*/ { + /// Creates a new array containing the specified number of a single, + /// repeated value. + /// + /// - Parameters: + /// - repeatedValue: The element to repeat. + /// - count: The number of times to repeat the value passed in the + /// `repeating` parameter. `count` must be zero or greater. + public init(repeating repeatedValue: Element, count: Int) { + self.init(consuming: RigidArray(repeating: repeatedValue, count: count)) + } +} + +extension DynamicArray /*where Element: Copyable*/ { + @_alwaysEmitIntoClient + @inline(__always) + public init(capacity: Int? = nil, copying contents: some Sequence) { + self.init(capacity: capacity ?? 0) + self.append(copying: contents) + } + + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + @inline(__always) + public init & ~Copyable & ~Escapable>( + capacity: Int? = nil, + copying contents: borrowing C + ) { + self.init(consuming: RigidArray(capacity: capacity, copying: contents)) + } + + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + @inline(__always) + public init & Sequence>( + capacity: Int? = nil, + copying contents: C + ) { + self.init(consuming: RigidArray(capacity: capacity, copying: contents)) + } +} + +//MARK: - Basics + +extension DynamicArray where Element: ~Copyable { + @inlinable + @inline(__always) + public var capacity: Int { _storage.capacity } + + @inlinable + @inline(__always) + public var freeCapacity: Int { capacity - count } +} + +//MARK: - Span creation + +extension DynamicArray where Element: ~Copyable { + @available(SwiftStdlib 6.2, *) + public var span: Span { + @lifetime(borrow self) + @inlinable + get { + _storage.span + } + } + +#if compiler(>=6.2) && $InoutLifetimeDependence + @available(SwiftStdlib 6.2, *) + public var mutableSpan: MutableSpan { + @lifetime(&self) + @inlinable + mutating get { + _storage.mutableSpan + } + } +#else + @available(SwiftStdlib 6.2, *) + public var mutableSpan: MutableSpan { + @lifetime(borrow self) + @inlinable + mutating get { + _storage.mutableSpan + } + } +#endif +} + +//MARK: RandomAccessContainer conformance + +extension DynamicArray where Element: ~Copyable { + public typealias Index = Int + + @inlinable + @inline(__always) + public var isEmpty: Bool { _storage.isEmpty } + + @inlinable + @inline(__always) + public var count: Int { _storage.count } + + @inlinable + @inline(__always) + public var startIndex: Int { _storage.startIndex } + + @inlinable + @inline(__always) + public var endIndex: Int { _storage.count } + + @inlinable + @inline(__always) + public var indices: Range { _storage.indices} + + @inlinable + @inline(__always) + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + _storage.borrowElement(at: index) + } +} + +@available(SwiftStdlib 6.2, *) +extension DynamicArray: RandomAccessContainer where Element: ~Copyable { + @inlinable + @lifetime(borrow self) + public func nextSpan(after index: inout Int) -> Span { + _storage.nextSpan(after: &index) + } + + @inlinable + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + _storage.previousSpan(before: &index) + } +} + +// MARK: - MutableContainer conformance + +extension DynamicArray where Element: ~Copyable { + @inlinable +#if compiler(>=6.2) && $InoutLifetimeDependence + @lifetime(&self) +#else + @lifetime(borrow self) +#endif + public mutating func mutateElement(at index: Int) -> Inout { + _storage.mutateElement(at: index) + } + + @inlinable + public mutating func swapAt(_ i: Int, _ j: Int) { + _storage.swapAt(i, j) + } +} + +extension DynamicArray: MutableContainer where Element: ~Copyable { + @available(SwiftStdlib 6.2, *) +#if compiler(>=6.2) && $InoutLifetimeDependence + @lifetime(&self) +#else + @lifetime(borrow self) +#endif + public mutating func nextMutableSpan(after index: inout Int) -> MutableSpan { + _storage.nextMutableSpan(after: &index) + } +} + +//MARK: Unsafe access + +extension DynamicArray where Element: ~Copyable { + // FIXME: Replace this with an OutputSpan-based mutator + @inlinable + public mutating func withUnsafeMutableBufferPointer( + _ body: (UnsafeMutableBufferPointer, inout Int) throws(E) -> R + ) throws(E) -> R { + unsafe try _storage.withUnsafeMutableBufferPointer(body) + } +} + +//MARK: - Resizing + +@inlinable +@_transparent +internal func _growDynamicArrayCapacity(_ capacity: Int) -> Int { + 2 * capacity +} + +extension DynamicArray where Element: ~Copyable { + @inlinable @inline(never) + public mutating func reallocate(capacity: Int) { + _storage.reallocate(capacity: capacity) + } + + @inlinable @inline(never) + public mutating func reserveCapacity(_ n: Int) { + _storage.reserveCapacity(n) + } + + @inlinable + @_transparent + public mutating func _ensureFreeCapacity(_ freeCapacity: Int) { + guard _storage.freeCapacity < freeCapacity else { return } + _ensureFreeCapacitySlow(freeCapacity) + } + + @inlinable + internal mutating func _ensureFreeCapacitySlow(_ freeCapacity: Int) { + let newCapacity = Swift.max( + count + freeCapacity, + _growDynamicArrayCapacity(capacity)) + reallocate(capacity: newCapacity) + } +} + +//MARK: - Removal operations + +extension DynamicArray where Element: ~Copyable { + /// Removes all elements from the array, preserving its allocated capacity. + /// + /// - Complexity: O(*n*), where *n* is the original count of the array. + @inlinable + @inline(__always) + public mutating func removeAll(keepingCapacity keepCapacity: Bool = false) { + if keepCapacity { + _storage.removeAll() + } else { + _storage = RigidArray(capacity: 0) + } + } + + /// Removes and returns the last element of the array. + /// + /// The array must not be empty. + /// + /// - Returns: The last element of the original array. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + @discardableResult + public mutating func removeLast() -> Element { + _storage.removeLast() + } + + /// Removes the specified number of elements from the end of the array. + /// + /// Attempting to remove more elements than exist in the array triggers a + /// runtime error. + /// + /// - Parameter k: The number of elements to remove from the array. + /// `k` must be greater than or equal to zero and must not exceed + /// the count of the array. + /// + /// - Complexity: O(`k`) + @inlinable + public mutating func removeLast(_ k: Int) { + _storage.removeLast(k) + } + + /// Removes and returns the element at the specified position. + /// + /// All the elements following the specified position are moved to close the + /// gap. + /// + /// - Parameter i: The position of the element to remove. `index` must be + /// a valid index of the array that is not equal to the end index. + /// - Returns: The removed element. + /// + /// - Complexity: O(`count`) + @inlinable + @inline(__always) + @discardableResult + public mutating func remove(at index: Int) -> Element { + _storage.remove(at: index) + } + + /// Removes the specified subrange of elements from the array. + /// + /// All the elements following the specified subrange are moved to close the + /// resulting gap. + /// + /// - Parameter bounds: The subrange of the array to remove. The bounds + /// of the range must be valid indices of the array. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + _storage.removeSubrange(bounds) + } + + /// Removes the specified subrange of elements from the array. + /// + /// - Parameter bounds: The subrange of the array to remove. The bounds + /// of the range must be valid indices of the array. + /// + /// - Complexity: O(`count`) + @_alwaysEmitIntoClient + public mutating func removeSubrange(_ bounds: some RangeExpression) { + // FIXME: Remove this in favor of a standard algorithm. + removeSubrange(bounds.relative(to: indices)) + } +} + +extension DynamicArray where Element: ~Copyable { + /// Removes all the elements that satisfy the given predicate. + /// + /// Use this method to remove every element in a container that meets + /// particular criteria. The order of the remaining elements is preserved. + /// + /// - Parameter shouldBeRemoved: A closure that takes an element of the + /// sequence as its argument and returns a Boolean value indicating + /// whether the element should be removed from the array. + /// + /// - Complexity: O(`count`) + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + public mutating func removeAll( + where shouldBeRemoved: (borrowing Element) throws(E) -> Bool + ) throws(E) { + // FIXME: Remove this in favor of a standard algorithm. + let suffixStart = try _halfStablePartition(isSuffixElement: shouldBeRemoved) + removeSubrange(suffixStart...) + } +} + +extension DynamicArray where Element: ~Copyable { + /// Removes and returns the last element of the array, if there is one. + /// + /// - Returns: The last element of the array if the array is not empty; + /// otherwise, `nil`. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + public mutating func popLast() -> Element? { + if isEmpty { return nil } + return removeLast() + } +} + + +//MARK: - Insertion operations + +extension DynamicArray where Element: ~Copyable { + /// Adds an element to the end of the array. + /// + /// If the array does not have sufficient capacity to hold any more elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameter item: The element to append to the collection. + /// + /// - Complexity: O(1) when amortized over many invocations on the same array + @inlinable + public mutating func append(_ item: consuming Element) { + _ensureFreeCapacity(1) + _storage.append(item) + } +} + +extension DynamicArray { + /// Copies the elements of a buffer to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: A fully initialized buffer whose contents to copy into + /// the array. + /// + /// - Complexity: O(`newElements.count`) when amortized over many + /// invocations on the same array. + @_alwaysEmitIntoClient + public mutating func append( + copying newElements: UnsafeBufferPointer + ) { + _ensureFreeCapacity(newElements.count) + unsafe _storage.append(copying: newElements) + } + + /// Copies the elements of a buffer to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: A fully initialized buffer whose contents to copy into + /// the array. + /// + /// - Complexity: O(`newElements.count`) when amortized over many + /// invocations on the same array. + @_alwaysEmitIntoClient + public mutating func append( + copying newElements: UnsafeMutableBufferPointer + ) { + unsafe self.append(copying: UnsafeBufferPointer(newElements)) + } + + /// Copies the elements of a span to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: A span whose contents to copy into the array. + /// + /// - Complexity: O(`newElements.count`) when amortized over many + /// invocations on the same array. + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + public mutating func append(copying newElements: Span) { + _ensureFreeCapacity(newElements.count) + _storage.append(copying: newElements) + } + + /// Copies the elements of a sequence to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. This + /// reallocation can happen multiple times. + /// + /// - Parameters + /// - newElements: The new elements to copy into the array. + /// + /// - Complexity: O(*m*), where *m* is the length of `newElements`, when + /// amortized over many invocations over the same array. + @_alwaysEmitIntoClient + public mutating func append(copying newElements: some Sequence) { + let done: Void? = newElements.withContiguousStorageIfAvailable { buffer in + _ensureFreeCapacity(buffer.count) + unsafe _storage.append(copying: buffer) + return + } + if done != nil { return } + + _ensureFreeCapacity(newElements.underestimatedCount) + var it = _storage._append(prefixOf: newElements) + while let item = it.next() { + _ensureFreeCapacity(1) + _storage.append(item) + } + } + + @available(SwiftStdlib 6.2, *) + public mutating func _appendContainer< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C + ) { + var i = newElements.startIndex + while true { + let span = newElements.nextSpan(after: &i) + if span.isEmpty { break } + self.append(copying: span) + } + } + + /// Copies the elements of a container to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: A container whose contents to copy into the array. + /// + /// - Complexity: O(`newElements.count`), when amortized over many invocations + /// over the same array. + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + public mutating func append< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C + ) { + _appendContainer(copying: newElements) + } + + /// Copies the elements of a container to the end of this array. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: A container whose contents to copy into the array. + /// + /// - Complexity: O(`newElements.count`), when amortized over many invocations + /// over the same array. + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + public mutating func append< + C: Container & Sequence + >( + copying newElements: borrowing C + ) { + _appendContainer(copying: newElements) + } +} + +extension DynamicArray where Element: ~Copyable { + /// Inserts a new element into the array at the specified position. + /// + /// If the array does not have sufficient capacity to hold any more elements, + /// then this reallocates storage to extend its capacity. + /// + /// The new element is inserted before the element currently at the specified + /// index. If you pass the array's `endIndex` as the `index` parameter, then + /// the new element is appended to the container. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// - Parameter item: The new element to insert into the array. + /// - Parameter i: The position at which to insert the new element. + /// `index` must be a valid index in the array. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func insert(_ item: consuming Element, at index: Int) { + precondition(index >= 0 && index <= count) + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(1) + _storage.insert(item, at: index) + } +} + +extension DynamicArray { + /// Copyies the elements of a fully initialized buffer pointer into this + /// array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. The buffer + /// must be fully initialized. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + public mutating func insert( + copying newElements: UnsafeBufferPointer, at index: Int + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(newElements.count) + unsafe _storage.insert(copying: newElements, at: index) + } + + /// Copyies the elements of a fully initialized buffer pointer into this + /// array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. The buffer + /// must be fully initialized. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + public mutating func insert( + copying newElements: UnsafeMutableBufferPointer, + at index: Int + ) { + unsafe self.insert(copying: UnsafeBufferPointer(newElements), at: index) + } + + /// Copies the elements of a span into this array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftStdlib 6.2, *) + @inlinable + public mutating func insert( + copying newElements: Span, at index: Int + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(newElements.count) + _storage.insert(copying: newElements, at: index) + } + + /// Copies the elements of a collection into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved + /// to make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + public mutating func insert( + copying newElements: some Collection, at index: Int + ) { + // FIXME: Avoiding moving the subsequent elements twice. + let newCount = newElements.count + _ensureFreeCapacity(newCount) + _storage._insertCollection( + at: index, copying: newElements, newCount: newCount) + } + + @available(SwiftStdlib 6.2, *) + @inlinable + internal mutating func _insertContainer< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C, at index: Int + ) { + // FIXME: Avoiding moving the subsequent elements twice. + let newCount = newElements.count + _ensureFreeCapacity(newCount) + _storage._insertContainer( + at: index, copying: newElements, newCount: newCount) + } + + /// Copies the elements of a container into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + @inline(__always) + public mutating func insert< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C, at index: Int + ) { + _insertContainer(copying: newElements, at: index) + } + + /// Copies the elements of a container into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the array does not have sufficient capacity to hold enough elements, + /// then this reallocates the array's storage to extend its capacity. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + @inline(__always) + public mutating func insert< + C: Container & Collection + >( + copying newElements: borrowing C, at index: Int + ) { + _insertContainer(copying: newElements, at: index) + } +} + +//MARK: - Range replacement + +extension DynamicArray { + /// Replaces the specified subrange of elements by copying the elements of + /// the given buffer pointer, which must be fully initialized. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + @inline(__always) + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: UnsafeBufferPointer + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(newElements.count) + unsafe _storage.replaceSubrange(subrange, copying: newElements) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given buffer pointer, which must be fully initialized. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + @inline(__always) + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: UnsafeMutableBufferPointer + ) { + unsafe self.replaceSubrange( + subrange, copying: UnsafeBufferPointer(newElements)) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given span. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length span as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftStdlib 6.2, *) + @inlinable + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: Span + ) { + // FIXME: Avoiding moving the subsequent elements twice. + _ensureFreeCapacity(newElements.count) + _storage.replaceSubrange(subrange, copying: newElements) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given collection. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length collection as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + @inline(__always) + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: __owned some Collection + ) { + // FIXME: Avoiding moving the subsequent elements twice. + let c = newElements.count + _ensureFreeCapacity(c) + _storage._replaceSubrange( + subrange, copyingCollection: newElements, newCount: c) + } + + @available(SwiftStdlib 6.2, *) + @inlinable + public mutating func _replaceSubrange< + C: Container & ~Copyable & ~Escapable + >( + _ subrange: Range, + copyingContainer newElements: borrowing C + ) { + // FIXME: Avoiding moving the subsequent elements twice. + let c = newElements.count + _ensureFreeCapacity(c) + _storage._replaceSubrange( + subrange, copyingContainer: newElements, newCount: c) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given container. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length container as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftStdlib 6.2, *) + @inlinable + @inline(__always) + public mutating func replaceSubrange< + C: Container & ~Copyable & ~Escapable + >( + _ subrange: Range, + copying newElements: borrowing C + ) { + _replaceSubrange(subrange, copyingContainer: newElements) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given container. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same location. + /// The number of new elements need not match the number of elements being + /// removed. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length container as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftStdlib 6.2, *) + @inlinable + @inline(__always) + public mutating func replaceSubrange< + C: Container & Collection + >( + _ subrange: Range, + copying newElements: borrowing C + ) { + _replaceSubrange(subrange, copyingContainer: newElements) + } +} diff --git a/Sources/Future/Arrays/NewArray.swift b/Sources/Future/Arrays/NewArray.swift new file mode 100644 index 000000000..b5db4e909 --- /dev/null +++ b/Sources/Future/Arrays/NewArray.swift @@ -0,0 +1,255 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +/// What `Array` might look like if we defined it today. +@frozen +public struct NewArray { + @usableFromInline + internal var _storage: Shared> + + @inlinable + public init(minimumCapacity: Int) { + self._storage = Shared(RigidArray(capacity: minimumCapacity)) + } +} + +extension NewArray { + @available(SwiftStdlib 6.2, *) + public var span: Span { + @lifetime(borrow self) + get { + _storage.value.span + } + } +} + +extension NewArray { + @inlinable + public var capacity: Int { _storage.value.capacity } +} + +extension NewArray: RandomAccessCollection, MutableCollection { + public typealias Index = Int + public typealias Indices = Range + + @inlinable + public var startIndex: Int { 0 } + + @inlinable + public var endIndex: Int { _storage.value.count } + + public subscript(position: Int) -> Element { + @inlinable + unsafeAddress { + unsafe _storage.value.borrowElement(at: position)._pointer + } + @inlinable + unsafeMutableAddress { + _ensureUnique() + return unsafe _storage.value.mutateElement(at: position)._pointer + } + } +} + +@available(SwiftStdlib 6.2, *) +extension NewArray: RandomAccessContainer, MutableContainer { + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + _storage.value.borrowElement(at: index) + } + + @lifetime(borrow self) + public func nextSpan(after index: inout Int) -> Span { + _storage.value.nextSpan(after: &index) + } + + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + return _storage.value.previousSpan(before: &index) + } + +#if compiler(>=6.2) && $InoutLifetimeDependence + @lifetime(&self) +#else + @lifetime(borrow self) +#endif + public mutating func mutateElement(at index: Int) -> Inout { + _ensureUnique() + return _storage.value.mutateElement(at: index) + } + +#if compiler(>=6.2) && $InoutLifetimeDependence + @lifetime(&self) +#else + @lifetime(borrow self) +#endif + public mutating func nextMutableSpan(after index: inout Int) -> MutableSpan { + _ensureUnique() + return _storage.value.nextMutableSpan(after: &index) + } +} + +extension NewArray { + @inlinable + public mutating func reserveCapacity(_ n: Int) { + if _storage.isUnique() { + _storage.value.reserveCapacity(n) + } else { + _storage.replace { $0.copy(capacity: Swift.max($0.capacity, n)) } + } + } + + @inlinable + internal static func _grow(_ capacity: Int) -> Int { + 2 * capacity + } + + @inlinable + public mutating func _ensureFreeCapacity(_ minimumCapacity: Int) { + guard _storage.value.freeCapacity < minimumCapacity else { return } + let newCapacity = Swift.max(count + minimumCapacity, Self._grow(capacity)) + _storage.value.reserveCapacity(newCapacity) + } + + @inlinable + internal mutating func _ensureUnique() { + _storage.ensureUnique { $0.copy() } + } + + @inlinable + internal mutating func _ensureUnique( + minimumCapacity: Int, + linear: Bool + ) { + if minimumCapacity <= self.capacity { + _ensureUnique() + return + } + let c = Swift.max(minimumCapacity, Self._grow(capacity)) + _storage.edit( + shared: { $0.copy(capacity: c) }, + unique: { $0.reserveCapacity(c) } + ) + } + + @inlinable + public mutating func _edit( + ensuringMinimumCapacity minimumCapacity: Int, + shared cloner: (borrowing RigidArray, Int) -> RigidArray, + resize resizer: (inout RigidArray, Int) -> RigidArray, + direct updater: (inout RigidArray) -> Void + ) { + var c = capacity + let isUnique = _storage.isUnique() + let hasEnoughCapacity = minimumCapacity <= c + if !hasEnoughCapacity { + c = Swift.max(minimumCapacity, Self._grow(c)) + } + switch (isUnique, hasEnoughCapacity) { + case (true, true): + updater(&_storage.value) + case (true, false): + _modify(&_storage.value, by: { resizer(&$0, c) }) + case (false, _): + _storage.replace(using: { cloner($0, c) }) + } + } +} + +@inlinable +@_transparent +internal func _modify( + _ value: inout T, + by body: (inout T) -> T +) { + value = body(&value) +} + +extension NewArray { + @inlinable + public mutating func append(_ newElement: Element) { + self._ensureFreeCapacity(1) + self._storage.value.append(newElement) + } + + @available(SwiftStdlib 6.2, *) + @inlinable + public mutating func insert(_ item: consuming Element, at index: Int) { + precondition(index >= 0 && index <= count) + var item: Element? = item + _edit( + ensuringMinimumCapacity: count + 1, + shared: { src, capacity in + var new = RigidArray(capacity: capacity) + new.append(copying: src._span(in: 0 ..< index)) + new.append(item.take()!) + new.append(copying: src._span(in: index ..< src.count)) + return new + }, + resize: { src, capacity in + var dst = RigidArray(capacity: capacity) + unsafe src.withUnsafeMutableBufferPointer { srcBuffer, srcCount in + unsafe dst.withUnsafeMutableBufferPointer { dstBuffer, dstCount in + assert(dstCount == 0) + assert(dstBuffer.count >= srcCount + 1) + _ = unsafe dstBuffer.moveInitialize(fromContentsOf: srcBuffer.extracting(.. Element { + precondition(count > 0, "Cannot remove last element from empty array") + if _storage.isUnique() { + return _storage.value.removeLast() + } else { + let old = self[count - 1] + _storage.replace { + var new = RigidArray(capacity: $0.capacity) + new.append(copying: $0.span._extracting(droppingLast: 1)) + return new + } + return old + } + } +} + +#if false // FIXME: Implement +extension NewArray: RangeReplaceableCollection { + public init() { + // FIXME: Figure out if we can implement empty singletons in this setup. + self._storage = Shared(RigidArray(capacity: 0)) + } + + mutating public func replaceSubrange( + _ subrange: Range, + with newElements: some Collection + ) { + let delta = newElements.count - subrange.count + _ensureUnique(minimumCapacity: capacity + delta) + } +} +#endif diff --git a/Sources/Future/Arrays/RigidArray.swift b/Sources/Future/Arrays/RigidArray.swift new file mode 100644 index 000000000..f10ed9e0e --- /dev/null +++ b/Sources/Future/Arrays/RigidArray.swift @@ -0,0 +1,1595 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +#if !COLLECTIONS_SINGLE_MODULE +import InternalCollectionsUtilities +#endif + +/// A fixed capacity, heap allocated, noncopyable array of potentially +/// noncopyable elements. +/// +/// `RigidArray` instances are created with a certain maximum capacity. Elements +/// can be added to the array up to that capacity, but no more: trying to add an +/// item to a full array results in a runtime trap. +/// +/// var items = RigidArray(capacity: 2) +/// items.append(1) +/// items.append(2) +/// items.append(3) // Runtime error: RigidArray capacity overflow +/// +/// Rigid arrays provide convenience properties to help verify that it has +/// enough available capacity: `isFull` and `freeCapacity`. +/// +/// guard items.freeCapacity >= 4 else { throw CapacityOverflow() } +/// items.append(copying: newItems) +/// +/// It is possible to extend or shrink the capacity of a rigid array instance, +/// but this needs to be done explicitly, with operations dedicated to this +/// purpose (such as ``reserveCapacity`` and ``reallocate(capacity:)``). +/// The array never resizes itself automatically. +/// +/// It therefore requires careful manual analysis or up front runtime capacity +/// checks to prevent the array from overflowing its storage. This makes +/// this type more difficult to use than a dynamic array. However, it allows +/// this construct to provide predictably stable performance. +/// +/// This trading of usability in favor of stable performance limits `RigidArray` +/// to the most resource-constrained of use cases, such as space-constrained +/// environments that require carefully accounting of every heap allocation, or +/// time-constrained applications that cannot accommodate unexpected latency +/// spikes due to a reallocation getting triggered at an inopportune moment. +/// +/// For use cases outside of these narrow domains, we generally recommmend +/// the use of ``DynamicArray`` rather than `RigidArray`. +@safe +@frozen +public struct RigidArray: ~Copyable { + @usableFromInline + internal var _storage: UnsafeMutableBufferPointer + + @usableFromInline + internal var _count: Int + + deinit { + unsafe _storage.extracting(0 ..< count).deinitialize() + unsafe _storage.deallocate() + } + + @inlinable + public init(capacity: Int) { + precondition(capacity >= 0, "Array capacity must be nonnegative") + if capacity > 0 { + unsafe _storage = .allocate(capacity: capacity) + } else { + unsafe _storage = .init(start: nil, count: 0) + } + _count = 0 + } +} +extension RigidArray: @unchecked Sendable where Element: Sendable & ~Copyable {} + +//MARK: - Initializers + +extension RigidArray where Element: ~Copyable { + // FIXME: Remove in favor of an OutputSpan-based initializer + @inlinable + public init( + capacity: Int, + count: Int, + initializedWith generator: (Int) -> Element + ) { + precondition( + count >= 0 && count <= capacity, + "RigidArray capacity overflow") + unsafe _storage = .allocate(capacity: capacity) + for i in 0 ..< count { + unsafe _storage.initializeElement(at: i, to: generator(i)) + } + _count = count + } + + // FIXME: Remove in favor of an OutputSpan-based initializer + @inlinable + public init(count: Int, initializedWith generator: (Int) -> Element) { + self.init(capacity: count, count: count, initializedWith: generator) + } +} + +extension RigidArray /*where Element: Copyable*/ { + /// Creates a new array containing the specified number of a single, + /// repeated value. + /// + /// - Parameters: + /// - repeatedValue: The element to repeat. + /// - count: The number of times to repeat the value passed in the + /// `repeating` parameter. `count` must be zero or greater. + public init(repeating repeatedValue: Element, count: Int) { + self.init(capacity: count) + unsafe _freeSpace.initialize(repeating: repeatedValue) + _count = count + } +} + +extension RigidArray /*where Element: Copyable*/ { + @_alwaysEmitIntoClient + @inline(__always) + public init( + capacity: Int, + copying contents: some Sequence + ) { + self.init(capacity: capacity) + self.append(copying: contents) + } + + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + @inline(__always) + public init & Sequence>( + capacity: Int, + copying contents: C + ) { + self.init(capacity: capacity) + self.append(copying: contents) + } + + @_alwaysEmitIntoClient + @inline(__always) + public init( + capacity: Int? = nil, + copying contents: some Collection + ) { + self.init(capacity: capacity ?? contents.count) + self.append(copying: contents) + } + + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + @inline(__always) + public init & ~Copyable & ~Escapable>( + capacity: Int? = nil, + copying contents: borrowing C + ) { + self.init(capacity: capacity ?? contents.count) + self.append(copying: contents) + } + + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + @inline(__always) + public init & Collection>( + capacity: Int? = nil, + copying contents: C + ) { + self.init(capacity: capacity ?? contents.count) + self.append(copying: contents) + } +} + +// FIXME: init(moving:), init(consuming:) + +//MARK: - Basics + +extension RigidArray where Element: ~Copyable { + @inlinable + @inline(__always) + public var capacity: Int { unsafe _storage.count } + + @inlinable + @inline(__always) + public var freeCapacity: Int { capacity - count } + + @inlinable + @inline(__always) + public var isFull: Bool { freeCapacity == 0 } +} + +extension RigidArray where Element: ~Copyable { + @inlinable + internal var _items: UnsafeMutableBufferPointer { + unsafe _storage.extracting(Range(uncheckedBounds: (0, _count))) + } + + @inlinable + internal var _freeSpace: UnsafeMutableBufferPointer { + unsafe _storage.extracting(Range(uncheckedBounds: (_count, capacity))) + } +} + +//MARK: - Span creation + +extension RigidArray where Element: ~Copyable { + @available(SwiftStdlib 6.2, *) + public var span: Span { + @lifetime(borrow self) + @inlinable + get { + let result = unsafe Span(_unsafeElements: _items) + return unsafe _overrideLifetime(result, borrowing: self) + } + } + +#if compiler(>=6.2) && $InoutLifetimeDependence + @available(SwiftStdlib 6.2, *) + public var mutableSpan: MutableSpan { + @lifetime(&self) + @inlinable + mutating get { + let result = unsafe MutableSpan(_unsafeElements: _items) + return unsafe _overrideLifetime(result, mutating: &self) + } + } +#else + @available(SwiftStdlib 6.2, *) + public var mutableSpan: MutableSpan { + @lifetime(borrow self) + @inlinable + mutating get { + let result = unsafe MutableSpan(_unsafeElements: _items) + return unsafe _overrideLifetime(result, mutating: &self) + } + } +#endif + + @available(SwiftStdlib 6.2, *) + @inlinable + @lifetime(borrow self) + internal func _span(in range: Range) -> Span { + span._extracting(range) + } + + @available(SwiftStdlib 6.2, *) + @inlinable +#if compiler(>=6.2) && $InoutLifetimeDependence + @lifetime(&self) +#else + @lifetime(borrow self) +#endif + internal mutating func _mutableSpan( + in range: Range + ) -> MutableSpan { + let result = unsafe MutableSpan(_unsafeElements: _items.extracting(range)) + return unsafe _overrideLifetime(result, mutating: &self) + } +} + +extension RigidArray where Element: ~Copyable { + @inlinable + internal func _contiguousSubrange(following index: inout Int) -> Range { + precondition(index >= 0 && index <= _count, "Index out of bounds") + defer { index = _count } + return unsafe Range(uncheckedBounds: (index, _count)) + } + + @inlinable + internal func _contiguousSubrange(preceding index: inout Int) -> Range { + precondition(index >= 0 && index <= _count, "Index out of bounds") + defer { index = 0 } + return unsafe Range(uncheckedBounds: (0, index)) + } +} + +//MARK: - RandomAccessContainer and MutableContainer conformance + +extension RigidArray where Element: ~Copyable { + public typealias Index = Int + + @inlinable + @inline(__always) + public var isEmpty: Bool { count == 0 } + + @inlinable + @inline(__always) + public var count: Int { _count } + + @inlinable + @inline(__always) + public var startIndex: Int { 0 } + + @inlinable + @inline(__always) + public var endIndex: Int { count } + + @inlinable + @inline(__always) + public var indices: Range { unsafe Range(uncheckedBounds: (0, count)) } + + @inlinable + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + precondition(index >= 0 && index < _count, "Index out of bounds") + return unsafe Borrow( + unsafeAddress: _storage.baseAddress.unsafelyUnwrapped.advanced(by: index), + borrowing: self + ) + } +} + +@available(SwiftStdlib 6.2, *) +extension RigidArray: RandomAccessContainer where Element: ~Copyable { + @inlinable + @lifetime(borrow self) + public func nextSpan(after index: inout Int) -> Span { + _span(in: _contiguousSubrange(following: &index)) + } + + @inlinable + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + _span(in: _contiguousSubrange(preceding: &index)) + } +} + +extension RigidArray where Element: ~Copyable { + @inlinable +#if compiler(>=6.2) && $InoutLifetimeDependence + @lifetime(&self) +#else + @lifetime(borrow self) +#endif + public mutating func mutateElement(at index: Int) -> Inout { + precondition(index >= 0 && index < _count) + return unsafe Inout( + unsafeAddress: _storage.baseAddress.unsafelyUnwrapped.advanced(by: index), + mutating: &self + ) + } + + @inlinable + public mutating func swapAt(_ i: Int, _ j: Int) { + precondition( + i >= 0 && i < _count && j >= 0 && j < _count, + "Index out of bounds") + unsafe _items.swapAt(i, j) + } +} + +@available(SwiftStdlib 6.2, *) +extension RigidArray: MutableContainer where Element: ~Copyable { +#if compiler(>=6.2) && $InoutLifetimeDependence + @lifetime(&self) +#else + @lifetime(borrow self) +#endif + public mutating func nextMutableSpan( + after index: inout Int + ) -> MutableSpan { + _mutableSpan(in: _contiguousSubrange(following: &index)) + } +} + +//MARK: Unsafe access + +extension RigidArray where Element: ~Copyable { + // FIXME: Replace this with an OutputSpan-based mutator + @inlinable + public mutating func withUnsafeMutableBufferPointer( + _ body: (UnsafeMutableBufferPointer, inout Int) throws(E) -> R + ) throws(E) -> R { + defer { precondition(_count >= 0 && _count <= capacity) } + return unsafe try body(_storage, &_count) + } +} + +//MARK: - Resizing + +extension RigidArray where Element: ~Copyable { + @inlinable + public mutating func reallocate(capacity newCapacity: Int) { + precondition(newCapacity >= count, "RigidArray capacity overflow") + guard newCapacity != capacity else { return } + let newStorage: UnsafeMutableBufferPointer = .allocate( + capacity: newCapacity) + let i = unsafe newStorage.moveInitialize(fromContentsOf: self._items) + assert(i == count) + unsafe _storage.deallocate() + unsafe _storage = newStorage + } + + @inlinable + public mutating func reserveCapacity(_ n: Int) { + guard capacity < n else { return } + reallocate(capacity: n) + } +} + +//MARK: - Copying helpers + +extension RigidArray { + @inlinable + public func copy() -> Self { + copy(capacity: capacity) + } + + @inlinable + public func copy(capacity: Int) -> Self { + precondition(capacity >= count, "RigidArray capacity overflow") + var result = RigidArray(capacity: capacity) + let initialized = unsafe result._storage.initialize(fromContentsOf: _items) + precondition(initialized == count) + result._count = count + return result + } +} + + +//MARK: - Opening and closing gaps + +extension RigidArray where Element: ~Copyable { + @inlinable + internal mutating func _closeGap( + at index: Int, count: Int + ) { + guard count > 0 else { return } + let source = unsafe _storage.extracting( + Range(uncheckedBounds: (index + count, _count))) + let target = unsafe _storage.extracting( + Range(uncheckedBounds: (index, index + source.count))) + let i = unsafe target.moveInitialize(fromContentsOf: source) + assert(i == target.endIndex) + } + + @inlinable + internal mutating func _openGap( + at index: Int, count: Int + ) -> UnsafeMutableBufferPointer { + assert(index >= 0 && index <= _count) + assert(count <= freeCapacity) + guard count > 0 else { return unsafe _storage.extracting(index ..< index) } + let source = unsafe _storage.extracting( + Range(uncheckedBounds: (index, _count))) + let target = unsafe _storage.extracting( + Range(uncheckedBounds: (index + count, _count + count))) + let i = unsafe target.moveInitialize(fromContentsOf: source) + assert(i == target.count) + return unsafe _storage.extracting( + Range(uncheckedBounds: (index, index + count))) + } +} + +//MARK: - Removal operations + +extension RigidArray where Element: ~Copyable { + /// Removes all elements from the array, preserving its allocated capacity. + /// + /// - Complexity: O(*n*), where *n* is the original count of the array. + @inlinable + public mutating func removeAll() { + unsafe _items.deinitialize() + _count = 0 + } + + /// Removes and returns the last element of the array. + /// + /// The array must not be empty. + /// + /// - Returns: The last element of the original array. + /// + /// - Complexity: O(1) + @inlinable + @discardableResult + public mutating func removeLast() -> Element { + precondition(!isEmpty, "Cannot remove last element from an empty array") + let old = unsafe _storage.moveElement(from: _count - 1) + _count -= 1 + return old + } + + /// Removes the specified number of elements from the end of the array. + /// + /// Attempting to remove more elements than exist in the array + /// triggers a runtime error. + /// + /// - Parameter k: The number of elements to remove from the array. + /// `k` must be greater than or equal to zero and must not exceed + /// the count of the array. + /// + /// - Complexity: O(`k`) + @inlinable + public mutating func removeLast(_ k: Int) { + if k == 0 { return } + precondition( + k >= 0 && k <= _count, + "Count of elements to remove is out of bounds") + unsafe _storage.extracting( + Range(uncheckedBounds: (_count - k, _count)) + ).deinitialize() + _count &-= k + } + + /// Removes and returns the element at the specified position. + /// + /// All the elements following the specified position are moved to close the + /// gap. + /// + /// - Parameter i: The position of the element to remove. `index` must be + /// a valid index of the array that is not equal to the end index. + /// - Returns: The removed element. + /// + /// - Complexity: O(`count`) + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Element { + precondition(index >= 0 && index < _count, "Index out of bounds") + let old = unsafe _storage.moveElement(from: index) + _closeGap(at: index, count: 1) + _count -= 1 + return old + } + + /// Removes the specified subrange of elements from the array. + /// + /// All the elements following the specified subrange are moved to close the + /// resulting gap. + /// + /// - Parameter bounds: The subrange of the array to remove. The bounds + /// of the range must be valid indices of the array. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + precondition( + bounds.lowerBound >= 0 && bounds.upperBound <= _count, + "Subrange out of bounds") + guard !bounds.isEmpty else { return } + unsafe _storage.extracting(bounds).deinitialize() + _closeGap(at: bounds.lowerBound, count: bounds.count) + _count -= bounds.count + } + + /// Removes the specified subrange of elements from the array. + /// + /// - Parameter bounds: The subrange of the array to remove. The bounds of the + /// range must be valid indices of the array. + /// + /// - Complexity: O(`count`) + @_alwaysEmitIntoClient + public mutating func removeSubrange(_ bounds: some RangeExpression) { + // FIXME: Remove this in favor of a standard algorithm. + removeSubrange(bounds.relative(to: indices)) + } +} + +extension RigidArray where Element: ~Copyable { + /// Removes all the elements that satisfy the given predicate. + /// + /// Use this method to remove every element in a container that meets + /// particular criteria. The order of the remaining elements is preserved. + /// + /// - Parameter shouldBeRemoved: A closure that takes an element of the + /// sequence as its argument and returns a Boolean value indicating + /// whether the element should be removed from the array. + /// + /// - Complexity: O(`count`) + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + public mutating func removeAll( + where shouldBeRemoved: (borrowing Element) throws(E) -> Bool + ) throws(E) { + // FIXME: Remove this in favor of a standard algorithm. + let suffixStart = try _halfStablePartition(isSuffixElement: shouldBeRemoved) + removeSubrange(suffixStart...) + } +} + +extension RigidArray where Element: ~Copyable { + /// Removes and returns the last element of the array, if there is one. + /// + /// - Returns: The last element of the array if the array is not empty; + /// otherwise, `nil`. + /// + /// - Complexity: O(1) + @_alwaysEmitIntoClient + public mutating func popLast() -> Element? { + // FIXME: Remove this in favor of a standard algorithm. + if isEmpty { return nil } + return removeLast() + } +} + +//MARK: - Insertion operations + +extension RigidArray where Element: ~Copyable { + /// Adds an element to the end of the array. + /// + /// If the array does not have sufficient capacity to hold any more elements, + /// then this triggers a runtime error. + /// + /// - Parameter item: The element to append to the collection. + /// + /// - Complexity: O(1) + @inlinable + public mutating func append(_ item: consuming Element) { + precondition(!isFull, "RigidArray capacity overflow") + unsafe _storage.initializeElement(at: _count, to: item) + _count &+= 1 + } + + /// Adds an element to the end of the array, if possible. + /// + /// If the array does not have sufficient capacity to hold any more elements, + /// then this returns the given item without appending it; otherwise it + /// returns nil. + /// + /// - Parameter item: The element to append to the collection. + /// - Returns: `item` if the array is full; otherwise nil. + /// + /// - Complexity: O(1) + @inlinable + public mutating func pushLast(_ item: consuming Element) -> Element? { + // FIXME: Remove this in favor of a standard algorithm. + if isFull { return item } + append(item) + return nil + } +} + +extension RigidArray where Element: ~Copyable { + /// Moves the elements of a buffer to the end of this array, leaving the + /// buffer uninitialized. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// buffer, then this triggers a runtime error. + /// + /// - Parameters + /// - items: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`items.count`) + @_alwaysEmitIntoClient + public mutating func append( + moving items: UnsafeMutableBufferPointer + ) { + precondition(items.count <= freeCapacity, "RigidArray capacity overflow") + guard items.count > 0 else { return } + let c = unsafe _freeSpace._moveInitializePrefix(from: items) + assert(c == items.count) + _count &+= items.count + } + + /// Appends the elements of a given array to the end of this array by moving + /// them between the containers. On return, the input array becomes empty, but + /// it is not destroyed, and it preserves its original storage capacity. + /// + /// If the target array does not have sufficient capacity to hold all items + /// in the source array, then this triggers a runtime error. + /// + /// - Parameters + /// - items: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`items.count`) + @_alwaysEmitIntoClient + public mutating func append( + moving items: inout RigidArray + ) { + unsafe items.withUnsafeMutableBufferPointer { buffer, count in + let source = buffer.extracting(.. + ) { + var items = items + self.append(moving: &items) + } +} + +extension RigidArray { + /// Copies the elements of a buffer to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// buffer, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: A fully initialized buffer whose contents to copy into + /// the array. + /// + /// - Complexity: O(`newElements.count`) + @_alwaysEmitIntoClient + public mutating func append( + copying newElements: UnsafeBufferPointer + ) { + precondition( + newElements.count <= freeCapacity, + "RigidArray capacity overflow") + guard newElements.count > 0 else { return } + unsafe _freeSpace.baseAddress.unsafelyUnwrapped.initialize( + from: newElements.baseAddress.unsafelyUnwrapped, count: newElements.count) + _count &+= newElements.count + } + + /// Copies the elements of a buffer to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// buffer, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: A fully initialized buffer whose contents to copy into + /// the array. + /// + /// - Complexity: O(`newElements.count`) + @_alwaysEmitIntoClient + public mutating func append( + copying items: UnsafeMutableBufferPointer + ) { + unsafe self.append(copying: UnsafeBufferPointer(items)) + } + + /// Copies the elements of a span to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// span, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: A span whose contents to copy into the array. + /// + /// - Complexity: O(`newElements.count`) + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + public mutating func append(copying items: Span) { + unsafe items.withUnsafeBufferPointer { source in + unsafe self.append(copying: source) + } + } + + @_alwaysEmitIntoClient + @inline(__always) + internal mutating func _append>( + prefixOf items: S + ) -> S.Iterator { + let (it, c) = unsafe items._copyContents(initializing: _freeSpace) + _count += c + return it + } + + /// Copies the elements of a sequence to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// sequence, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to copy into the array. + /// + /// - Complexity: O(*m*), where *m* is the length of `newElements`. + @_alwaysEmitIntoClient + public mutating func append(copying newElements: some Sequence) { + let done: Void? = newElements.withContiguousStorageIfAvailable { buffer in + unsafe self.append(copying: buffer) + return + } + if done != nil { return } + + var it = self._append(prefixOf: newElements) + precondition(it.next() == nil, "RigidArray capacity overflow") + } + + @available(SwiftStdlib 6.2, *) + @inlinable + internal mutating func _appendContainer< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C + ) { + let (copied, end) = unsafe _freeSpace._initializePrefix( + copying: newElements) + precondition(end == newElements.endIndex, "RigidArray capacity overflow") + _count += copied + } + + /// Copies the elements of a container to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// container, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: A container whose contents to copy into the array. + /// + /// - Complexity: O(`newElements.count`) + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + @inline(__always) + public mutating func append & ~Copyable & ~Escapable>( + copying newElements: borrowing C + ) { + _appendContainer(copying: newElements) + } + + /// Copies the elements of a container to the end of this array. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// container, then this triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to copy into the array. + /// + /// - Complexity: O(*m*), where *m* is the length of `newElements`. + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + @inline(__always) + public mutating func append< + C: Container & Sequence + >(copying newElements: C) { + _appendContainer(copying: newElements) + } +} + +extension RigidArray where Element: ~Copyable { + /// Inserts a new element into the array at the specified position. + /// + /// If the array does not have sufficient capacity to hold any more elements, + /// then this triggers a runtime error. + /// + /// The new element is inserted before the element currently at the specified + /// index. If you pass the array's `endIndex` as the `index` parameter, then + /// the new element is appended to the container. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// - Parameter item: The new element to insert into the array. + /// - Parameter i: The position at which to insert the new element. + /// `index` must be a valid index in the array. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func insert(_ item: consuming Element, at index: Int) { + precondition(index >= 0 && index <= count, "Index out of bounds") + precondition(!isFull, "RigidArray capacity overflow") + if index < count { + let source = unsafe _storage.extracting(index ..< count) + let target = unsafe _storage.extracting(index + 1 ..< count + 1) + let last = unsafe target.moveInitialize(fromContentsOf: source) + assert(last == target.endIndex) + } + unsafe _storage.initializeElement(at: index, to: item) + _count += 1 + } +} + +extension RigidArray where Element: ~Copyable { + /// Moves the elements of a fully initialized buffer into this array, + /// starting at the specified position, and leaving the buffer + /// uninitialized. + /// + /// If the array does not have sufficient capacity to hold all items in the + /// buffer, then this triggers a runtime error. + /// + /// - Parameters + /// - items: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`items.count`) + @_alwaysEmitIntoClient + public mutating func insert( + moving items: UnsafeMutableBufferPointer, + at index: Int + ) { + precondition(items.count <= freeCapacity, "RigidArray capacity overflow") + guard items.count > 0 else { return } + let target = unsafe _openGap(at: index, count: items.count) + let c = unsafe target._moveInitializePrefix(from: items) + assert(c == items.count) + _count &+= items.count + } + + /// Inserts the elements of a given array into the given position in this + /// array by moving them between the containers. On return, the input array + /// becomes empty, but it is not destroyed, and it preserves its original + /// storage capacity. + /// + /// If the target array does not have sufficient capacity to hold all items + /// in the source array, then this triggers a runtime error. + /// + /// - Parameters + /// - items: An array whose contents to move into `self`. + /// + /// - Complexity: O(`items.count`) + @_alwaysEmitIntoClient + public mutating func insert( + moving items: inout RigidArray, + at index: Int + ) { + precondition(items.count <= freeCapacity, "RigidArray capacity overflow") + guard items.count > 0 else { return } + unsafe items.withUnsafeMutableBufferPointer { buffer, count in + let source = buffer.extracting(.., + at index: Int + ) { + var items = items + self.insert(moving: &items, at: index) + } +} + +extension RigidArray { + /// Copies the elements of a fully initialized buffer pointer into this + /// array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. The buffer + /// must be fully initialized. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + public mutating func insert( + copying newElements: UnsafeBufferPointer, at index: Int + ) { + guard newElements.count > 0 else { return } + precondition( + newElements.count <= freeCapacity, + "RigidArray capacity overflow") + let gap = unsafe _openGap(at: index, count: newElements.count) + unsafe gap.baseAddress.unsafelyUnwrapped.initialize( + from: newElements.baseAddress.unsafelyUnwrapped, count: newElements.count) + _count += newElements.count + } + + /// Copies the elements of a fully initialized buffer pointer into this + /// array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. The buffer + /// must be fully initialized. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + public mutating func insert( + copying newElements: UnsafeMutableBufferPointer, + at index: Int + ) { + unsafe self.insert(copying: UnsafeBufferPointer(newElements), at: index) + } + + /// Copies the elements of a span into this array at the specified position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftStdlib 6.2, *) + @inlinable + public mutating func insert( + copying newElements: Span, at index: Int + ) { + precondition( + newElements.count <= freeCapacity, + "RigidArray capacity overflow") + unsafe newElements.withUnsafeBufferPointer { + unsafe self.insert(copying: $0, at: index) + } + } + + @inlinable + internal mutating func _insertCollection( + at index: Int, + copying items: some Collection, + newCount: Int + ) { + precondition(index >= 0 && index <= _count, "Index out of bounds") + precondition(newCount <= freeCapacity, "RigidArray capacity overflow") + let gap = unsafe _openGap(at: index, count: newCount) + + let done: Void? = items.withContiguousStorageIfAvailable { buffer in + let i = unsafe gap._initializePrefix(copying: buffer) + precondition( + i == newCount, + "Broken Collection: count doesn't match contents") + _count += newCount + } + if done != nil { return } + + var (it, copied) = unsafe items._copyContents(initializing: gap) + precondition( + it.next() == nil && copied == newCount, + "Broken Collection: count doesn't match contents") + _count += newCount + } + + @available(SwiftStdlib 6.2, *) + @inlinable + internal mutating func _insertContainer< + C: Container & ~Copyable & ~Escapable + >( + at index: Int, + copying items: borrowing C, + newCount: Int + ) { + precondition(index >= 0 && index <= _count, "Index out of bounds") + precondition(newCount <= freeCapacity, "RigidArray capacity overflow") + let target = unsafe _openGap(at: index, count: newCount) + let (copied, end) = unsafe target._initializePrefix(copying: items) + precondition( + copied == newCount && end == items.endIndex, + "Broken Container: count doesn't match contents") + _count += newCount + } + + /// Copies the elements of a collection into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved + /// to make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @inlinable + @inline(__always) + public mutating func insert( + copying newElements: some Collection, at index: Int + ) { + _insertCollection( + at: index, copying: newElements, newCount: newElements.count) + } + + /// Copies the elements of a container into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + @inline(__always) + public mutating func insert< + C: Container & ~Copyable & ~Escapable + >( + copying newElements: borrowing C, at index: Int + ) { + _insertContainer( + at: index, copying: newElements, newCount: newElements.count) + } + + /// Copies the elements of a container into this array at the specified + /// position. + /// + /// The new elements are inserted before the element currently at the + /// specified index. If you pass the array’s `endIndex` as the `index` + /// parameter, then the new elements are appended to the end of the array. + /// + /// All existing elements at or following the specified position are moved to + /// make room for the new item. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// - Parameters + /// - newElements: The new elements to insert into the array. + /// - index: The position at which to insert the new elements. It must be + /// a valid index of the array. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftStdlib 6.2, *) + @_alwaysEmitIntoClient + @inline(__always) + public mutating func insert< + C: Container & Collection + >( + copying newElements: borrowing C, at index: Int + ) { + _insertContainer( + at: index, copying: newElements, newCount: newElements.count) + } +} + +//MARK: - Range replacement + +extension RigidArray where Element: ~Copyable { + /// Perform a range replacement up to populating the newly opened gap. This + /// deinitializes existing elements in the specified subrange, rearranges + /// following elements to be at their final location, and sets the container's + /// new count. + /// + /// - Returns: A buffer pointer addressing the newly opened gap, to be + /// initialized by the caller. + @inlinable + internal mutating func _gapForReplacement( + of subrange: Range, withNewCount newCount: Int + ) -> UnsafeMutableBufferPointer { + // FIXME: Replace this with a public variant based on OutputSpan. + precondition( + subrange.lowerBound >= 0 && subrange.upperBound <= _count, + "Index range out of bounds") + precondition( + newCount - subrange.count <= freeCapacity, + "RigidArray capacity overflow") + unsafe _items.extracting(subrange).deinitialize() + if newCount > subrange.count { + _ = unsafe _openGap( + at: subrange.upperBound, count: newCount - subrange.count) + } else if newCount < subrange.count { + _closeGap( + at: subrange.lowerBound + newCount, count: subrange.count - newCount) + } + _count += newCount - subrange.count + let gapRange = unsafe Range( + uncheckedBounds: (subrange.lowerBound, subrange.lowerBound + newCount)) + return unsafe _storage.extracting(gapRange) + } +} + +extension RigidArray where Element: ~Copyable { + /// Replaces the specified range of elements by moving the elements of a + /// fully initialized buffer into their place. On return, the buffer is left + /// in an uninitialized state. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: A fully initialized buffer whose contents to move into + /// the array. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @_alwaysEmitIntoClient + public mutating func replaceSubrange( + _ subrange: Range, + moving newElements: UnsafeMutableBufferPointer, + ) { + let gap = unsafe _gapForReplacement( + of: subrange, withNewCount: newElements.count) + let c = unsafe gap._moveInitializePrefix(from: newElements) + assert(c == newElements.count) + } + + /// Replaces the specified range of elements by moving the elements of a + /// another array into their place. On return, the source array + /// becomes empty, but it is not destroyed, and it preserves its original + /// storage capacity. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: An array whose contents to move into `self`. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @_alwaysEmitIntoClient + public mutating func replaceSubrange( + _ subrange: Range, + moving newElements: inout RigidArray, + ) { + unsafe newElements.withUnsafeMutableBufferPointer { buffer, count in + unsafe self.replaceSubrange(subrange, moving: buffer) + count = 0 + } + } + + /// Replaces the specified range of elements by moving the elements of a + /// given array into their place, consuming it in the process. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: An array whose contents to move into `self`. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @_alwaysEmitIntoClient + public mutating func replaceSubrange( + _ subrange: Range, + consuming newElements: consuming RigidArray, + ) { + replaceSubrange(subrange, moving: &newElements) + } +} + +extension RigidArray { + /// Replaces the specified subrange of elements by copying the elements of + /// the given buffer pointer, which must be fully initialized. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @inlinable + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: UnsafeBufferPointer + ) { + let gap = unsafe _gapForReplacement( + of: subrange, withNewCount: newElements.count) + let i = unsafe gap._initializePrefix(copying: newElements) + assert(i == gap.count) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given buffer pointer, which must be fully initialized. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length buffer as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @inlinable + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: UnsafeMutableBufferPointer + ) { + unsafe self.replaceSubrange( + subrange, + copying: UnsafeBufferPointer(newElements)) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given span. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length span as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @available(SwiftStdlib 6.2, *) + @inlinable + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: Span + ) { + unsafe newElements.withUnsafeBufferPointer { buffer in + unsafe self.replaceSubrange(subrange, copying: buffer) + } + } + + @inlinable + internal mutating func _replaceSubrange( + _ subrange: Range, + copyingCollection newElements: __owned some Collection, + newCount: Int + ) { + let gap = unsafe _gapForReplacement(of: subrange, withNewCount: newCount) + + let done: Void? = newElements.withContiguousStorageIfAvailable { buffer in + let i = unsafe gap._initializePrefix(copying: buffer) + precondition( + i == newCount, + "Broken Collection: count doesn't match contents") + } + if done != nil { return } + + var (it, copied) = unsafe newElements._copyContents(initializing: gap) + precondition( + it.next() == nil && copied == newCount, + "Broken Collection: count doesn't match contents") + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given collection. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length collection as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @inlinable + @inline(__always) + public mutating func replaceSubrange( + _ subrange: Range, + copying newElements: __owned some Collection + ) { + _replaceSubrange( + subrange, copyingCollection: newElements, newCount: newElements.count) + } + + @available(SwiftStdlib 6.2, *) + @inlinable + public mutating func _replaceSubrange< + C: Container & ~Copyable & ~Escapable + >( + _ subrange: Range, + copyingContainer newElements: borrowing C, + newCount: Int + ) { + let gap = unsafe _gapForReplacement(of: subrange, withNewCount: newCount) + let (copied, end) = unsafe gap._initializePrefix(copying: newElements) + precondition( + copied == newCount && end == newElements.endIndex, + "Broken Container: count doesn't match contents") + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given container. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length container as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(`self.count` + `newElements.count`) + @available(SwiftStdlib 6.2, *) + @inlinable + @inline(__always) + public mutating func replaceSubrange< + C: Container & ~Copyable & ~Escapable + >( + _ subrange: Range, + copying newElements: borrowing C + ) { + _replaceSubrange( + subrange, copyingContainer: newElements, newCount: newElements.count) + } + + /// Replaces the specified subrange of elements by copying the elements of + /// the given container. + /// + /// This method has the effect of removing the specified range of elements + /// from the array and inserting the new elements starting at the same + /// location. The number of new elements need not match the number of elements + /// being removed. + /// + /// If the capacity of the array isn't sufficient to accommodate the new + /// elements, then this method triggers a runtime error. + /// + /// If you pass a zero-length range as the `subrange` parameter, this method + /// inserts the elements of `newElements` at `subrange.lowerBound`. Calling + /// the `insert(copying:at:)` method instead is preferred in this case. + /// + /// Likewise, if you pass a zero-length container as the `newElements` + /// parameter, this method removes the elements in the given subrange + /// without replacement. Calling the `removeSubrange(_:)` method instead is + /// preferred in this case. + /// + /// - Parameters: + /// - subrange: The subrange of the array to replace. The bounds of + /// the range must be valid indices in the array. + /// - newElements: The new elements to copy into the collection. + /// + /// - Complexity: O(*n* + *m*), where *n* is count of this array and + /// *m* is the count of `newElements`. + @available(SwiftStdlib 6.2, *) + @inlinable + @inline(__always) + public mutating func replaceSubrange< + C: Container & Collection + >( + _ subrange: Range, + copying newElements: C + ) { + _replaceSubrange( + subrange, copyingContainer: newElements, newCount: newElements.count) + } +} diff --git a/Sources/Future/CMakeLists.txt b/Sources/Future/CMakeLists.txt new file mode 100644 index 000000000..bf7e94ec7 --- /dev/null +++ b/Sources/Future/CMakeLists.txt @@ -0,0 +1,38 @@ +#[[ +This source file is part of the Swift Collections Open Source Project + +Copyright (c) 2024 - 2025 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 +#]] + +add_library(Future + "Span/LifetimeOverride.swift" + "Span/MutableRawSpan.swift" + "Span/MutableSpan.swift" + "Span/MutableSpanSlicing.swift" + "Span/OutputSpan.swift" + "Span/SpanExtensions.swift" + "Span/StdlibOutputSpanExtensions.swift" + "Span/UnsafeBufferPointer+Additions.swift" + "Containers/Container.swift" + "Containers/DynamicArray.swift" + "Containers/NewArray.swift" + "Containers/RigidArray.swift" + "Containers/Shared.swift" + "Borrow.swift" + "Box.swift" + "ContiguousStorage.swift" + "Inout.swift" + "Span+Iterator.swift" + "StdlibAdditions.swift" +) + +target_link_libraries(Future PRIVATE + InternalCollectionsUtilities) +set_target_properties(Future PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) + +_install_target(Future) +set_property(GLOBAL APPEND PROPERTY SWIFT_COLLECTIONS_EXPORTS Future) diff --git a/Sources/Future/Containers/BidirectionalContainer.swift b/Sources/Future/Containers/BidirectionalContainer.swift new file mode 100644 index 000000000..401658a98 --- /dev/null +++ b/Sources/Future/Containers/BidirectionalContainer.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +@available(SwiftStdlib 6.2, *) +public protocol BidirectionalContainer: Container, ~Copyable, ~Escapable { + func index(before i: Index) -> Index + func formIndex(before i: inout Index) + + @lifetime(borrow self) + func previousSpan(before index: inout Index) -> Span +} + +@available(SwiftStdlib 6.2, *) +extension BidirectionalContainer where Self: ~Copyable & ~Escapable { + @inlinable + @lifetime(borrow self) + public func previousSpan(before index: inout Index, maximumCount: Int) -> Span { + var span = previousSpan(before: &index) + if span.count > maximumCount { + // Index remains within the same span, so offseting it is expected to be quick + index = self.index(index, offsetBy: span.count - maximumCount) + span = span._extracting(last: maximumCount) + } + return span + } +} diff --git a/Sources/Future/Containers/Container.swift b/Sources/Future/Containers/Container.swift new file mode 100644 index 000000000..948292b98 --- /dev/null +++ b/Sources/Future/Containers/Container.swift @@ -0,0 +1,325 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +@available(SwiftStdlib 6.2, *) +public protocol Container: ~Copyable, ~Escapable { + associatedtype Element: ~Copyable/* & ~Escapable*/ + associatedtype Index: Comparable + + var isEmpty: Bool { get } + var count: Int { get } + + var startIndex: Index { get } + var endIndex: Index { get } + + func index(after index: Index) -> Index + func formIndex(after i: inout Index) + func distance(from start: Index, to end: Index) -> Int + func index(_ index: Index, offsetBy n: Int) -> Index + func formIndex( + _ i: inout Index, + offsetBy distance: inout Int, + limitedBy limit: Index + ) + + #if compiler(>=9999) // FIXME: Needs borrow accessors + subscript(index: Index) -> Element { borrow } + #else + @lifetime(borrow self) + func borrowElement(at index: Index) -> Borrow + #endif + + /// Return a span over the container's storage that begins with the element at the given index, + /// and extends to the end of the contiguous storage chunk that contains it. On return, the index + /// is updated to address the next item following the end of the returned span. + /// + /// This method can be used to efficiently process the items of a container in bulk, by + /// directly iterating over its piecewise contiguous pieces of storage: + /// + /// var index = items.startIndex + /// while true { + /// let span = items.nextSpan(after: &index) + /// if span.isEmpty { break } + /// // Process items in `span` + /// } + /// + /// Note: The spans returned by this method are not guaranteed to be disjunct. Some containers + /// may use the same storage chunk (or parts of a storage chunk) multiple times, to repeat their + /// contents. + /// + /// Note: Repeated invocations of `nextSpan` on the same container and index are not guaranteed + /// to return identical results. (This is particularly the case with containers that can store + /// contents in their "inline" representation. Such containers may not always have + /// a unique address in memory; the locations of the spans exposed by this method may vary + /// between different borrows of the same container.) + /// + /// - Parameter index: A valid index in the container, including the end index. On return, this + /// index is advanced by the count of the resulting span, to simplify iteration. + /// - Returns: A span over contiguous storage that starts at the given index. If the input index + /// is the end index, then this returns an empty span. Otherwise the result is non-empty, + /// with its first element matching the element at the input index. + @lifetime(borrow self) + func nextSpan(after index: inout Index) -> Span + + // FIXME: Try a version where nextSpan takes an index range + + // FIXME: See if it makes sense to have a ~Escapable ValidatedIndex type, as a sort of non-self-driving iterator substitute + + // *** Requirements with default implementations *** + + // FIXME: Do we want these as standard requirements this time? + func index(alignedDown index: Index) -> Index + func index(alignedUp index: Index) -> Index + + func _customIndexOfEquatableElement(_ element: borrowing Element) -> Index?? + func _customLastIndexOfEquatableElement(_ element: borrowing Element) -> Index?? +} + +@available(SwiftStdlib 6.2, *) +extension Container where Self: ~Copyable & ~Escapable { + @inlinable + public func _defaultIndex(_ i: Index, advancedBy distance: Int) -> Index { + precondition( + distance >= 0, + "Only BidirectionalContainers can be advanced by a negative amount") + + var i = index(alignedDown: i) + var distance = distance + + // FIXME: This implementation can be wasteful for contiguous containers, + // as iterating over spans will overshoot the target immediately. + // Reintroducing `nextSpan(after:maximumCount:)` would help avoid + // having to use a second loop to refine the result, but it would + // complicate conformances. + + // Skip forward until we find the span that contains our target. + while distance > 0 { + var j = i + let span = self.nextSpan(after: &j) + precondition( + !span.isEmpty, + "Can't advance index beyond the end of the container") + guard span.count <= distance else { break } + i = j + distance &-= span.count + } + // Step through to find the precise target. + while distance > 0 { + self.formIndex(after: &i) + distance &-= 1 + } + return i + } + + @inlinable + public func _defaultDistance(from start: Index, to end: Index) -> Int { + var start = index(alignedDown: start) + let end = index(alignedDown: end) + if start > end { + return -_defaultDistance(from: end, to: start) + } + var count = 0 + while start != end { + count = count + 1 + formIndex(after: &start) + } + return count + } + + @inlinable + public func _defaultFormIndex( + _ i: inout Index, + advancedBy distance: inout Int, + limitedBy limit: Index + ) { + precondition( + distance >= 0, + "Only BidirectionalContainers can be advanced by a negative amount") + + i = index(alignedDown: i) + let limit = index(alignedDown: limit) + + // Skip forward until we find the span that contains our target. + while distance > 0 { + var j = i + let span = self.nextSpan(after: &j) + precondition( + !span.isEmpty, + "Can't advance index beyond the end of the container") + guard span.count <= distance, j < limit else { + break + } + i = j + distance &-= span.count + } + // Step through to find the precise target. + while distance != 0 { + if i == limit { + return + } + formIndex(after: &i) + distance &-= 1 + } + } +} + +@available(SwiftStdlib 6.2, *) +extension Container where Self: ~Copyable & ~Escapable { + @inlinable + public var isEmpty: Bool { + return startIndex == endIndex + } + + @inlinable + public func formIndex(after i: inout Index) { + i = self.index(after: i) + } + + @inlinable + public func distance(from start: Index, to end: Index) -> Int { + _defaultDistance(from: start, to: end) + } + + @inlinable + public func index(_ index: Index, offsetBy n: Int) -> Index { + _defaultIndex(index, advancedBy: n) + } + + @inlinable + public func formIndex( + _ i: inout Index, + offsetBy distance: inout Int, + limitedBy limit: Index + ) { + _defaultFormIndex(&i, advancedBy: &distance, limitedBy: limit) + } + + @inlinable + public func index(alignedDown index: Index) -> Index { index } + + @inlinable + public func index(alignedUp index: Index) -> Index { index } + + @inlinable + public func _customIndexOfEquatableElement(_ element: borrowing Element) -> Index?? { nil } + + @inlinable + public func _customLastIndexOfEquatableElement(_ element: borrowing Element) -> Index?? { nil } +} + +@available(SwiftStdlib 6.2, *) +extension Container where Self: Sequence { + @inlinable + public var underestimatedCount: Int { count } +} + +@available(SwiftStdlib 6.2, *) +extension Container where Self: Collection { + // Resolve ambiguities between default implementations between Collection + // and Container. + + @inlinable + public var underestimatedCount: Int { count } + + @inlinable + public var isEmpty: Bool { + return startIndex == endIndex + } + + @inlinable + public func formIndex(after i: inout Index) { + i = self.index(after: i) + } + + @inlinable + public func distance(from start: Index, to end: Index) -> Int { + _defaultDistance(from: start, to: end) + } + + @inlinable + public func index(_ index: Index, offsetBy n: Int) -> Index { + _defaultIndex(index, advancedBy: n) + } + + @inlinable + public func _customIndexOfEquatableElement(_ element: borrowing Element) -> Index?? { nil } + + @inlinable + public func _customLastIndexOfEquatableElement(_ element: borrowing Element) -> Index?? { nil } +} + +@available(SwiftStdlib 6.2, *) +extension Container where Self: ~Copyable & ~Escapable { + @inlinable + public subscript(index: Index) -> Element { + // This stopgap definition is standing in for the eventual subscript requirement with a + // borrowing accessor. + @lifetime(copy self) + unsafeAddress { + unsafe borrowElement(at: index)._pointer + } + } +} + +@available(SwiftStdlib 6.2, *) +extension Container where Self: ~Copyable & ~Escapable { + @inlinable + @lifetime(borrow self) + public func nextSpan(after index: inout Index, maximumCount: Int) -> Span { + let original = index + var span = nextSpan(after: &index) + if span.count > maximumCount { + span = span._extracting(first: maximumCount) + // Index remains within the same span, so offseting it is expected to be quick + index = self.index(original, offsetBy: maximumCount) + } + return span + } +} + + +#if false // DEMO +@available(SwiftStdlib 6.2, *) +extension Container where Self: ~Copyable & ~Escapable { + // This is just to demo the bulk iteration model + func forEachSpan(_ body: (Span) throws(E) -> Void) throws(E) { + var it = self.startIndex + while true { + let span = self.nextSpan(after: &it) + if span.isEmpty { break } + try body(span) + } + } + + // This is just to demo the bulk iteration model + func forEach(_ body: (borrowing Element) throws(E) -> Void) throws(E) { + var it = self.startIndex + while true { + let span = self.nextSpan(after: &it) + if span.isEmpty { break } + var i = 0 + while i < span.count { + unsafe try body(span[unchecked: i]) + i &+= 1 + } + } + } + + borrowing func borrowingMap( + _ transform: (borrowing Element) throws(E) -> U + ) throws(E) -> [U] { + var result: [U] = [] + result.reserveCapacity(count) + try self.forEach { value throws(E) in result.append(try transform(value)) } + return result + } +} +#endif diff --git a/Sources/Future/Containers/ContainerAlgorithms.swift b/Sources/Future/Containers/ContainerAlgorithms.swift new file mode 100644 index 000000000..d1c97467b --- /dev/null +++ b/Sources/Future/Containers/ContainerAlgorithms.swift @@ -0,0 +1,418 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2014 - 2025 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 +// +//===----------------------------------------------------------------------===// + +//MARK: - firstIndex(where:) + +@available(SwiftStdlib 6.2, *) +extension Container where Self: ~Copyable & ~Escapable { + @inlinable + internal func _containerFirstIndex( + where predicate: (borrowing Element) throws(E) -> Bool + ) throws(E) -> Index? { + var i = self.startIndex + while true { + let spanStart = i + let span = self.nextSpan(after: &i) + if span.isEmpty { break } + for offset in span.indices { + if try predicate(span[offset]) { + return self.index(spanStart, offsetBy: offset) + } + } + } + return nil + } + + /// Returns the first index that addresses an element of the container that + /// satisfies the given predicate. + /// + /// You can use the predicate to find an element of a type that doesn't + /// conform to the `Equatable` protocol or to find an element that matches + /// particular criteria. + /// + /// - Parameter predicate: A closure that takes an element as its argument + /// and returns a Boolean value that indicates whether the passed element + /// represents a match. + /// - Returns: The index of the first element for which `predicate` returns + /// `true`. If no elements in the container satisfy the given predicate, + /// returns `nil`. + /// + /// - Complexity: O(*n*), where *n* is the count of the container. + @_alwaysEmitIntoClient + @inline(__always) + public func firstIndex( + where predicate: (borrowing Element) throws(E) -> Bool + ) throws(E) -> Index? { + try _containerFirstIndex(where: predicate) + } +} + +@available(SwiftStdlib 6.2, *) +extension Container where Self: Collection { + /// Returns the first index that addresses an element of the container that + /// satisfies the given predicate. + /// + /// You can use the predicate to find an element of a type that doesn't + /// conform to the `Equatable` protocol or to find an element that matches + /// particular criteria. + /// + /// - Parameter predicate: A closure that takes an element as its argument + /// and returns a Boolean value that indicates whether the passed element + /// represents a match. + /// - Returns: The index of the first element for which `predicate` returns + /// `true`. If no elements in the container satisfy the given predicate, + /// returns `nil`. + /// + /// - Complexity: O(*n*), where *n* is the count of the container. + @_alwaysEmitIntoClient + @inline(__always) + public func firstIndex( + where predicate: (borrowing Element) throws(E) -> Bool + ) throws(E) -> Index? { + try _containerFirstIndex(where: predicate) + } +} + +//MARK: - firstIndex(of:) + +@available(SwiftStdlib 6.2, *) +extension Container where Element: Equatable { + @inlinable + internal func _containerFirstIndex(of element: Element) -> Index? { + if let result = _customIndexOfEquatableElement(element) { + return result + } + return firstIndex(where: { $0 == element }) + } + + /// Returns the first index where the specified value appears in the container. + /// + /// After using `firstIndex(of:)` to find the position of a particular element + /// in a container, you can use it to access the element by subscripting. + /// + /// - Parameter element: An element to search for in the container. + /// - Returns: The first index where `element` is found. If `element` is not + /// found in the container, returns `nil`. + /// + /// - Complexity: O(`count`) + @_alwaysEmitIntoClient + @inline(__always) + public func firstIndex(of element: Element) -> Index? { + _containerFirstIndex(of: element) + } +} + +@available(SwiftStdlib 6.2, *) +extension Container where Self: Collection, Element: Equatable { + /// Returns the first index where the specified value appears in the container. + /// + /// After using `firstIndex(of:)` to find the position of a particular element + /// in a container, you can use it to access the element by subscripting. + /// + /// - Parameter element: An element to search for in the container. + /// - Returns: The first index where `element` is found. If `element` is not + /// found in the container, returns `nil`. + /// + /// - Complexity: O(`count`) + @_alwaysEmitIntoClient + @inline(__always) + public func firstIndex(of element: Element) -> Index? { + _containerFirstIndex(of: element) + } +} + +//MARK: - lastIndex(where:) + +@available(SwiftStdlib 6.2, *) +extension BidirectionalContainer where Self: ~Copyable & ~Escapable { + @inlinable + internal func _containerLastIndex( + where predicate: (borrowing Element) throws(E) -> Bool + ) throws(E) -> Index? { + var i = self.endIndex + while true { + let span = self.previousSpan(before: &i) + if span.isEmpty { break } + var offset = span.count - 1 + while offset >= 0 { + if try predicate(span[offset]) { + return self.index(i, offsetBy: offset) + } + offset &-= 1 + } + } + return nil + } + + /// Returns the index of the last element in the container that matches the + /// given predicate. + /// + /// You can use the predicate to find an element of a type that doesn't + /// conform to the `Equatable` protocol or to find an element that matches + /// particular criteria. + /// + /// - Parameter predicate: A closure that takes an element as its argument + /// and returns a Boolean value that indicates whether the passed element + /// represents a match. + /// - Returns: The index of the last element in the container that matches + /// `predicate`, or `nil` if no elements match. + /// + /// - Complexity: O(*n*), where *n* is the count of the container. + @_alwaysEmitIntoClient + @inline(__always) + public func lastIndex( + where predicate: (borrowing Element) throws(E) -> Bool + ) throws(E) -> Index? { + try _containerLastIndex(where: predicate) + } +} + +@available(SwiftStdlib 6.2, *) +extension BidirectionalContainer where Self: BidirectionalCollection { + /// Returns the first index that addresses an element of the container that + /// satisfies the given predicate. + /// + /// You can use the predicate to find an element of a type that doesn't + /// conform to the `Equatable` protocol or to find an element that matches + /// particular criteria. + /// + /// - Parameter predicate: A closure that takes an element as its argument + /// and returns a Boolean value that indicates whether the passed element + /// represents a match. + /// - Returns: The index of the first element for which `predicate` returns + /// `true`. If no elements in the container satisfy the given predicate, + /// returns `nil`. + /// + /// - Complexity: O(*n*), where *n* is the count of the container. + @_alwaysEmitIntoClient + @inline(__always) + public func lastIndex( + where predicate: (borrowing Element) throws(E) -> Bool + ) throws(E) -> Index? { + try _containerLastIndex(where: predicate) + } +} + +//MARK: - lastIndex(of:) + +@available(SwiftStdlib 6.2, *) +extension BidirectionalContainer where Element: Equatable { + @inlinable + internal func _containerLastIndex(of element: Element) -> Index? { + if let result = _customLastIndexOfEquatableElement(element) { + return result + } + return self._containerLastIndex(where: { $0 == element }) + } + + /// Returns the first index where the specified value appears in the container. + /// + /// After using `firstIndex(of:)` to find the position of a particular element + /// in a container, you can use it to access the element by subscripting. + /// + /// - Parameter element: An element to search for in the container. + /// - Returns: The first index where `element` is found. If `element` is not + /// found in the container, returns `nil`. + /// + /// - Complexity: O(`count`) + @_alwaysEmitIntoClient + @inline(__always) + public func lastIndex(of element: Element) -> Index? { + _containerLastIndex(of: element) + } +} + +@available(SwiftStdlib 6.2, *) +extension BidirectionalContainer where Self: BidirectionalCollection, Element: Equatable { + /// Returns the first index where the specified value appears in the container. + /// + /// After using `firstIndex(of:)` to find the position of a particular element + /// in a container, you can use it to access the element by subscripting. + /// + /// - Parameter element: An element to search for in the container. + /// - Returns: The first index where `element` is found. If `element` is not + /// found in the container, returns `nil`. + /// + /// - Complexity: O(`count`) + @_alwaysEmitIntoClient + @inline(__always) + public func lastIndex(of element: Element) -> Index? { + _containerLastIndex(of: element) + } +} + +//MARK: - elementsEqual(_:,by:) + +@available(SwiftStdlib 6.2, *) +extension Container where Self: ~Copyable & ~Escapable { + @inlinable + internal func _spanwiseZip( + state: inout State, + with other: borrowing Other, + by process: (inout State, Span, Span) throws(E) -> Bool + ) throws(E) -> (Index, Other.Index) { + var i = self.startIndex // End index of the current span in `self` + var j = other.startIndex // End index of the current span in `other` + var a: Span = .empty + var b: Span = .empty + var pi = i // Start index of the original span we're currently processing in `self` + var pj = j // Start index of the original span we're currently processing in `other` + var ca = 0 // Count of the original span we're currently processing in `self` + var cb = 0 // Count of the original span we're currently processing in `other` + loop: + while true { + if a.isEmpty { + pi = i + a = self.nextSpan(after: &i) + ca = a.count + } + if b.isEmpty { + pj = j + b = other.nextSpan(after: &j) + cb = b.count + } + if a.isEmpty || b.isEmpty { + return ( + (a.isEmpty ? i : self.index(pi, offsetBy: ca - a.count)), + (b.isEmpty ? j : other.index(pj, offsetBy: cb - b.count))) + } + + let c = Swift.min(a.count, b.count) + guard try process(&state, a._extracting(first: c), b._extracting(first: c)) else { + return ( + (c == a.count ? i : self.index(pi, offsetBy: c)), + (c == b.count ? j : other.index(pj, offsetBy: c))) + } + a = a._extracting(droppingFirst: c) + b = b._extracting(droppingFirst: c) + } + } + + @inlinable + internal func _containerElementsEqual( + _ other: borrowing Other, + by areEquivalent: (borrowing Element, borrowing Other.Element) throws(E) -> Bool + ) throws(E) -> Bool { + var result = true + let (i, j) = try _spanwiseZip(state: &result, with: other) { state, a, b throws(E) in + assert(a.count == b.count) + for i in a.indices { + guard try areEquivalent(a[i], b[i]) else { + state = false + return false + } + } + return true + } + return result && i == self.endIndex && j == other.endIndex + } + + /// Returns a Boolean value indicating whether this container and another + /// container contain equivalent elements in the same order, using the given + /// predicate as the equivalence test. + /// + /// The predicate must form an *equivalence relation* over the elements. That + /// is, for any elements `a`, `b`, and `c`, the following conditions must + /// hold: + /// + /// - `areEquivalent(a, a)` is always `true`. (Reflexivity) + /// - `areEquivalent(a, b)` implies `areEquivalent(b, a)`. (Symmetry) + /// - If `areEquivalent(a, b)` and `areEquivalent(b, c)` are both `true`, then + /// `areEquivalent(a, c)` is also `true`. (Transitivity) + /// + /// - Parameters: + /// - other: A container to compare to this container. + /// - areEquivalent: A predicate that returns `true` if its two arguments + /// are equivalent; otherwise, `false`. + /// - Returns: `true` if this container and `other` contain equivalent items, + /// using `areEquivalent` as the equivalence test; otherwise, `false.` + /// + /// - Complexity: O(*m*), where *m* is the count of the smaller of the input containers. + @_alwaysEmitIntoClient + public func elementsEqual( + _ other: borrowing Other, + by areEquivalent: (borrowing Element, borrowing Other.Element) throws(E) -> Bool + ) throws(E) -> Bool { + try _containerElementsEqual(other, by: areEquivalent) + } +} + +@available(SwiftStdlib 6.2, *) +extension Container where Self: Sequence { + /// Returns a Boolean value indicating whether this container and another + /// container contain equivalent elements in the same order, using the given + /// predicate as the equivalence test. + /// + /// The predicate must form an *equivalence relation* over the elements. That + /// is, for any elements `a`, `b`, and `c`, the following conditions must + /// hold: + /// + /// - `areEquivalent(a, a)` is always `true`. (Reflexivity) + /// - `areEquivalent(a, b)` implies `areEquivalent(b, a)`. (Symmetry) + /// - If `areEquivalent(a, b)` and `areEquivalent(b, c)` are both `true`, then + /// `areEquivalent(a, c)` is also `true`. (Transitivity) + /// + /// - Parameters: + /// - other: A container to compare to this container. + /// - areEquivalent: A predicate that returns `true` if its two arguments + /// are equivalent; otherwise, `false`. + /// - Returns: `true` if this container and `other` contain equivalent items, + /// using `areEquivalent` as the equivalence test; otherwise, `false.` + /// + /// - Complexity: O(*m*), where *m* is the count of the smaller of the input containers. + @_alwaysEmitIntoClient + public func elementsEqual & Sequence, E: Error>( + _ other: Other, + by areEquivalent: (borrowing Element, borrowing Element) throws(E) -> Bool + ) throws(E) -> Bool { + try _containerElementsEqual(other, by: areEquivalent) + } +} +//MARK: - elementsEqual(_:) + + +@available(SwiftStdlib 6.2, *) +extension Container where Self: ~Copyable & ~Escapable, Element: Equatable { + /// Returns a Boolean value indicating whether this container and another + /// container contain the same elements in the same order. + /// + /// - Parameter other: A container whose contents to compare to this container. + /// - Returns: `true` if this container and `other` contain the same elements + /// in the same order. + /// + /// - Complexity: O(*m*), where *m* is the count of the smaller of the input containers. + @_alwaysEmitIntoClient + @inline(__always) + public func elementsEqual & ~Copyable & ~Escapable>( + _ other: borrowing Other + ) -> Bool { + self._containerElementsEqual(other, by: { $0 == $1 }) + } +} + +@available(SwiftStdlib 6.2, *) +extension Container where Self: Sequence, Element: Equatable { + /// Returns a Boolean value indicating whether this container and another + /// container contain the same elements in the same order. + /// + /// - Parameter other: A container whose contents to compare to this container. + /// - Returns: `true` if this container and `other` contain the same elements + /// in the same order. + /// + /// - Complexity: O(*m*), where *m* is the count of the smaller of the input containers. + @_alwaysEmitIntoClient + @inline(__always) + public func elementsEqual & Container>( + _ other: Other + ) -> Bool { + self._containerElementsEqual(other, by: { $0 == $1 }) + } +} diff --git a/Sources/Future/Containers/ContiguousContainer.swift b/Sources/Future/Containers/ContiguousContainer.swift new file mode 100644 index 000000000..9f25b2b38 --- /dev/null +++ b/Sources/Future/Containers/ContiguousContainer.swift @@ -0,0 +1,57 @@ +//===---------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +@available(SwiftStdlib 6.2, *) +public protocol ContiguousContainer + : RandomAccessContainer, ~Copyable, ~Escapable +{ + var span: Span { @lifetime(borrow self) get } +} + +@available(SwiftStdlib 6.2, *) +extension ContiguousContainer +where Self: ~Copyable & ~Escapable, Index == Int { + @inlinable + @lifetime(borrow self) + public func borrowElement(at index: Index) -> Borrow { + span.borrowElement(at: index - startIndex) + } + + @inlinable + @lifetime(borrow self) + public func nextSpan(after index: inout Index) -> Span { + var i = index - startIndex + let result = span.nextSpan(after: &i) + index = i + startIndex + return result + } + + @inlinable + @lifetime(borrow self) + public func previousSpan(before index: inout Index) -> Span { + var i = index - startIndex + let result = span.previousSpan(before: &i) + index = i + startIndex + return result + } +} + +@available(SwiftStdlib 6.2, *) +extension Array: ContiguousContainer {} + +#if compiler(>=6.2) && $InoutLifetimeDependence // FIXME: Crashes older 6.2 compilers. +@available(SwiftStdlib 6.2, *) +extension ContiguousArray: ContiguousContainer {} +#endif + +@available(SwiftStdlib 6.2, *) +extension CollectionOfOne: ContiguousContainer {} diff --git a/Sources/Future/Containers/MutableContainer.swift b/Sources/Future/Containers/MutableContainer.swift new file mode 100644 index 000000000..ee846f245 --- /dev/null +++ b/Sources/Future/Containers/MutableContainer.swift @@ -0,0 +1,146 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +@available(SwiftStdlib 6.2, *) +public protocol MutableContainer: Container, ~Copyable, ~Escapable { +#if compiler(>=9999) // FIXME: Needs borrow accessors + subscript(index: Index) -> Element { borrow mutate } +#else + +#if compiler(>=6.2) && $InoutLifetimeDependence + @lifetime(&self) +#else + @lifetime(borrow self) +#endif + @lifetime(self: copy self) + mutating func mutateElement(at index: Index) -> Inout +#endif + +#if compiler(>=6.2) && $InoutLifetimeDependence + @lifetime(&self) +#else + @lifetime(borrow self) +#endif + @lifetime(self: copy self) + mutating func nextMutableSpan(after index: inout Index) -> MutableSpan + + // FIXME: What about previousMutableSpan? + + @lifetime(self: copy self) + mutating func swapAt(_ i: Index, _ j: Index) +} + +@available(SwiftStdlib 6.2, *) +extension MutableContainer where Self: ~Copyable & ~Escapable { +#if false // FIXME: This has flagrant exclusivity violations. +#if compiler(>=6.2) && $InoutLifetimeDependence + @lifetime(&self) +#else + @lifetime(borrow self) +#endif + public mutating func nextMutableSpan( + after index: inout Index, maximumCount: Int + ) -> MutableSpan { + let start = index + do { + let span = self.nextMutableSpan(after: &index) + guard span.count > maximumCount else { return span } + } + // Index remains within the same span, so offseting it is expected to be quick + let end = self.index(start, offsetBy: maximumCount) + index = start + var span = self.nextMutableSpan(after: &index) + let extract = span.extracting(first: maximumCount) // FIXME: Oops + index = end + return extract + } +#endif + +#if compiler(>=6.2) && $InoutLifetimeDependence // FIXME: Crashes older 6.2 compilers. + @lifetime(self: copy self) + public mutating func withNextMutableSpan< + E: Error, R: ~Copyable + >( + after index: inout Index, maximumCount: Int, + body: (inout MutableSpan) throws(E) -> R + ) throws(E) -> R { + // FIXME: We don't want this to be closure-based, but MutableSpan cannot be sliced "in place". + let start = index + do { + var span = self.nextMutableSpan(after: &index) + if span.count <= maximumCount { + return try body(&span) + } + } + // Try again, but figure out what our target index is first. + // Index remains within the same span, so offseting it is expected to be quick + index = self.index(start, offsetBy: maximumCount) + var i = start + var span = self.nextMutableSpan(after: &i) + var extract = span.extracting(first: maximumCount) + return try body(&extract) + } +#endif + +#if compiler(>=6.2) && $InoutLifetimeDependence + @inlinable + public subscript(index: Index) -> Element { + @lifetime(borrow self) + unsafeAddress { + unsafe borrowElement(at: index)._pointer + } + + @lifetime(&self) + unsafeMutableAddress { + unsafe mutateElement(at: index)._pointer + } + } +#else + @inlinable + public subscript(index: Index) -> Element { + @lifetime(borrow self) + unsafeAddress { + unsafe borrowElement(at: index)._pointer + } + + @lifetime(borrow self) + unsafeMutableAddress { + unsafe mutateElement(at: index)._pointer + } + } +#endif +} + +@available(SwiftStdlib 6.2, *) +extension MutableContainer where Self: ~Copyable & ~Escapable { + /// Moves all elements satisfying `isSuffixElement` into a suffix of the + /// container, returning the start position of the resulting suffix. + /// + /// - Complexity: O(*n*) where n is the count of the container. + @inlinable + @lifetime(self: copy self) + internal mutating func _halfStablePartition( + isSuffixElement: (borrowing Element) throws(E) -> Bool + ) throws(E) -> Index { + guard var i = try firstIndex(where: isSuffixElement) + else { return endIndex } + + var j = index(after: i) + while j != endIndex { + if try !isSuffixElement(self[j]) { + swapAt(i, j) + formIndex(after: &i) + } + formIndex(after: &j) + } + return i + } +} diff --git a/Sources/Future/Containers/RandomAccessContainer.swift b/Sources/Future/Containers/RandomAccessContainer.swift new file mode 100644 index 000000000..7da0a0c7f --- /dev/null +++ b/Sources/Future/Containers/RandomAccessContainer.swift @@ -0,0 +1,161 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +@available(SwiftStdlib 6.2, *) +public protocol RandomAccessContainer +: BidirectionalContainer, ~Copyable, ~Escapable {} + +@available(SwiftStdlib 6.2, *) +extension RandomAccessContainer +where Self: ~Copyable & ~Escapable, Index: Strideable, Index.Stride == Int +{ + @inlinable + public var isEmpty: Bool { startIndex == endIndex } + + @inlinable + public var count: Int { startIndex.distance(to: endIndex) } + + @inlinable + public func index(after index: Index) -> Index { + // Note: Range checks are deferred until element access. + index.advanced(by: 1) + } + + @inlinable + public func index(before index: Index) -> Index { + // Note: Range checks are deferred until element access. + index.advanced(by: -1) + } + + @inlinable + public func formIndex(after index: inout Index) { + // Note: Range checks are deferred until element access. + index = index.advanced(by: 1) + } + + @inlinable + public func formIndex(before index: inout Index) { + // Note: Range checks are deferred until element access. + index = index.advanced(by: -1) + } + + @inlinable + public func distance(from start: Index, to end: Index) -> Int { + // Note: Range checks are deferred until element access. + start.distance(to: end) + } + + @inlinable + public func index(_ index: Index, offsetBy n: Int) -> Index { + // Note: Range checks are deferred until element access. + index.advanced(by: n) + } + + @inlinable + public func formIndex( + _ index: inout Index, offsetBy distance: inout Index.Stride, limitedBy limit: Index + ) { + // Note: Range checks are deferred until element access. + index.advance(by: &distance, limitedBy: limit) + } + + + @lifetime(borrow self) + @inlinable + public func nextSpan( + after index: inout Index, maximumCount: Int + ) -> Span { + precondition(maximumCount >= 0, "Maximum count must be non-negative") + let span = self.nextSpan(after: &index) + if span.count <= maximumCount { return span } + index = index.advanced(by: maximumCount - span.count) + return span._extracting(first: maximumCount) + } +} + +// Disambiguate parallel extensions on RandomAccessContainer and RandomAccessCollection + +@available(SwiftStdlib 6.2, *) +extension RandomAccessCollection +where + Self: RandomAccessContainer, + Index: Strideable, + Index.Stride == Int, + Indices == Range +{ + @inlinable + public var isEmpty: Bool { startIndex == endIndex } + + @inlinable + public var count: Int { endIndex - startIndex } + + @inlinable + public func index(after index: Index) -> Index { + // Note: Range checks are deferred until element access. + index.advanced(by: 1) + } + + @inlinable + public func index(before index: Index) -> Index { + // Note: Range checks are deferred until element access. + index.advanced(by: -1) + } + + @inlinable + public func formIndex(after index: inout Index) { + // Note: Range checks are deferred until element access. + index = index.advanced(by: 1) + } + + @inlinable + public func formIndex(before index: inout Index) { + // Note: Range checks are deferred until element access. + index = index.advanced(by: -1) + } + + @inlinable + public func distance(from start: Index, to end: Index) -> Int { + // Note: Range checks are deferred until element access. + start.distance(to: end) + } + + @inlinable + public func index(_ index: Index, offsetBy n: Int) -> Index { + // Note: Range checks are deferred until element access. + index.advanced(by: n) + } +} + +extension Strideable { + @inlinable + public mutating func advance(by distance: inout Stride, limitedBy limit: Self) { + if distance >= 0 { + guard limit >= self else { + self = self.advanced(by: distance) + distance = 0 + return + } + let d = Swift.min(distance, self.distance(to: limit)) + self = self.advanced(by: d) + distance -= d + } else { + guard limit <= self else { + self = self.advanced(by: distance) + distance = 0 + return + } + let d = Swift.max(distance, self.distance(to: limit)) + self = self.advanced(by: d) + distance -= d + } + } +} + diff --git a/Sources/Future/Containers/RepeatingContainer.swift b/Sources/Future/Containers/RepeatingContainer.swift new file mode 100644 index 000000000..712dbdcaa --- /dev/null +++ b/Sources/Future/Containers/RepeatingContainer.swift @@ -0,0 +1,115 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +#if false // FIXME: Disabled until I have time to track down a flaky compiler crash + +#if false // FIXME: This is what we'd want +import Builtin + +@frozen +@_addressableForDependencies +public struct RepeatingContainer: ~Copyable { + @usableFromInline + internal var _count: Int + + @usableFromInline + internal var _item: Element + + public init(repeating item: consuming Element, count: Int) { + self._item = item + self._count = count + } +} +#else +@frozen +public struct RepeatingContainer: ~Copyable { + @usableFromInline + internal var _count: Int + + @usableFromInline + internal var _contents: Box + + public init(repeating item: consuming Element, count: Int) { + self._count = count + self._contents = Box(item) + } +} +#endif + +extension RepeatingContainer where Element: ~Copyable { + @inlinable + public var startIndex: Int { 0 } + + @inlinable + public var endIndex: Int { _count } +} + +#if false // FIXME: This is what we'd want +@available(SwiftStdlib 6.2, *) +extension RepeatingContainer: RandomAccessContainer where Element: ~Copyable { + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + Borrow(self._item) + } + + @lifetime(borrow self) + public func nextSpan(after index: inout Int) -> Span { + let c = _count + precondition(index >= 0 && index <= c, "Index out of bounds") + guard index < c else { return .empty } + index += 1 + // FIXME: Oh my + let pointer = unsafe UnsafePointer(Builtin.unprotectedAddressOfBorrow(self._item)) + let span = unsafe Span(_unsafeStart: pointer, count: 1) + return unsafe _overrideLifetime(span, borrowing: self) + } + + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + let c = _count + precondition(index >= 0 && index <= c, "Index out of bounds") + guard index > 0 else { return .empty } + index -= 1 + // FIXME: Oh my + let pointer = unsafe UnsafePointer(Builtin.addressOfBorrow(self._item)) + let span = unsafe Span(_unsafeStart: pointer, count: 1) + return unsafe _overrideLifetime(span, borrowing: self) + } +} +#else +@available(SwiftStdlib 6.2, *) +extension RepeatingContainer: RandomAccessContainer where Element: ~Copyable { + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + _contents.borrow() + } + + @lifetime(borrow self) + public func nextSpan(after index: inout Int) -> Span { + let c = _count + precondition(index >= 0 && index <= c, "Index out of bounds") + guard index < c else { return .empty } + index += 1 + return _contents.span + } + + @lifetime(borrow self) + public func previousSpan(before index: inout Int) -> Span { + let c = _count + precondition(index >= 0 && index <= c, "Index out of bounds") + guard index > 0 else { return .empty } + index -= 1 + return _contents.span + } +} +#endif + +#endif diff --git a/Sources/Future/Containers/UnsafeBufferPointer+ContiguousContainer.swift b/Sources/Future/Containers/UnsafeBufferPointer+ContiguousContainer.swift new file mode 100644 index 000000000..a6206e9b5 --- /dev/null +++ b/Sources/Future/Containers/UnsafeBufferPointer+ContiguousContainer.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +@available(SwiftStdlib 6.2, *) +extension UnsafeBufferPointer: @unsafe ContiguousContainer +where Element: ~Copyable +{ + public var span: Span { + @inlinable + @lifetime(borrow self) + get { + unsafe Span(_unsafeElements: self) + } + } + + @inlinable + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + precondition(index >= 0 && index < count, "Index out of bounds") + let ptr = unsafe baseAddress.unsafelyUnwrapped + index + return unsafe Borrow(unsafeAddress: ptr, borrowing: self) + } +} diff --git a/Sources/Future/Containers/UnsafeMutableBufferPointer+ContiguousContainer.swift b/Sources/Future/Containers/UnsafeMutableBufferPointer+ContiguousContainer.swift new file mode 100644 index 000000000..50a1bcab5 --- /dev/null +++ b/Sources/Future/Containers/UnsafeMutableBufferPointer+ContiguousContainer.swift @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +@available(SwiftStdlib 6.2, *) +extension UnsafeMutableBufferPointer: @unsafe ContiguousContainer +where Element: ~Copyable +{ + /// The contents of `self` must not be mutated while the returned span exists. + public var span: Span { + @inlinable + @lifetime(borrow self) + get { + unsafe Span(_unsafeElements: self) + } + } + + @inlinable + @lifetime(borrow self) + public func borrowElement(at index: Int) -> Borrow { + precondition(index >= 0 && index < count, "Index out of bounds") + let ptr = unsafe baseAddress.unsafelyUnwrapped + index + return unsafe Borrow(unsafeAddress: ptr, borrowing: self) + } +} diff --git a/Sources/Future/Span/LifetimeOverride.swift b/Sources/Future/Span/LifetimeOverride.swift new file mode 100644 index 000000000..8faa9e6cd --- /dev/null +++ b/Sources/Future/Span/LifetimeOverride.swift @@ -0,0 +1,71 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` with a lifetime dependency +/// on the caller's borrow scope of the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@lifetime(borrow source) +public func _overrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, borrowing source: borrowing U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} + +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` that inherits +/// all lifetime dependencies from the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@lifetime(copy source) +public func _overrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, copying source: borrowing U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} + +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` with a lifetime dependency +/// on the caller's exclusive borrow scope of the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +#if compiler(>=6.2) && $InoutLifetimeDependence +@lifetime(&source) +#else +@lifetime(borrow source) +#endif +public func _overrideLifetime< + T: ~Copyable & ~Escapable, + U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, + mutating source: inout U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} diff --git a/Sources/Future/Span/OutputSpan.swift b/Sources/Future/Span/OutputSpan.swift new file mode 100644 index 000000000..bf4fe6574 --- /dev/null +++ b/Sources/Future/Span/OutputSpan.swift @@ -0,0 +1,422 @@ +//===--- OutputSpan.swift -------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +#if false // FIXME: Revive +// OutputSpan represents a span of memory which contains +// a variable number of `Element` instances, followed by uninitialized memory. +@safe +@frozen +@available(macOS 9999, *) +public struct OutputSpan: ~Copyable, ~Escapable { + @usableFromInline + internal let _pointer: UnsafeMutableRawPointer? + + public let capacity: Int + + @usableFromInline + internal var _initialized: Int = 0 + + @_alwaysEmitIntoClient + internal func _start() -> UnsafeMutableRawPointer { + unsafe _pointer.unsafelyUnwrapped + } + + @_alwaysEmitIntoClient + public var available: Int { capacity &- _initialized } + + @_alwaysEmitIntoClient + public var count: Int { _initialized } + + @_alwaysEmitIntoClient + public var isEmpty: Bool { _initialized == 0 } + + deinit { + if _initialized > 0 { + unsafe _start().withMemoryRebound( + to: Element.self, capacity: _initialized + ) { + [ workaround = _initialized ] in + _ = unsafe $0.deinitialize(count: workaround) + } + } + } + + @_alwaysEmitIntoClient + @lifetime(borrow start) + internal init( + _unchecked start: UnsafeMutableRawPointer?, + capacity: Int, + initialized: Int + ) { + unsafe _pointer = start + self.capacity = capacity + _initialized = initialized + } +} + +@available(macOS 9999, *) +@available(*, unavailable) +extension OutputSpan: Sendable {} + +@available(macOS 9999, *) +extension OutputSpan where Element: ~Copyable { + + @_alwaysEmitIntoClient + @lifetime(borrow buffer) + internal init( + _unchecked buffer: UnsafeMutableBufferPointer, + initialized: Int + ) { + unsafe _pointer = .init(buffer.baseAddress) + capacity = buffer.count + _initialized = initialized + } + + @_alwaysEmitIntoClient + @lifetime(borrow buffer) + public init( + _initializing buffer: UnsafeMutableBufferPointer, + initialized: Int = 0 + ) { + precondition( + ((Int(bitPattern: buffer.baseAddress) & + (MemoryLayout.alignment&-1)) == 0), + "baseAddress must be properly aligned to access Element" + ) + unsafe self.init(_unchecked: buffer, initialized: initialized) + } + + @_alwaysEmitIntoClient + @lifetime(borrow pointer) + public init( + _initializing pointer: UnsafeMutablePointer, + capacity: Int, + initialized: Int = 0 + ) { + precondition(capacity >= 0, "Capacity must be 0 or greater") + let buf = unsafe UnsafeMutableBufferPointer(start: pointer, count: capacity) + let os = unsafe OutputSpan(_initializing: buf, initialized: initialized) + self = unsafe _overrideLifetime(os, borrowing: pointer) + } +} + +@available(macOS 9999, *) +extension OutputSpan { + + @_alwaysEmitIntoClient + @lifetime(borrow buffer) + public init( + _initializing buffer: borrowing Slice>, + initialized: Int = 0 + ) { + let rebased = unsafe UnsafeMutableBufferPointer(rebasing: buffer) + let os = unsafe OutputSpan(_initializing: rebased, initialized: 0) + self = unsafe unsafe _overrideLifetime(os, borrowing: buffer) + } +} + +@available(macOS 9999, *) +extension OutputSpan where Element: BitwiseCopyable { + + @_alwaysEmitIntoClient + @lifetime(borrow bytes) + public init( + _initializing bytes: UnsafeMutableRawBufferPointer, + initialized: Int = 0 + ) { + precondition( + ((Int(bitPattern: bytes.baseAddress) & + (MemoryLayout.alignment&-1)) == 0), + "baseAddress must be properly aligned to access Element" + ) + let (byteCount, stride) = (bytes.count, MemoryLayout.stride) + let (count, remainder) = byteCount.quotientAndRemainder(dividingBy: stride) + precondition(remainder == 0, "Span must contain a whole number of elements") + let pointer = bytes.baseAddress + let os = unsafe OutputSpan( + _unchecked: pointer, capacity: count, initialized: initialized + ) + self = unsafe _overrideLifetime(os, borrowing: bytes) + } + + @_alwaysEmitIntoClient + @lifetime(borrow pointer) + public init( + _initializing pointer: UnsafeMutableRawPointer, + capacity: Int, + initialized: Int = 0 + ) { + precondition(capacity >= 0, "Capacity must be 0 or greater") + let buf = unsafe UnsafeMutableRawBufferPointer(start: pointer, count: capacity) + let os = unsafe OutputSpan(_initializing: buf, initialized: initialized) + self = unsafe _overrideLifetime(os, borrowing: pointer) + } + + @_alwaysEmitIntoClient + @lifetime(borrow buffer) + public init( + _initializing buffer: borrowing Slice, + initialized: Int = 0 + ) { + let rebased = unsafe UnsafeMutableRawBufferPointer(rebasing: buffer) + let os = unsafe OutputSpan(_initializing: rebased, initialized: initialized) + self = unsafe _overrideLifetime(os, borrowing: buffer) + } +} + +@available(macOS 9999, *) +extension OutputSpan where Element: ~Copyable { + + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func append(_ value: consuming Element) { + precondition(_initialized < capacity, "Output buffer overflow") + let p = unsafe _start().advanced(by: _initialized&*MemoryLayout.stride) + unsafe p.initializeMemory(as: Element.self, to: value) + _initialized &+= 1 + } + + @_alwaysEmitIntoClient + public mutating func removeLast() -> Element? { + guard _initialized > 0 else { return nil } + _initialized &-= 1 + let p = unsafe _start().advanced(by: _initialized&*MemoryLayout.stride) + return unsafe p.withMemoryRebound(to: Element.self, capacity: 1, { unsafe $0.move() }) + } + + @_alwaysEmitIntoClient + public mutating func removeAll() { + _ = unsafe _start().withMemoryRebound(to: Element.self, capacity: _initialized) { + unsafe $0.deinitialize(count: _initialized) + } + _initialized = 0 + } +} + +//MARK: bulk-update functions +@available(macOS 9999, *) +extension OutputSpan { + + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func append(repeating repeatedValue: Element, count: Int) { + let available = capacity &- _initialized + precondition( + count <= available, + "destination span cannot contain number of elements requested." + ) + let offset = _initialized&*MemoryLayout.stride + let p = unsafe _start().advanced(by: offset) + unsafe p.withMemoryRebound(to: Element.self, capacity: count) { + unsafe $0.initialize(repeating: repeatedValue, count: count) + } + _initialized &+= count + } + + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func append( + from elements: S + ) -> S.Iterator where S: Sequence, S.Element == Element { + var iterator = elements.makeIterator() + append(from: &iterator) + return iterator + } + + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func append( + from elements: inout some IteratorProtocol + ) { + while _initialized < capacity { + guard let element = elements.next() else { break } + let p = unsafe _start().advanced(by: _initialized&*MemoryLayout.stride) + unsafe p.initializeMemory(as: Element.self, to: element) + _initialized &+= 1 + } + } + + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func append( + fromContentsOf source: some Collection + ) { + let void: Void? = source.withContiguousStorageIfAvailable { + append(fromContentsOf: unsafe Span(_unsafeElements: $0)) + } + if void != nil { + return + } + + let available = capacity &- _initialized + let tail = unsafe _start().advanced(by: _initialized&*MemoryLayout.stride) + var (iterator, copied) = + unsafe tail.withMemoryRebound(to: Element.self, capacity: available) { + let suffix = unsafe UnsafeMutableBufferPointer(start: $0, count: available) + return unsafe source._copyContents(initializing: suffix) + } + precondition( + iterator.next() == nil, + "destination span cannot contain every element from source." + ) + assert(_initialized + copied <= capacity) // invariant check + _initialized &+= copied + } + + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func append( + fromContentsOf source: Span + ) { + guard !source.isEmpty else { return } + precondition( + source.count <= available, + "destination span cannot contain every element from source." + ) + let tail = unsafe _start().advanced(by: _initialized&*MemoryLayout.stride) + _ = unsafe source.withUnsafeBufferPointer { + unsafe tail.initializeMemory( + as: Element.self, from: $0.baseAddress!, count: $0.count + ) + } + _initialized += source.count + } + + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func append(fromContentsOf source: borrowing MutableSpan) { + unsafe source.withUnsafeBufferPointer { unsafe append(fromContentsOf: $0) } + } +} + +@available(macOS 9999, *) +extension OutputSpan where Element: ~Copyable { + + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func moveAppend( + fromContentsOf source: consuming Self + ) { + guard !source.isEmpty else { return } + precondition( + source.count <= available, + "buffer cannot contain every element from source." + ) + let buffer = unsafe source.relinquishBorrowedMemory() + // we must now deinitialize the returned UMBP + let tail = unsafe _start().advanced(by: _initialized&*MemoryLayout.stride) + unsafe tail.moveInitializeMemory( + as: Element.self, from: buffer.baseAddress!, count: buffer.count + ) + _initialized &+= buffer.count + } + + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func moveAppend( + fromContentsOf source: UnsafeMutableBufferPointer + ) { + let source = unsafe OutputSpan(_initializing: source, initialized: source.count) + moveAppend(fromContentsOf: source) + } +} + +@available(macOS 9999, *) +extension OutputSpan { + + @_alwaysEmitIntoClient + @lifetime(self: copy self) + public mutating func moveAppend( + fromContentsOf source: Slice> + ) { + unsafe moveAppend( + fromContentsOf: UnsafeMutableBufferPointer(rebasing: source) + ) + } +} + +@available(macOS 9999, *) +extension OutputSpan where Element: BitwiseCopyable { +// TODO: alternative append() implementations for BitwiseCopyable elements +} + +@available(macOS 9999, *) +extension OutputSpan where Element: ~Copyable { + + @_alwaysEmitIntoClient + public var span: Span { + @lifetime(borrow self) + borrowing get { + let pointer = unsafe _pointer?.assumingMemoryBound(to: Element.self) + let buffer = unsafe UnsafeBufferPointer(start: pointer, count: _initialized) + let span = unsafe Span(_unsafeElements: buffer) + return unsafe _overrideLifetime(span, borrowing: self) + } + } + +#if compiler(>=6.2) && $InoutLifetimeDependence + @_alwaysEmitIntoClient + public var mutableSpan: MutableSpan { + @lifetime(&self) + mutating get { + let pointer = unsafe _pointer?.assumingMemoryBound(to: Element.self) + let buffer = unsafe UnsafeMutableBufferPointer( + start: pointer, count: _initialized + ) + let span = unsafe MutableSpan(_unsafeElements: buffer) + return unsafe _overrideLifetime(span, mutating: &self) + } + } +#else + @_alwaysEmitIntoClient + public var mutableSpan: MutableSpan { + @lifetime(borrow self) + mutating get { + let pointer = unsafe _pointer?.assumingMemoryBound(to: Element.self) + let buffer = unsafe UnsafeMutableBufferPointer( + start: pointer, count: _initialized + ) + let span = unsafe MutableSpan(_unsafeElements: buffer) + return unsafe _overrideLifetime(span, mutating: &self) + } + } +#endif +} + +@available(macOS 9999, *) +extension OutputSpan where Element: ~Copyable { + + @unsafe + @_alwaysEmitIntoClient + public consuming func relinquishBorrowedMemory( + ) -> UnsafeMutableBufferPointer { + let (start, count) = unsafe (self._pointer, self._initialized) + discard self + let typed = unsafe start?.bindMemory(to: Element.self, capacity: count) + return unsafe UnsafeMutableBufferPointer(start: typed, count: count) + } +} + +@available(macOS 9999, *) +extension OutputSpan where Element: BitwiseCopyable { + + @unsafe + @_alwaysEmitIntoClient + public consuming func relinquishBorrowedBytes( + ) -> UnsafeMutableRawBufferPointer { + let (start, count) = unsafe (self._pointer, self._initialized) + discard self + let byteCount = count&*MemoryLayout.stride + return unsafe UnsafeMutableRawBufferPointer(start: start, count: byteCount) + } +} +#endif diff --git a/Sources/Future/Span/Span+ContiguousContainer.swift b/Sources/Future/Span/Span+ContiguousContainer.swift new file mode 100644 index 000000000..6578e62e4 --- /dev/null +++ b/Sources/Future/Span/Span+ContiguousContainer.swift @@ -0,0 +1,90 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +import Builtin + +@available(SwiftStdlib 6.2, *) +extension Span: RandomAccessContainer where Element: ~Copyable { + @inlinable + public var startIndex: Int { 0 } + @inlinable + public var endIndex: Int { count } + + @inlinable + @lifetime(copy self) + public func borrowElement(at index: Int) -> Borrow { + unsafe Borrow(unsafeAddress: _unsafeAddressOfElement(index), copying: self) + } + + @lifetime(copy self) + public func nextSpan(after index: inout Index) -> Span { + precondition(index >= 0 && index <= count, "Index out of bounds") + defer { index = count } + return _extracting(unsafe Range(uncheckedBounds: (index, count))) + } + + @lifetime(copy self) + public func previousSpan(before index: inout Int) -> Span { + precondition(index >= 0 && index <= count, "Index out of bounds") + defer { index = 0 } + return _extracting(unsafe Range(uncheckedBounds: (0, index))) + } +} + +@available(SwiftStdlib 6.2, *) +extension Span: ContiguousContainer where Element: ~Copyable { + public var span: Self { + @lifetime(copy self) + get { self } + } +} + +@available(SwiftStdlib 6.2, *) +extension Span where Element: ~Copyable { + @usableFromInline + typealias _Components = (pointer: UnsafeRawPointer?, count: Int) + + // FIXME: This is *wildly* unsafe; remove it. + @unsafe + @_alwaysEmitIntoClient + @_transparent + @lifetime(copy self) + internal func _unsafelyExplode() -> _Components { + unsafe assert( + MemoryLayout.size == MemoryLayout<_Components>.size, + "Unexpected Span layout") + let immortal = unsafe _overrideLifetime(self, borrowing: immortalThing) + return unsafe Builtin.reinterpretCast(immortal) + } + + // FIXME: This is *wildly* unsafe; remove it. + @unsafe + @_alwaysEmitIntoClient + internal func _unsafeAddressOfElement( + unchecked position: Index + ) -> UnsafePointer { + let (start, _) = unsafe _unsafelyExplode() + let elementOffset = position &* MemoryLayout.stride + let address = unsafe start.unsafelyUnwrapped.advanced(by: elementOffset) + return unsafe address.assumingMemoryBound(to: Element.self) + } + + // FIXME: This is *wildly* unsafe; remove it. + @unsafe + @_alwaysEmitIntoClient + internal func _unsafeAddressOfElement( + _ position: Index + ) -> UnsafePointer { + precondition(position >= 0 && position < count, "Index out of bounds") + return unsafe _unsafeAddressOfElement(unchecked: position) + } +} + diff --git a/Sources/Future/Span/SpanExtensions.swift b/Sources/Future/Span/SpanExtensions.swift new file mode 100644 index 000000000..a1af10451 --- /dev/null +++ b/Sources/Future/Span/SpanExtensions.swift @@ -0,0 +1,107 @@ +//===--- SpanExtensions.swift ---------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +@usableFromInline +internal let immortalThing: [Void] = [] + +@available(SwiftStdlib 6.2, *) +extension Span where Element: ~Copyable { + public static var empty: Span { + @lifetime(immortal) + get { + let empty = unsafe UnsafeBufferPointer(start: nil, count: 0) + let span = unsafe Span(_unsafeElements: empty) + return unsafe _overrideLifetime(span, borrowing: immortalThing) + } + } + + @lifetime(immortal) + public init() { + let empty = unsafe UnsafeBufferPointer(start: nil, count: 0) + let span = unsafe Span(_unsafeElements: empty) + self = unsafe _overrideLifetime(span, borrowing: immortalThing) + } +} + +@available(SwiftStdlib 6.2, *) +extension Span where Element: Equatable { + /// Returns a Boolean value indicating whether this and another span + /// contain equal elements in the same order. + /// + /// - Parameters: + /// - other: A span to compare to this one. + /// - Returns: `true` if this sequence and `other` contain equivalent items, + /// using `areEquivalent` as the equivalence test; otherwise, `false.` + /// + /// - Complexity: O(*m*), where *m* is the lesser of the length of the + /// sequence and the length of `other`. + @_alwaysEmitIntoClient + public func _elementsEqual(_ other: Self) -> Bool { + guard count == other.count else { return false } + if count == 0 { return true } + + //FIXME: This could be short-cut + // with a layout constraint where stride equals size, + // as long as there is at most 1 unused bit pattern. + // if Element is BitwiseEquatable { + // return _swift_stdlib_memcmp(lhs.baseAddress, rhs.baseAddress, count) == 0 + // } + for o in 0..) -> Bool { + let equal = other.withContiguousStorageIfAvailable { + _elementsEqual(unsafe Span(_unsafeElements: $0)) + } + if let equal { return equal } + + guard count == other.count else { return false } + if count == 0 { return true } + + return _elementsEqual(AnySequence(other)) + } + + /// Returns a Boolean value indicating whether this span and a Sequence + /// contain equal elements in the same order. + /// + /// - Parameters: + /// - other: A Sequence to compare to this span. + /// - Returns: `true` if this sequence and `other` contain equivalent items, + /// using `areEquivalent` as the equivalence test; otherwise, `false.` + /// + /// - Complexity: O(*m*), where *m* is the lesser of the length of the + /// sequence and the length of `other`. + @_alwaysEmitIntoClient + public func _elementsEqual(_ other: some Sequence) -> Bool { + var offset = 0 + for otherElement in other { + if offset >= count { return false } + if unsafe self[unchecked: offset] != otherElement { return false } + offset += 1 + } + return offset == count + } +} diff --git a/Sources/Future/Span/StdlibOutputSpanExtensions.swift b/Sources/Future/Span/StdlibOutputSpanExtensions.swift new file mode 100644 index 000000000..624bca8a5 --- /dev/null +++ b/Sources/Future/Span/StdlibOutputSpanExtensions.swift @@ -0,0 +1,90 @@ +//===--- StdlibOutputSpanExtensions.swift ---------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +#if false // FIXME: Revive +extension Array { + + @available(macOS 9999, *) + public init( + capacity: Int, + initializingWith initializer: (inout OutputSpan) throws -> Void + ) rethrows { + try unsafe self.init( + unsafeUninitializedCapacity: capacity, + initializingWith: { (buffer, count) in + let pointer = unsafe buffer.baseAddress.unsafelyUnwrapped + var output = unsafe OutputSpan( + _initializing: pointer, capacity: buffer.count + ) + try initializer(&output) + let initialized = unsafe output.relinquishBorrowedMemory() + unsafe assert(initialized.baseAddress == buffer.baseAddress) + count = initialized.count + } + ) + } +} + +extension String { + + // also see https://github.com/apple/swift/pull/23050 + // and `final class __SharedStringStorage` + + @available(macOS 9999, *) + public init( + utf8Capacity capacity: Int, + initializingWith initializer: (inout OutputSpan) throws -> Void + ) rethrows { + try unsafe self.init( + unsafeUninitializedCapacity: capacity, + initializingUTF8With: { buffer in + let pointer = unsafe buffer.baseAddress.unsafelyUnwrapped + var output = unsafe OutputSpan( + _initializing: pointer, capacity: buffer.count + ) + try initializer(&output) + let initialized = unsafe output.relinquishBorrowedMemory() + unsafe assert(initialized.baseAddress == buffer.baseAddress) + return initialized.count + } + ) + } +} + +import Foundation + +extension Data { + + @available(macOS 9999, *) + public init( + capacity: Int, + initializingWith initializer: (inout OutputSpan) throws -> Void + ) rethrows { + self = Data(count: capacity) // initialized with zeroed buffer + let count = unsafe try self.withUnsafeMutableBytes { rawBuffer in + unsafe try rawBuffer.withMemoryRebound(to: UInt8.self) { buffer in + unsafe buffer.deinitialize() + let pointer = unsafe buffer.baseAddress.unsafelyUnwrapped + var output = unsafe OutputSpan( + _initializing: pointer, capacity: capacity + ) + try initializer(&output) + let initialized = unsafe output.relinquishBorrowedMemory() + unsafe assert(initialized.baseAddress == buffer.baseAddress) + return initialized.count + } + } + assert(count <= self.count) + self.replaceSubrange(count.. Bool { + unsafe (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + /// Returns a Boolean value indicating whether two + /// `UnsafeMutableBufferPointer` instances refer to the same region in + /// memory. + @inlinable @inline(__always) + public func isIdentical(to other: Self) -> Bool { + unsafe (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} + +extension UnsafeRawBufferPointer { + /// Returns a Boolean value indicating whether two `UnsafeRawBufferPointer` + /// instances refer to the same region in memory. + @inlinable @inline(__always) + public func isIdentical(to other: Self) -> Bool { + unsafe (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} + +extension UnsafeMutableRawBufferPointer { + /// Returns a Boolean value indicating whether two + /// `UnsafeMutableRawBufferPointer` instances refer to the same region in + /// memory. + @inlinable @inline(__always) + public func isIdentical(to other: Self) -> Bool { + unsafe (self.baseAddress == other.baseAddress) && (self.count == other.count) + } +} + + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + internal func _moveInitializePrefix( + from source: UnsafeMutableBufferPointer + ) -> Int { + if source.isEmpty { return 0 } + precondition(source.count <= self.count) + unsafe self.baseAddress.unsafelyUnwrapped.moveInitialize( + from: source.baseAddress.unsafelyUnwrapped, count: source.count) + return source.count + } +} + +extension UnsafeMutableBufferPointer { + /// Initialize slots at the start of this buffer by copying data from `source`. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized, while `source` must be fully initialized. + /// + /// The `source` buffer must fit entirely in `self`. + /// + /// - Returns: The index after the last item that was initialized in this buffer. + @inlinable + internal func _initializePrefix( + copying source: UnsafeBufferPointer + ) -> Int { + if source.isEmpty { return 0 } + precondition(source.count <= self.count) + unsafe self.baseAddress.unsafelyUnwrapped.initialize( + from: source.baseAddress.unsafelyUnwrapped, count: source.count) + return source.count + } + + @inlinable + internal func _initializePrefix( + copying source: UnsafeMutableBufferPointer + ) -> Int { + unsafe _initializePrefix(copying: UnsafeBufferPointer(source)) + } + + /// Initialize slots at the start of this buffer by copying data from `source`. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized. + /// + /// The `source` span must fit entirely in `self`. + /// + /// - Returns: The index after the last item that was initialized in this buffer. + @available(SwiftStdlib 6.2, *) + @inlinable + internal func _initializePrefix(copying source: Span) -> Int { + unsafe source.withUnsafeBufferPointer { unsafe self._initializePrefix(copying: $0) } + } + + /// Initialize all slots in this buffer by copying data from `items`, which must fit entirely + /// in this buffer. + /// + /// If `items` contains more elements than can fit into this buffer, then this function + /// will return an index other than `items.endIndex`. In that case, `self` may not be fully + /// populated. + /// + /// If `Element` is not bitwise copyable, then this function must be called on an + /// entirely uninitialized buffer. + /// + /// - Returns: A pair of values `(count, end)`, where `count` is the number of items that were + /// successfully initialized, and `end` is the index into `items` after the last copied item. + @available(SwiftStdlib 6.2, *) + @inlinable + internal func _initializePrefix< + C: Container & ~Copyable & ~Escapable + >( + copying items: borrowing C + ) -> (copied: Int, end: C.Index) { + var target = unsafe self + var i = items.startIndex + while true { + let start = i + let span = items.nextSpan(after: &i) + if span.isEmpty { break } + guard span.count <= target.count else { + return (self.count - target.count, start) + } + unsafe target._initializeAndDropPrefix(copying: span) + } + return (self.count - target.count, i) + } + + /// Initialize slots at the start of this buffer by copying data from `buffer`, then + /// shrink `self` to drop all initialized items from its front, leaving it addressing the + /// uninitialized remainder. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized, while `buffer` must be fully initialized. + /// + /// The count of `buffer` must not be greater than `self.count`. + @inlinable + internal mutating func _initializeAndDropPrefix(copying source: UnsafeBufferPointer) { + let i = unsafe _initializePrefix(copying: source) + unsafe self = self.extracting(i...) + } + + /// Initialize slots at the start of this buffer by copying data from `span`, then + /// shrink `self` to drop all initialized items from its front, leaving it addressing the + /// uninitialized remainder. + /// + /// If `Element` is not bitwise copyable, then the memory region addressed by `self` must be + /// entirely uninitialized. + /// + /// The count of `span` must not be greater than `self.count`. + @available(SwiftStdlib 6.2, *) + @inlinable + internal mutating func _initializeAndDropPrefix(copying span: Span) { + unsafe span.withUnsafeBufferPointer { buffer in + unsafe self._initializeAndDropPrefix(copying: buffer) + } + } +} diff --git a/Sources/Future/StdlibAdditions.swift b/Sources/Future/StdlibAdditions.swift new file mode 100644 index 000000000..31dec320f --- /dev/null +++ b/Sources/Future/StdlibAdditions.swift @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +import Builtin + +extension Optional where Wrapped: ~Copyable { + @lifetime(borrow self) + @_addressableSelf + public func borrow() -> Borrow? { + if self == nil { + return nil + } + + let pointer = unsafe UnsafePointer( + Builtin.unprotectedAddressOfBorrow(self) + ) + + return unsafe Borrow(unsafeAddress: pointer, borrowing: self) + } + +// #if false // Compiler bug preventing this +// @lifetime(&self) +// public mutating func mutate() -> Inout? { +// if self == nil { +// return nil +// } +// +// let pointer = unsafe UnsafeMutablePointer( +// Builtin.unprotectedAddressOf(&self) +// ) +// +// return unsafe Inout(unsafeAddress: pointer, owner: &self) +// } +} diff --git a/Sources/Future/Tools/Borrow.swift b/Sources/Future/Tools/Borrow.swift new file mode 100644 index 000000000..7ba3d03da --- /dev/null +++ b/Sources/Future/Tools/Borrow.swift @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +import Builtin + +@frozen +@safe +public struct Borrow: Copyable, ~Escapable { + @usableFromInline + package let _pointer: UnsafePointer + + @lifetime(borrow value) + @_alwaysEmitIntoClient + @_transparent + public init(_ value: borrowing @_addressable T) { + unsafe _pointer = UnsafePointer(Builtin.unprotectedAddressOfBorrow(value)) + } + + @lifetime(borrow owner) + @_alwaysEmitIntoClient + @_transparent + public init( + unsafeAddress: UnsafePointer, + borrowing owner: borrowing Owner + ) { + unsafe _pointer = unsafeAddress + } + + @lifetime(copy owner) + @_alwaysEmitIntoClient + @_transparent + public init( + unsafeAddress: UnsafePointer, + copying owner: borrowing Owner + ) { + unsafe _pointer = unsafeAddress + } + + @_alwaysEmitIntoClient + public subscript() -> T { + @_transparent + unsafeAddress { + unsafe _pointer + } + } +} diff --git a/Sources/Future/Tools/Box.swift b/Sources/Future/Tools/Box.swift new file mode 100644 index 000000000..5b94e655f --- /dev/null +++ b/Sources/Future/Tools/Box.swift @@ -0,0 +1,121 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +@frozen +@safe +public struct Box: ~Copyable { + @usableFromInline + internal let _pointer: UnsafeMutablePointer + + @_alwaysEmitIntoClient + @_transparent + public init(_ value: consuming T) { + unsafe _pointer = UnsafeMutablePointer.allocate(capacity: 1) + unsafe _pointer.initialize(to: value) + } + + @_alwaysEmitIntoClient + @inlinable + deinit { + unsafe _pointer.deinitialize(count: 1) + unsafe _pointer.deallocate() + } +} + +extension Box where T: ~Copyable { + @_alwaysEmitIntoClient + @_transparent + public consuming func consume() -> T { + let result = unsafe _pointer.move() + unsafe _pointer.deallocate() + discard self + return result + } + + @_alwaysEmitIntoClient + @_transparent + @lifetime(immortal) + public consuming func leak() -> Inout { + let result = unsafe Inout(unsafeImmortalAddress: _pointer) + discard self + return result + } + + @_alwaysEmitIntoClient + public subscript() -> T { + @_transparent + unsafeAddress { + unsafe UnsafePointer(_pointer) + } + + @_transparent + unsafeMutableAddress { + unsafe _pointer + } + } + + @_alwaysEmitIntoClient + @_transparent + @lifetime(borrow self) + public func borrow() -> Borrow { + unsafe Borrow(unsafeAddress: UnsafePointer(_pointer), borrowing: self) + } + + @_alwaysEmitIntoClient + @_transparent +#if compiler(>=6.2) && $InoutLifetimeDependence + @lifetime(&self) +#else + @lifetime(borrow self) +#endif + public mutating func mutate() -> Inout { + unsafe Inout(unsafeAddress: _pointer, mutating: &self) + } +} + +extension Box where T: Copyable { + @_alwaysEmitIntoClient + @_transparent + public borrowing func copy() -> T { + unsafe _pointer.pointee + } +} + +extension Box where T: ~Copyable { + @available(SwiftStdlib 6.2, *) + public var span: Span { + @_alwaysEmitIntoClient + @lifetime(borrow self) + get { + unsafe Span(_unsafeStart: _pointer, count: 1) + } + } + +#if compiler(>=6.2) && $InoutLifetimeDependence + @available(SwiftStdlib 6.2, *) + public var mutableSpan: MutableSpan { + @_alwaysEmitIntoClient + @lifetime(&self) + mutating get { + unsafe MutableSpan(_unsafeStart: _pointer, count: 1) + } + } +#else + @available(SwiftStdlib 6.2, *) + public var mutableSpan: MutableSpan { + @_alwaysEmitIntoClient + @lifetime(borrow self) + mutating get { + unsafe MutableSpan(_unsafeStart: _pointer, count: 1) + } + } +#endif +} diff --git a/Sources/Future/Tools/Inout.swift b/Sources/Future/Tools/Inout.swift new file mode 100644 index 000000000..9c06f9aa7 --- /dev/null +++ b/Sources/Future/Tools/Inout.swift @@ -0,0 +1,92 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +import Builtin + +// FIXME: A better name for the generic argument. + +/// A safe mutable reference allowing in-place mutation to an exclusive value. +/// +/// In order to get an instance of an `Inout`, one must have exclusive access +/// to the instance of `T`. This is achieved through the 'inout' operator, '&'. +@frozen +@safe +public struct Inout: ~Copyable, ~Escapable { + @usableFromInline + package let _pointer: UnsafeMutablePointer + + /// Initializes an instance of 'Inout' extending the exclusive access of the + /// passed instance. + /// + /// - Parameter instance: The desired instance to get a mutable reference to. + @_alwaysEmitIntoClient + @_transparent + public init(_ instance: inout T) { + unsafe _pointer = UnsafeMutablePointer(Builtin.unprotectedAddressOf(&instance)) + } + + /// Unsafely initializes an instance of 'Inout' using the given 'unsafeAddress' + /// as the mutable reference based on the lifetime of the given 'owner' + /// argument. + /// + /// - Parameter unsafeAddress: The address to use to mutably reference an + /// instance of type 'T'. + /// - Parameter owner: The owning instance that this 'Inout' instance's + /// lifetime is based on. + @unsafe + @_alwaysEmitIntoClient + @_transparent +#if compiler(>=6.2) && $InoutLifetimeDependence + @lifetime(&owner) +#else + @lifetime(borrow owner) +#endif + public init( + unsafeAddress: UnsafeMutablePointer, + mutating owner: inout Owner + ) { + unsafe _pointer = unsafeAddress + } + + /// Unsafely initializes an instance of 'Inout' using the given + /// 'unsafeImmortalAddress' as the mutable reference acting as though its + /// lifetime is immortal. + /// + /// - Parameter unsafeImmortalAddress: The address to use to mutably reference + /// an immortal instance of type 'T'. + @lifetime(immortal) + @unsafe + @_alwaysEmitIntoClient + @_transparent + public init( + unsafeImmortalAddress: UnsafeMutablePointer + ) { + unsafe _pointer = unsafeImmortalAddress + } +} + +extension Inout where T: ~Copyable { + /// Dereferences the mutable reference allowing for in-place reads and writes + /// to the underlying instance. + @_alwaysEmitIntoClient + public subscript() -> T { + @_transparent + unsafeAddress { + unsafe UnsafePointer(_pointer) + } + + @lifetime(copy self) + @_transparent + unsafeMutableAddress { + unsafe _pointer + } + } +} diff --git a/Sources/Future/Tools/Shared.swift b/Sources/Future/Tools/Shared.swift new file mode 100644 index 000000000..1baf0d3ea --- /dev/null +++ b/Sources/Future/Tools/Shared.swift @@ -0,0 +1,181 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +import Builtin // For Shared.isIdentical + +/// A utility adapter that wraps a noncopyable storage type in a copy-on-write +/// struct, enabling efficient implementation of value semantics. The type +/// allows safe borrowing and mutating access to its storage, with minimal fuss. +/// +/// Like `ManagedBufferPointer`, this type is intended to be used within the +/// internal implementation of public types. Instances of it aren't designed +/// to be exposed as public. +@safe +@frozen +public struct Shared { + @usableFromInline + internal var _box: _Box + + @inlinable + public init(_ storage: consuming Storage) { + unsafe self._box = _Box(storage) + } +} + +extension Shared: @unchecked Sendable where Storage: Sendable & ~Copyable {} + +extension Shared where Storage: ~Copyable { + @unsafe + @usableFromInline + internal final class _Box { + @exclusivity(unchecked) + @usableFromInline + internal var storage: Storage + + @inlinable + internal init(_ storage: consuming Storage) { + unsafe self.storage = storage + } + } +} + +extension Shared where Storage: ~Copyable { + @inlinable + @inline(__always) + public mutating func isUnique() -> Bool { + unsafe isKnownUniquelyReferenced(&_box) + } + + /// - Returns true if this instance was already uniquely referenced. + @discardableResult + @inlinable + public mutating func ensureUnique( + cloner: (borrowing Storage) -> Storage + ) -> Bool { + if isUnique() { return true } + replace(using: cloner) + return false + } + + @inlinable + public mutating func replace( + using body: (borrowing Storage) -> Storage + ) { + unsafe _box = _Box(body(_box.storage)) + } + + @inlinable + public mutating func edit( + shared cloner: (borrowing Storage) -> Storage, + unique updater: (inout Storage) -> Void + ) { + if isUnique() { + unsafe updater(&_box.storage) + } else { + unsafe _box = _Box(cloner(_box.storage)) + } + } +} + +import struct SwiftShims.HeapObject + +extension Shared where Storage: ~Copyable { + // FIXME: Can we avoid hacks like this? If not, perhaps `_Box.storage` should be tail-allocated. + @inlinable + internal var _address: UnsafePointer { + // Adapted from _getUnsafePointerToStoredProperties + let p = unsafe ( + UnsafeRawPointer(Builtin.bridgeToRawPointer(_box)) + + MemoryLayout.size) + return unsafe p.alignedUp(for: Storage.self).assumingMemoryBound(to: Storage.self) + } + + @inlinable + internal var _mutableAddress: UnsafeMutablePointer { + // Adapted from _getUnsafePointerToStoredProperties + let p = unsafe ( + UnsafeMutableRawPointer(Builtin.bridgeToRawPointer(_box)) + + MemoryLayout.size) + return unsafe p.alignedUp(for: Storage.self).assumingMemoryBound(to: Storage.self) + } +} + +extension Shared where Storage: ~Copyable { +#if compiler(>=6.2) && $InoutLifetimeDependence + @inlinable + @inline(__always) + public var value: Storage { + @lifetime(borrow self) + unsafeAddress { + unsafe _address + } + @lifetime(&self) + unsafeMutableAddress { + precondition(isUnique()) + return unsafe _mutableAddress + } + } +#else + @inlinable + @inline(__always) + public var value: Storage { + @lifetime(borrow self) + unsafeAddress { + unsafe _address + } + @lifetime(borrow self) + unsafeMutableAddress { + precondition(isUnique()) + return unsafe _mutableAddress + } + } +#endif +} + +extension Shared where Storage: ~Copyable { + @inlinable + @lifetime(borrow self) + public borrowing func borrow() -> Borrow { + // This is gloriously (and very explicitly) unsafe, as it should be. + // `Shared` is carefully constructed to guarantee that + // lifetime(self) == lifetime(_box.storage). + unsafe Borrow(unsafeAddress: _address, borrowing: self) + } + + + @inlinable +#if compiler(>=6.2) && $InoutLifetimeDependence + @lifetime(&self) +#else + @lifetime(borrow self) +#endif + public mutating func mutate() -> Inout { + // This is gloriously (and very explicitly) unsafe, as it should be. + // `Shared` is carefully constructed to guarantee that + // lifetime(self) == lifetime(_box.storage). + unsafe Inout(unsafeAddress: _mutableAddress, mutating: &self) + } +} + +extension Shared where Storage: ~Copyable { + @inlinable + public func isIdentical(to other: Self) -> Bool { + if #available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) { + return unsafe self._box === other._box + } else { + // To call the standard `===`, we need to do `_SharedBox` -> AnyObject conversions + // that are only supported in the Swift 6+ runtime. + let a = unsafe Builtin.bridgeToRawPointer(self._box) + let b = unsafe Builtin.bridgeToRawPointer(other._box) + return Bool(Builtin.cmp_eq_RawPointer(a, b)) + } + } +} diff --git a/Sources/HashTreeCollections/HashNode/_HashNode+Storage.swift b/Sources/HashTreeCollections/HashNode/_HashNode+Storage.swift index 0aefeb848..88af96555 100644 --- a/Sources/HashTreeCollections/HashNode/_HashNode+Storage.swift +++ b/Sources/HashTreeCollections/HashNode/_HashNode+Storage.swift @@ -26,6 +26,7 @@ internal typealias _RawHashStorage = ManagedBuffer<_HashNodeHeader, _RawHashNode /// `_HashNode.Storage` subclass is to allow storage instances to properly /// clean up after themselves in their `deinit` method.) @usableFromInline +nonisolated(unsafe) internal let _emptySingleton: _RawHashStorage = _RawHashStorage.create( minimumCapacity: 0, makingHeaderWith: { _ in _HashNodeHeader(byteCapacity: 0) }) diff --git a/Sources/InternalCollectionsUtilities/CMakeLists.txt b/Sources/InternalCollectionsUtilities/CMakeLists.txt index 808e36c2e..0adb28f1c 100644 --- a/Sources/InternalCollectionsUtilities/CMakeLists.txt +++ b/Sources/InternalCollectionsUtilities/CMakeLists.txt @@ -24,12 +24,8 @@ target_sources(${module_name} PRIVATE "autogenerated/Debugging.swift" "autogenerated/Descriptions.swift" "autogenerated/RandomAccessCollection+Offsets.swift" - "autogenerated/Specialize.swift" "autogenerated/UnsafeBufferPointer+Extras.swift" "autogenerated/UnsafeMutableBufferPointer+Extras.swift" - "Compatibility/autogenerated/UnsafeMutableBufferPointer+SE-0370.swift" - "Compatibility/autogenerated/UnsafeMutablePointer+SE-0370.swift" - "Compatibility/autogenerated/UnsafeRawPointer extensions.swift" "IntegerTricks/autogenerated/FixedWidthInteger+roundUpToPowerOfTwo.swift" "IntegerTricks/autogenerated/Integer rank.swift" "IntegerTricks/autogenerated/UInt+first and last set bit.swift" diff --git a/Sources/InternalCollectionsUtilities/Compatibility/UnsafeMutableBufferPointer+SE-0370.swift.gyb b/Sources/InternalCollectionsUtilities/Compatibility/UnsafeMutableBufferPointer+SE-0370.swift.gyb deleted file mode 100644 index 8e4f6debf..000000000 --- a/Sources/InternalCollectionsUtilities/Compatibility/UnsafeMutableBufferPointer+SE-0370.swift.gyb +++ /dev/null @@ -1,424 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 - 2024 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 -// -//===----------------------------------------------------------------------===// - -// Note: These are adapted from SE-0370 in the Swift 5.8 Standard Library. - -%{ - from gyb_utils import * -}% -${autogenerated_warning()} - -% for modifier in visibility_levels: -${visibility_boilerplate(modifier)} -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Deinitializes every instance in this buffer. - /// - /// The region of memory underlying this buffer must be fully initialized. - /// After calling `deinitialize(count:)`, the memory is uninitialized, - /// but still bound to the `Element` type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Returns: A raw buffer to the same range of memory as this buffer. - /// The range of memory is still bound to `Element`. - @discardableResult - @inlinable - ${modifier} func deinitialize() -> UnsafeMutableRawBufferPointer { - guard let start = baseAddress else { return .init(start: nil, count: 0) } - start.deinitialize(count: count) - return .init(start: UnsafeMutableRawPointer(start), - count: count * MemoryLayout.stride) - } -} -#endif - -// Note: this is left unconditionally enabled because we need the SR14663 workaround. :-( -extension UnsafeMutableBufferPointer { - /// Initializes the buffer's memory with - /// every element of the source. - /// - /// Prior to calling the `initialize(fromContentsOf:)` method on a buffer, - /// the memory referenced by the buffer must be uninitialized, - /// or the `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// must not overlap. - /// - /// - Parameter source: A collection of elements to be used to - /// initialize the buffer's storage. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - ${modifier} func initialize( - fromContentsOf source: C - ) -> Index - where C.Element == Element { - let count: Int? = source.withContiguousStorageIfAvailable { - guard let sourceAddress = $0.baseAddress, !$0.isEmpty else { - return 0 - } - precondition( - $0.count <= self.count, - "buffer cannot contain every element from source." - ) - baseAddress?.initialize(from: sourceAddress, count: $0.count) - return $0.count - } - if let count = count { - return startIndex.advanced(by: count) - } - - var (iterator, copied) = source._copyContents(initializing: self) - precondition( - iterator.next() == nil, - "buffer cannot contain every element from source." - ) - return startIndex.advanced(by: copied) - } -} - -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer, leaving the source memory - /// uninitialized and this buffer's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a buffer, - /// the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// may overlap. - /// - /// - Parameter source: A buffer containing the values to copy. The memory - /// region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func moveInitialize(fromContentsOf source: Self) -> Index { - guard let sourceAddress = source.baseAddress, !source.isEmpty else { - return startIndex - } - precondition( - source.count <= self.count, - "buffer cannot contain every element from source." - ) - baseAddress?.moveInitialize(from: sourceAddress, count: source.count) - return startIndex.advanced(by: source.count) - } - - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer, leaving the source memory - /// uninitialized and this buffer's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a buffer, - /// the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// may overlap. - /// - /// - Parameter source: A buffer containing the values to copy. The memory - /// region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func moveInitialize(fromContentsOf source: Slice) -> Index { - return moveInitialize(fromContentsOf: Self(rebasing: source)) - } - - /// Initializes the element at `index` to the given value. - /// - /// The memory underlying the destination element must be uninitialized, - /// or `Element` must be a trivial type. After a call to `initialize(to:)`, - /// the memory underlying this element of the buffer is initialized. - /// - /// - Parameters: - /// - value: The value used to initialize the buffer element's memory. - /// - index: The index of the element to initialize - @inlinable - @_alwaysEmitIntoClient - ${modifier} func initializeElement(at index: Index, to value: Element) { - assert(startIndex <= index && index < endIndex) - let p = baseAddress.unsafelyUnwrapped.advanced(by: index) - p.initialize(to: value) - } - - /// Retrieves and returns the element at `index`, - /// leaving that element's underlying memory uninitialized. - /// - /// The memory underlying the element at `index` must be initialized. - /// After calling `moveElement(from:)`, the memory underlying this element - /// of the buffer is uninitialized, and still bound to type `Element`. - /// - /// - Parameters: - /// - index: The index of the buffer element to retrieve and deinitialize. - /// - Returns: The instance referenced by this index in this buffer. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func moveElement(from index: Index) -> Element { - assert(startIndex <= index && index < endIndex) - return baseAddress.unsafelyUnwrapped.advanced(by: index).move() - } - - /// Deinitializes the memory underlying the element at `index`. - /// - /// The memory underlying the element at `index` must be initialized. - /// After calling `deinitializeElement()`, the memory underlying this element - /// of the buffer is uninitialized, and still bound to type `Element`. - /// - /// - Parameters: - /// - index: The index of the buffer element to deinitialize. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func deinitializeElement(at index: Index) { - assert(startIndex <= index && index < endIndex) - let p = baseAddress.unsafelyUnwrapped.advanced(by: index) - p.deinitialize(count: 1) - } -} -#endif - -#if swift(<5.8) -extension Slice { - /// Initializes the buffer slice's memory with with - /// every element of the source. - /// - /// Prior to calling the `initialize(fromContentsOf:)` method - /// on a buffer slice, the memory it references must be uninitialized, - /// or the `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the index of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to - /// the buffer slice's `startIndex`. If `source` contains as many elements - /// as the buffer slice can hold, the returned index is equal to - /// to the slice's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// must not overlap. - /// - /// - Parameter source: A collection of elements to be used to - /// initialize the buffer slice's storage. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func initialize( - fromContentsOf source: C - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.initialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer slice, leaving the - /// source memory uninitialized and this buffer slice's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a - /// buffer slice, the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// slice's `startIndex`. If `source` contains as many elements as the slice - /// can hold, the returned index is equal to the slice's `endIndex`. - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// may overlap. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Parameter source: A buffer containing the values to copy. - /// The memory region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func moveInitialize( - fromContentsOf source: UnsafeMutableBufferPointer - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.moveInitialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Moves every element of an initialized source buffer slice into the - /// uninitialized memory referenced by this buffer slice, leaving the - /// source memory uninitialized and this buffer slice's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a - /// buffer slice, the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// slice's `startIndex`. If `source` contains as many elements as the slice - /// can hold, the returned index is equal to the slice's `endIndex`. - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// may overlap. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Parameter source: A buffer slice containing the values to copy. - /// The memory region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func moveInitialize( - fromContentsOf source: Slice> - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.moveInitialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Deinitializes every instance in this buffer slice. - /// - /// The region of memory underlying this buffer slice must be fully - /// initialized. After calling `deinitialize(count:)`, the memory - /// is uninitialized, but still bound to the `Element` type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Returns: A raw buffer to the same range of memory as this buffer. - /// The range of memory is still bound to `Element`. - @discardableResult - @inlinable - @_alwaysEmitIntoClient - ${modifier} func deinitialize() -> UnsafeMutableRawBufferPointer - where Base == UnsafeMutableBufferPointer { - Base(rebasing: self).deinitialize() - } - - /// Initializes the element at `index` to the given value. - /// - /// The memory underlying the destination element must be uninitialized, - /// or `Element` must be a trivial type. After a call to `initialize(to:)`, - /// the memory underlying this element of the buffer slice is initialized. - /// - /// - Parameters: - /// - value: The value used to initialize the buffer element's memory. - /// - index: The index of the element to initialize - @inlinable - @_alwaysEmitIntoClient - ${modifier} func initializeElement(at index: Int, to value: Element) - where Base == UnsafeMutableBufferPointer { - assert(startIndex <= index && index < endIndex) - base.baseAddress.unsafelyUnwrapped.advanced(by: index).initialize(to: value) - } -} -#endif - -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Updates every element of this buffer's initialized memory. - /// - /// The buffer’s memory must be initialized or its `Element` type - /// must be a trivial type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - @_alwaysEmitIntoClient - ${modifier} func update(repeating repeatedValue: Element) { - guard let dstBase = baseAddress else { return } - dstBase.update(repeating: repeatedValue, count: count) - } -} -#endif - -#if swift(<5.8) -extension Slice { - /// Updates every element of this buffer slice's initialized memory. - /// - /// The buffer slice’s memory must be initialized or its `Element` - /// must be a trivial type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - @_alwaysEmitIntoClient - ${modifier} func update(repeating repeatedValue: Element) - where Base == UnsafeMutableBufferPointer { - Base(rebasing: self).update(repeating: repeatedValue) - } -} -#endif -% end -${visibility_boilerplate("end")} diff --git a/Sources/InternalCollectionsUtilities/Compatibility/UnsafeMutablePointer+SE-0370.swift.gyb b/Sources/InternalCollectionsUtilities/Compatibility/UnsafeMutablePointer+SE-0370.swift.gyb deleted file mode 100644 index d95199cf7..000000000 --- a/Sources/InternalCollectionsUtilities/Compatibility/UnsafeMutablePointer+SE-0370.swift.gyb +++ /dev/null @@ -1,43 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 - 2024 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 -// -//===----------------------------------------------------------------------===// - -%{ - from gyb_utils import * -}% -${autogenerated_warning()} - -% for modifier in visibility_levels: -${visibility_boilerplate(modifier)} -#if swift(<5.8) -extension UnsafeMutablePointer { - /// Update this pointer's initialized memory with the specified number of - /// consecutive copies of the given value. - /// - /// The region of memory starting at this pointer and covering `count` - /// instances of the pointer's `Pointee` type must be initialized or - /// `Pointee` must be a trivial type. After calling - /// `update(repeating:count:)`, the region is initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - /// - count: The number of consecutive elements to update. - /// `count` must not be negative. - @_alwaysEmitIntoClient - ${modifier} func update(repeating repeatedValue: Pointee, count: Int) { - assert(count >= 0, "UnsafeMutablePointer.update(repeating:count:) with negative count") - for i in 0 ..< count { - self[i] = repeatedValue - } - } -} -#endif -% end -${visibility_boilerplate("end")} diff --git a/Sources/InternalCollectionsUtilities/Compatibility/UnsafeRawPointer extensions.swift.gyb b/Sources/InternalCollectionsUtilities/Compatibility/UnsafeRawPointer extensions.swift.gyb deleted file mode 100644 index 68c2eedcd..000000000 --- a/Sources/InternalCollectionsUtilities/Compatibility/UnsafeRawPointer extensions.swift.gyb +++ /dev/null @@ -1,89 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 - 2024 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 -// -//===----------------------------------------------------------------------===// - -%{ - from gyb_utils import * -}% -${autogenerated_warning()} - -% for modifier in visibility_levels: -${visibility_boilerplate(modifier)} -#if compiler(<5.7) || (os(macOS) && compiler(<5.8)) // SE-0334 -extension UnsafeRawPointer { - /// Obtain the next pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func alignedUp(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = (UInt(bitPattern: self) &+ mask) & ~mask - return Self(bitPattern: bits)! - } - - /// Obtain the preceding pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func alignedDown(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = UInt(bitPattern: self) & ~mask - return Self(bitPattern: bits)! - } -} - -extension UnsafeMutableRawPointer { - /// Obtain the next pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func alignedUp(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = (UInt(bitPattern: self) &+ mask) & ~mask - return Self(bitPattern: bits)! - } - - /// Obtain the preceding pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - ${modifier} func alignedDown(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = UInt(bitPattern: self) & ~mask - return Self(bitPattern: bits)! - } -} -#endif -% end -${visibility_boilerplate("end")} diff --git a/Sources/InternalCollectionsUtilities/Compatibility/autogenerated/UnsafeMutableBufferPointer+SE-0370.swift b/Sources/InternalCollectionsUtilities/Compatibility/autogenerated/UnsafeMutableBufferPointer+SE-0370.swift deleted file mode 100644 index 9068f8b91..000000000 --- a/Sources/InternalCollectionsUtilities/Compatibility/autogenerated/UnsafeMutableBufferPointer+SE-0370.swift +++ /dev/null @@ -1,832 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 - 2024 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 -// -//===----------------------------------------------------------------------===// - -// Note: These are adapted from SE-0370 in the Swift 5.8 Standard Library. - - -// ############################################################################# -// # # -// # DO NOT EDIT THIS FILE; IT IS AUTOGENERATED. # -// # # -// ############################################################################# - - - -// In single module mode, we need these declarations to be internal, -// but in regular builds we want them to be public. Unfortunately -// the current best way to do this is to duplicate all definitions. -#if COLLECTIONS_SINGLE_MODULE -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Deinitializes every instance in this buffer. - /// - /// The region of memory underlying this buffer must be fully initialized. - /// After calling `deinitialize(count:)`, the memory is uninitialized, - /// but still bound to the `Element` type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Returns: A raw buffer to the same range of memory as this buffer. - /// The range of memory is still bound to `Element`. - @discardableResult - @inlinable - internal func deinitialize() -> UnsafeMutableRawBufferPointer { - guard let start = baseAddress else { return .init(start: nil, count: 0) } - start.deinitialize(count: count) - return .init(start: UnsafeMutableRawPointer(start), - count: count * MemoryLayout.stride) - } -} -#endif - -// Note: this is left unconditionally enabled because we need the SR14663 workaround. :-( -extension UnsafeMutableBufferPointer { - /// Initializes the buffer's memory with - /// every element of the source. - /// - /// Prior to calling the `initialize(fromContentsOf:)` method on a buffer, - /// the memory referenced by the buffer must be uninitialized, - /// or the `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// must not overlap. - /// - /// - Parameter source: A collection of elements to be used to - /// initialize the buffer's storage. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - internal func initialize( - fromContentsOf source: C - ) -> Index - where C.Element == Element { - let count: Int? = source.withContiguousStorageIfAvailable { - guard let sourceAddress = $0.baseAddress, !$0.isEmpty else { - return 0 - } - precondition( - $0.count <= self.count, - "buffer cannot contain every element from source." - ) - baseAddress?.initialize(from: sourceAddress, count: $0.count) - return $0.count - } - if let count = count { - return startIndex.advanced(by: count) - } - - var (iterator, copied) = source._copyContents(initializing: self) - precondition( - iterator.next() == nil, - "buffer cannot contain every element from source." - ) - return startIndex.advanced(by: copied) - } -} - -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer, leaving the source memory - /// uninitialized and this buffer's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a buffer, - /// the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// may overlap. - /// - /// - Parameter source: A buffer containing the values to copy. The memory - /// region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - @_alwaysEmitIntoClient - internal func moveInitialize(fromContentsOf source: Self) -> Index { - guard let sourceAddress = source.baseAddress, !source.isEmpty else { - return startIndex - } - precondition( - source.count <= self.count, - "buffer cannot contain every element from source." - ) - baseAddress?.moveInitialize(from: sourceAddress, count: source.count) - return startIndex.advanced(by: source.count) - } - - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer, leaving the source memory - /// uninitialized and this buffer's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a buffer, - /// the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// may overlap. - /// - /// - Parameter source: A buffer containing the values to copy. The memory - /// region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - @_alwaysEmitIntoClient - internal func moveInitialize(fromContentsOf source: Slice) -> Index { - return moveInitialize(fromContentsOf: Self(rebasing: source)) - } - - /// Initializes the element at `index` to the given value. - /// - /// The memory underlying the destination element must be uninitialized, - /// or `Element` must be a trivial type. After a call to `initialize(to:)`, - /// the memory underlying this element of the buffer is initialized. - /// - /// - Parameters: - /// - value: The value used to initialize the buffer element's memory. - /// - index: The index of the element to initialize - @inlinable - @_alwaysEmitIntoClient - internal func initializeElement(at index: Index, to value: Element) { - assert(startIndex <= index && index < endIndex) - let p = baseAddress.unsafelyUnwrapped.advanced(by: index) - p.initialize(to: value) - } - - /// Retrieves and returns the element at `index`, - /// leaving that element's underlying memory uninitialized. - /// - /// The memory underlying the element at `index` must be initialized. - /// After calling `moveElement(from:)`, the memory underlying this element - /// of the buffer is uninitialized, and still bound to type `Element`. - /// - /// - Parameters: - /// - index: The index of the buffer element to retrieve and deinitialize. - /// - Returns: The instance referenced by this index in this buffer. - @inlinable - @_alwaysEmitIntoClient - internal func moveElement(from index: Index) -> Element { - assert(startIndex <= index && index < endIndex) - return baseAddress.unsafelyUnwrapped.advanced(by: index).move() - } - - /// Deinitializes the memory underlying the element at `index`. - /// - /// The memory underlying the element at `index` must be initialized. - /// After calling `deinitializeElement()`, the memory underlying this element - /// of the buffer is uninitialized, and still bound to type `Element`. - /// - /// - Parameters: - /// - index: The index of the buffer element to deinitialize. - @inlinable - @_alwaysEmitIntoClient - internal func deinitializeElement(at index: Index) { - assert(startIndex <= index && index < endIndex) - let p = baseAddress.unsafelyUnwrapped.advanced(by: index) - p.deinitialize(count: 1) - } -} -#endif - -#if swift(<5.8) -extension Slice { - /// Initializes the buffer slice's memory with with - /// every element of the source. - /// - /// Prior to calling the `initialize(fromContentsOf:)` method - /// on a buffer slice, the memory it references must be uninitialized, - /// or the `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the index of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to - /// the buffer slice's `startIndex`. If `source` contains as many elements - /// as the buffer slice can hold, the returned index is equal to - /// to the slice's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// must not overlap. - /// - /// - Parameter source: A collection of elements to be used to - /// initialize the buffer slice's storage. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - internal func initialize( - fromContentsOf source: C - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.initialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer slice, leaving the - /// source memory uninitialized and this buffer slice's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a - /// buffer slice, the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// slice's `startIndex`. If `source` contains as many elements as the slice - /// can hold, the returned index is equal to the slice's `endIndex`. - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// may overlap. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Parameter source: A buffer containing the values to copy. - /// The memory region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - internal func moveInitialize( - fromContentsOf source: UnsafeMutableBufferPointer - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.moveInitialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Moves every element of an initialized source buffer slice into the - /// uninitialized memory referenced by this buffer slice, leaving the - /// source memory uninitialized and this buffer slice's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a - /// buffer slice, the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// slice's `startIndex`. If `source` contains as many elements as the slice - /// can hold, the returned index is equal to the slice's `endIndex`. - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// may overlap. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Parameter source: A buffer slice containing the values to copy. - /// The memory region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - internal func moveInitialize( - fromContentsOf source: Slice> - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.moveInitialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Deinitializes every instance in this buffer slice. - /// - /// The region of memory underlying this buffer slice must be fully - /// initialized. After calling `deinitialize(count:)`, the memory - /// is uninitialized, but still bound to the `Element` type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Returns: A raw buffer to the same range of memory as this buffer. - /// The range of memory is still bound to `Element`. - @discardableResult - @inlinable - @_alwaysEmitIntoClient - internal func deinitialize() -> UnsafeMutableRawBufferPointer - where Base == UnsafeMutableBufferPointer { - Base(rebasing: self).deinitialize() - } - - /// Initializes the element at `index` to the given value. - /// - /// The memory underlying the destination element must be uninitialized, - /// or `Element` must be a trivial type. After a call to `initialize(to:)`, - /// the memory underlying this element of the buffer slice is initialized. - /// - /// - Parameters: - /// - value: The value used to initialize the buffer element's memory. - /// - index: The index of the element to initialize - @inlinable - @_alwaysEmitIntoClient - internal func initializeElement(at index: Int, to value: Element) - where Base == UnsafeMutableBufferPointer { - assert(startIndex <= index && index < endIndex) - base.baseAddress.unsafelyUnwrapped.advanced(by: index).initialize(to: value) - } -} -#endif - -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Updates every element of this buffer's initialized memory. - /// - /// The buffer’s memory must be initialized or its `Element` type - /// must be a trivial type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - @_alwaysEmitIntoClient - internal func update(repeating repeatedValue: Element) { - guard let dstBase = baseAddress else { return } - dstBase.update(repeating: repeatedValue, count: count) - } -} -#endif - -#if swift(<5.8) -extension Slice { - /// Updates every element of this buffer slice's initialized memory. - /// - /// The buffer slice’s memory must be initialized or its `Element` - /// must be a trivial type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - @_alwaysEmitIntoClient - internal func update(repeating repeatedValue: Element) - where Base == UnsafeMutableBufferPointer { - Base(rebasing: self).update(repeating: repeatedValue) - } -} -#endif -#else // !COLLECTIONS_SINGLE_MODULE -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Deinitializes every instance in this buffer. - /// - /// The region of memory underlying this buffer must be fully initialized. - /// After calling `deinitialize(count:)`, the memory is uninitialized, - /// but still bound to the `Element` type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Returns: A raw buffer to the same range of memory as this buffer. - /// The range of memory is still bound to `Element`. - @discardableResult - @inlinable - public func deinitialize() -> UnsafeMutableRawBufferPointer { - guard let start = baseAddress else { return .init(start: nil, count: 0) } - start.deinitialize(count: count) - return .init(start: UnsafeMutableRawPointer(start), - count: count * MemoryLayout.stride) - } -} -#endif - -// Note: this is left unconditionally enabled because we need the SR14663 workaround. :-( -extension UnsafeMutableBufferPointer { - /// Initializes the buffer's memory with - /// every element of the source. - /// - /// Prior to calling the `initialize(fromContentsOf:)` method on a buffer, - /// the memory referenced by the buffer must be uninitialized, - /// or the `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// must not overlap. - /// - /// - Parameter source: A collection of elements to be used to - /// initialize the buffer's storage. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - public func initialize( - fromContentsOf source: C - ) -> Index - where C.Element == Element { - let count: Int? = source.withContiguousStorageIfAvailable { - guard let sourceAddress = $0.baseAddress, !$0.isEmpty else { - return 0 - } - precondition( - $0.count <= self.count, - "buffer cannot contain every element from source." - ) - baseAddress?.initialize(from: sourceAddress, count: $0.count) - return $0.count - } - if let count = count { - return startIndex.advanced(by: count) - } - - var (iterator, copied) = source._copyContents(initializing: self) - precondition( - iterator.next() == nil, - "buffer cannot contain every element from source." - ) - return startIndex.advanced(by: copied) - } -} - -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer, leaving the source memory - /// uninitialized and this buffer's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a buffer, - /// the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// may overlap. - /// - /// - Parameter source: A buffer containing the values to copy. The memory - /// region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - @_alwaysEmitIntoClient - public func moveInitialize(fromContentsOf source: Self) -> Index { - guard let sourceAddress = source.baseAddress, !source.isEmpty else { - return startIndex - } - precondition( - source.count <= self.count, - "buffer cannot contain every element from source." - ) - baseAddress?.moveInitialize(from: sourceAddress, count: source.count) - return startIndex.advanced(by: source.count) - } - - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer, leaving the source memory - /// uninitialized and this buffer's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a buffer, - /// the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// buffer's `startIndex`. If `source` contains as many elements as the buffer - /// can hold, the returned index is equal to the buffer's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer - /// may overlap. - /// - /// - Parameter source: A buffer containing the values to copy. The memory - /// region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer initialized - /// by this function. - @inlinable - @_alwaysEmitIntoClient - public func moveInitialize(fromContentsOf source: Slice) -> Index { - return moveInitialize(fromContentsOf: Self(rebasing: source)) - } - - /// Initializes the element at `index` to the given value. - /// - /// The memory underlying the destination element must be uninitialized, - /// or `Element` must be a trivial type. After a call to `initialize(to:)`, - /// the memory underlying this element of the buffer is initialized. - /// - /// - Parameters: - /// - value: The value used to initialize the buffer element's memory. - /// - index: The index of the element to initialize - @inlinable - @_alwaysEmitIntoClient - public func initializeElement(at index: Index, to value: Element) { - assert(startIndex <= index && index < endIndex) - let p = baseAddress.unsafelyUnwrapped.advanced(by: index) - p.initialize(to: value) - } - - /// Retrieves and returns the element at `index`, - /// leaving that element's underlying memory uninitialized. - /// - /// The memory underlying the element at `index` must be initialized. - /// After calling `moveElement(from:)`, the memory underlying this element - /// of the buffer is uninitialized, and still bound to type `Element`. - /// - /// - Parameters: - /// - index: The index of the buffer element to retrieve and deinitialize. - /// - Returns: The instance referenced by this index in this buffer. - @inlinable - @_alwaysEmitIntoClient - public func moveElement(from index: Index) -> Element { - assert(startIndex <= index && index < endIndex) - return baseAddress.unsafelyUnwrapped.advanced(by: index).move() - } - - /// Deinitializes the memory underlying the element at `index`. - /// - /// The memory underlying the element at `index` must be initialized. - /// After calling `deinitializeElement()`, the memory underlying this element - /// of the buffer is uninitialized, and still bound to type `Element`. - /// - /// - Parameters: - /// - index: The index of the buffer element to deinitialize. - @inlinable - @_alwaysEmitIntoClient - public func deinitializeElement(at index: Index) { - assert(startIndex <= index && index < endIndex) - let p = baseAddress.unsafelyUnwrapped.advanced(by: index) - p.deinitialize(count: 1) - } -} -#endif - -#if swift(<5.8) -extension Slice { - /// Initializes the buffer slice's memory with with - /// every element of the source. - /// - /// Prior to calling the `initialize(fromContentsOf:)` method - /// on a buffer slice, the memory it references must be uninitialized, - /// or the `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the index of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to - /// the buffer slice's `startIndex`. If `source` contains as many elements - /// as the buffer slice can hold, the returned index is equal to - /// to the slice's `endIndex`. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// must not overlap. - /// - /// - Parameter source: A collection of elements to be used to - /// initialize the buffer slice's storage. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - public func initialize( - fromContentsOf source: C - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.initialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Moves every element of an initialized source buffer into the - /// uninitialized memory referenced by this buffer slice, leaving the - /// source memory uninitialized and this buffer slice's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a - /// buffer slice, the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// slice's `startIndex`. If `source` contains as many elements as the slice - /// can hold, the returned index is equal to the slice's `endIndex`. - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// may overlap. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Parameter source: A buffer containing the values to copy. - /// The memory region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - public func moveInitialize( - fromContentsOf source: UnsafeMutableBufferPointer - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.moveInitialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Moves every element of an initialized source buffer slice into the - /// uninitialized memory referenced by this buffer slice, leaving the - /// source memory uninitialized and this buffer slice's memory initialized. - /// - /// Prior to calling the `moveInitialize(fromContentsOf:)` method on a - /// buffer slice, the memory it references must be uninitialized, - /// or its `Element` type must be a trivial type. After the call, - /// the memory referenced by the buffer slice up to, but not including, - /// the returned index is initialized. The memory referenced by - /// `source` is uninitialized after the function returns. - /// The buffer slice must reference enough memory to accommodate - /// `source.count` elements. - /// - /// The returned index is the position of the next uninitialized element - /// in the buffer slice, one past the index of the last element written. - /// If `source` contains no elements, the returned index is equal to the - /// slice's `startIndex`. If `source` contains as many elements as the slice - /// can hold, the returned index is equal to the slice's `endIndex`. - /// - /// - Note: The memory regions referenced by `source` and this buffer slice - /// may overlap. - /// - /// - Precondition: `self.count` >= `source.count` - /// - /// - Parameter source: A buffer slice containing the values to copy. - /// The memory region underlying `source` must be initialized. - /// - Returns: The index one past the last element of the buffer slice - /// initialized by this function. - @inlinable - @_alwaysEmitIntoClient - public func moveInitialize( - fromContentsOf source: Slice> - ) -> Index where Base == UnsafeMutableBufferPointer { - let buffer = Base(rebasing: self) - let index = buffer.moveInitialize(fromContentsOf: source) - let distance = buffer.distance(from: buffer.startIndex, to: index) - return startIndex.advanced(by: distance) - } - - /// Deinitializes every instance in this buffer slice. - /// - /// The region of memory underlying this buffer slice must be fully - /// initialized. After calling `deinitialize(count:)`, the memory - /// is uninitialized, but still bound to the `Element` type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Returns: A raw buffer to the same range of memory as this buffer. - /// The range of memory is still bound to `Element`. - @discardableResult - @inlinable - @_alwaysEmitIntoClient - public func deinitialize() -> UnsafeMutableRawBufferPointer - where Base == UnsafeMutableBufferPointer { - Base(rebasing: self).deinitialize() - } - - /// Initializes the element at `index` to the given value. - /// - /// The memory underlying the destination element must be uninitialized, - /// or `Element` must be a trivial type. After a call to `initialize(to:)`, - /// the memory underlying this element of the buffer slice is initialized. - /// - /// - Parameters: - /// - value: The value used to initialize the buffer element's memory. - /// - index: The index of the element to initialize - @inlinable - @_alwaysEmitIntoClient - public func initializeElement(at index: Int, to value: Element) - where Base == UnsafeMutableBufferPointer { - assert(startIndex <= index && index < endIndex) - base.baseAddress.unsafelyUnwrapped.advanced(by: index).initialize(to: value) - } -} -#endif - -#if swift(<5.8) -extension UnsafeMutableBufferPointer { - /// Updates every element of this buffer's initialized memory. - /// - /// The buffer’s memory must be initialized or its `Element` type - /// must be a trivial type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - @_alwaysEmitIntoClient - public func update(repeating repeatedValue: Element) { - guard let dstBase = baseAddress else { return } - dstBase.update(repeating: repeatedValue, count: count) - } -} -#endif - -#if swift(<5.8) -extension Slice { - /// Updates every element of this buffer slice's initialized memory. - /// - /// The buffer slice’s memory must be initialized or its `Element` - /// must be a trivial type. - /// - /// - Note: All buffer elements must already be initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - @_alwaysEmitIntoClient - public func update(repeating repeatedValue: Element) - where Base == UnsafeMutableBufferPointer { - Base(rebasing: self).update(repeating: repeatedValue) - } -} -#endif -#endif // COLLECTIONS_SINGLE_MODULE diff --git a/Sources/InternalCollectionsUtilities/Compatibility/autogenerated/UnsafeMutablePointer+SE-0370.swift b/Sources/InternalCollectionsUtilities/Compatibility/autogenerated/UnsafeMutablePointer+SE-0370.swift deleted file mode 100644 index 2e89d5aa0..000000000 --- a/Sources/InternalCollectionsUtilities/Compatibility/autogenerated/UnsafeMutablePointer+SE-0370.swift +++ /dev/null @@ -1,72 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 - 2024 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 -// -//===----------------------------------------------------------------------===// - - -// ############################################################################# -// # # -// # DO NOT EDIT THIS FILE; IT IS AUTOGENERATED. # -// # # -// ############################################################################# - - - -// In single module mode, we need these declarations to be internal, -// but in regular builds we want them to be public. Unfortunately -// the current best way to do this is to duplicate all definitions. -#if COLLECTIONS_SINGLE_MODULE -#if swift(<5.8) -extension UnsafeMutablePointer { - /// Update this pointer's initialized memory with the specified number of - /// consecutive copies of the given value. - /// - /// The region of memory starting at this pointer and covering `count` - /// instances of the pointer's `Pointee` type must be initialized or - /// `Pointee` must be a trivial type. After calling - /// `update(repeating:count:)`, the region is initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - /// - count: The number of consecutive elements to update. - /// `count` must not be negative. - @_alwaysEmitIntoClient - internal func update(repeating repeatedValue: Pointee, count: Int) { - assert(count >= 0, "UnsafeMutablePointer.update(repeating:count:) with negative count") - for i in 0 ..< count { - self[i] = repeatedValue - } - } -} -#endif -#else // !COLLECTIONS_SINGLE_MODULE -#if swift(<5.8) -extension UnsafeMutablePointer { - /// Update this pointer's initialized memory with the specified number of - /// consecutive copies of the given value. - /// - /// The region of memory starting at this pointer and covering `count` - /// instances of the pointer's `Pointee` type must be initialized or - /// `Pointee` must be a trivial type. After calling - /// `update(repeating:count:)`, the region is initialized. - /// - /// - Parameters: - /// - repeatedValue: The value used when updating this pointer's memory. - /// - count: The number of consecutive elements to update. - /// `count` must not be negative. - @_alwaysEmitIntoClient - public func update(repeating repeatedValue: Pointee, count: Int) { - assert(count >= 0, "UnsafeMutablePointer.update(repeating:count:) with negative count") - for i in 0 ..< count { - self[i] = repeatedValue - } - } -} -#endif -#endif // COLLECTIONS_SINGLE_MODULE diff --git a/Sources/InternalCollectionsUtilities/Compatibility/autogenerated/UnsafeRawPointer extensions.swift b/Sources/InternalCollectionsUtilities/Compatibility/autogenerated/UnsafeRawPointer extensions.swift deleted file mode 100644 index c389967eb..000000000 --- a/Sources/InternalCollectionsUtilities/Compatibility/autogenerated/UnsafeRawPointer extensions.swift +++ /dev/null @@ -1,164 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2022 - 2024 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 -// -//===----------------------------------------------------------------------===// - - -// ############################################################################# -// # # -// # DO NOT EDIT THIS FILE; IT IS AUTOGENERATED. # -// # # -// ############################################################################# - - - -// In single module mode, we need these declarations to be internal, -// but in regular builds we want them to be public. Unfortunately -// the current best way to do this is to duplicate all definitions. -#if COLLECTIONS_SINGLE_MODULE -#if compiler(<5.7) || (os(macOS) && compiler(<5.8)) // SE-0334 -extension UnsafeRawPointer { - /// Obtain the next pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - internal func alignedUp(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = (UInt(bitPattern: self) &+ mask) & ~mask - return Self(bitPattern: bits)! - } - - /// Obtain the preceding pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - internal func alignedDown(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = UInt(bitPattern: self) & ~mask - return Self(bitPattern: bits)! - } -} - -extension UnsafeMutableRawPointer { - /// Obtain the next pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - internal func alignedUp(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = (UInt(bitPattern: self) &+ mask) & ~mask - return Self(bitPattern: bits)! - } - - /// Obtain the preceding pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - internal func alignedDown(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = UInt(bitPattern: self) & ~mask - return Self(bitPattern: bits)! - } -} -#endif -#else // !COLLECTIONS_SINGLE_MODULE -#if compiler(<5.7) || (os(macOS) && compiler(<5.8)) // SE-0334 -extension UnsafeRawPointer { - /// Obtain the next pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - public func alignedUp(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = (UInt(bitPattern: self) &+ mask) & ~mask - return Self(bitPattern: bits)! - } - - /// Obtain the preceding pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - public func alignedDown(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = UInt(bitPattern: self) & ~mask - return Self(bitPattern: bits)! - } -} - -extension UnsafeMutableRawPointer { - /// Obtain the next pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - public func alignedUp(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = (UInt(bitPattern: self) &+ mask) & ~mask - return Self(bitPattern: bits)! - } - - /// Obtain the preceding pointer properly aligned to store a value of type `T`. - /// - /// If `self` is properly aligned for accessing `T`, - /// this function returns `self`. - /// - /// - Parameters: - /// - type: the type to be stored at the returned address. - /// - Returns: a pointer properly aligned to store a value of type `T`. - @inlinable - @_alwaysEmitIntoClient - public func alignedDown(for type: T.Type) -> Self { - let mask = UInt(MemoryLayout.alignment) &- 1 - let bits = UInt(bitPattern: self) & ~mask - return Self(bitPattern: bits)! - } -} -#endif -#endif // COLLECTIONS_SINGLE_MODULE diff --git a/Sources/InternalCollectionsUtilities/Specialize.swift.gyb b/Sources/InternalCollectionsUtilities/Specialize.swift.gyb deleted file mode 100644 index c654d78cc..000000000 --- a/Sources/InternalCollectionsUtilities/Specialize.swift.gyb +++ /dev/null @@ -1,34 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2023 - 2024 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 -// -//===----------------------------------------------------------------------===// - -%{ - from gyb_utils import * -}% -${autogenerated_warning()} - -% for modifier in visibility_levels: -${visibility_boilerplate(modifier)} -/// Returns `x` as its concrete type `U`, or `nil` if `x` has a different -/// concrete type. -/// -/// This cast can be useful for dispatching to specializations of generic -/// functions. -@_transparent -@inlinable -${modifier} func _specialize(_ x: T, for: U.Type) -> U? { - // Note: this was ported from recent versions of the Swift stdlib. - guard T.self == U.self else { - return nil - } - return _identityCast(x, to: U.self) -} -% end -${visibility_boilerplate("end")} diff --git a/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb b/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb index 603e3047e..014c9d304 100644 --- a/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb +++ b/Sources/InternalCollectionsUtilities/UnsafeBufferPointer+Extras.swift.gyb @@ -16,13 +16,44 @@ ${autogenerated_warning()} % for modifier in visibility_levels: ${visibility_boilerplate(modifier)} -extension UnsafeBufferPointer { +extension UnsafeBufferPointer where Element: ~Copyable { + @inlinable + @inline(__always) + ${modifier} static var _empty: Self { + .init(start: nil, count: 0) + } + @inlinable @inline(__always) ${modifier} func _ptr(at index: Int) -> UnsafePointer { assert(index >= 0 && index < count) return baseAddress.unsafelyUnwrapped + index } + + @_alwaysEmitIntoClient + ${modifier} func _extracting(unchecked bounds: Range) -> Self { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Index out of range") + guard let start = self.baseAddress else { + return Self(start: nil, count: 0) + } + return Self(start: start + bounds.lowerBound, count: bounds.count) + } + + @inlinable + ${modifier} func _extracting(first n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (0, n))) + } + + @inlinable + ${modifier} func _extracting(last n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (count - n, count))) + } } + % end ${visibility_boilerplate("end")} diff --git a/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb b/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb index c7c6ee0fb..a2f840f7d 100644 --- a/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb +++ b/Sources/InternalCollectionsUtilities/UnsafeMutableBufferPointer+Extras.swift.gyb @@ -16,9 +16,48 @@ ${autogenerated_warning()} % for modifier in visibility_levels: ${visibility_boilerplate(modifier)} +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + @inline(__always) + ${modifier} static var _empty: Self { + .init(start: nil, count: 0) + } + + @inlinable + @inline(__always) + ${modifier} func _ptr(at index: Int) -> UnsafeMutablePointer { + assert(index >= 0 && index < count) + return baseAddress.unsafelyUnwrapped + index + } + + @_alwaysEmitIntoClient + ${modifier} func _extracting(unchecked bounds: Range) -> Self { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Index out of range") + guard let start = self.baseAddress else { + return Self(start: nil, count: 0) + } + return Self(start: start + bounds.lowerBound, count: bounds.count) + } + + @inlinable + ${modifier} func _extracting(first n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (0, n))) + } + + @inlinable + ${modifier} func _extracting(last n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (count - n, count))) + } +} + extension UnsafeMutableBufferPointer { @inlinable - public func initialize(fromContentsOf source: Self) -> Index { + ${modifier} func initialize(fromContentsOf source: Self) -> Index { guard source.count > 0 else { return 0 } precondition( source.count <= self.count, @@ -30,7 +69,7 @@ extension UnsafeMutableBufferPointer { } @inlinable - public func initialize(fromContentsOf source: Slice) -> Index { + ${modifier} func initialize(fromContentsOf source: Slice) -> Index { let sourceCount = source.count guard sourceCount > 0 else { return 0 } precondition( @@ -45,7 +84,7 @@ extension UnsafeMutableBufferPointer { extension Slice { @inlinable @inline(__always) - public func initialize( + ${modifier} func initialize( fromContentsOf source: UnsafeMutableBufferPointer ) -> Index where Base == UnsafeMutableBufferPointer @@ -56,7 +95,7 @@ extension Slice { } @inlinable @inline(__always) - public func initialize( + ${modifier} func initialize( fromContentsOf source: Slice> ) -> Index where Base == UnsafeMutableBufferPointer @@ -69,7 +108,7 @@ extension Slice { extension UnsafeMutableBufferPointer { @inlinable @inline(__always) - public func initializeAll( + ${modifier} func initializeAll( fromContentsOf source: C ) where C.Element == Element { let i = self.initialize(fromContentsOf: source) @@ -77,25 +116,41 @@ extension UnsafeMutableBufferPointer { } @inlinable @inline(__always) - public func initializeAll(fromContentsOf source: Self) { - let i = self.initialize(fromContentsOf: source) - assert(i == self.endIndex) + ${modifier} func initializeAll(fromContentsOf source: UnsafeBufferPointer) { + assert(self.count == source.count) + guard source.count > 0 else { return } + self.baseAddress.unsafelyUnwrapped.initialize( + from: source.baseAddress.unsafelyUnwrapped, + count: source.count) } @inlinable @inline(__always) - public func initializeAll(fromContentsOf source: Slice) { - let i = self.initialize(fromContentsOf: source) - assert(i == self.endIndex) + ${modifier} func initializeAll(fromContentsOf source: Slice>) { + self.initializeAll(fromContentsOf: .init(rebasing: source)) } @inlinable @inline(__always) - public func moveInitializeAll(fromContentsOf source: Self) { + ${modifier} func initializeAll(fromContentsOf source: Self) { + self.initializeAll(fromContentsOf: UnsafeBufferPointer(source)) + } + + @inlinable @inline(__always) + ${modifier} func initializeAll(fromContentsOf source: Slice) { + self.initializeAll(fromContentsOf: UnsafeMutableBufferPointer(rebasing: source)) + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable @inline(__always) + ${modifier} func moveInitializeAll(fromContentsOf source: Self) { let i = self.moveInitialize(fromContentsOf: source) assert(i == self.endIndex) } +} +extension UnsafeMutableBufferPointer { @inlinable @inline(__always) - public func moveInitializeAll(fromContentsOf source: Slice) { + ${modifier} func moveInitializeAll(fromContentsOf source: Slice) { let i = self.moveInitialize(fromContentsOf: source) assert(i == self.endIndex) } @@ -103,7 +158,7 @@ extension UnsafeMutableBufferPointer { extension Slice { @inlinable @inline(__always) - public func initializeAll( + ${modifier} func initializeAll( fromContentsOf source: C ) where Base == UnsafeMutableBufferPointer { let i = self.initialize(fromContentsOf: source) @@ -111,7 +166,7 @@ extension Slice { } @inlinable @inline(__always) - public func initializeAll( + ${modifier} func initializeAll( fromContentsOf source: UnsafeMutableBufferPointer ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) @@ -119,7 +174,7 @@ extension Slice { } @inlinable @inline(__always) - public func initializeAll( + ${modifier} func initializeAll( fromContentsOf source: Slice> ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) @@ -127,7 +182,7 @@ extension Slice { } @inlinable @inline(__always) - public func moveInitializeAll( + ${modifier} func moveInitializeAll( fromContentsOf source: UnsafeMutableBufferPointer ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) @@ -135,7 +190,7 @@ extension Slice { } @inlinable @inline(__always) - public func moveInitializeAll( + ${modifier} func moveInitializeAll( fromContentsOf source: Slice> ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) diff --git a/Sources/InternalCollectionsUtilities/autogenerated/Specialize.swift b/Sources/InternalCollectionsUtilities/autogenerated/Specialize.swift deleted file mode 100644 index 07087af24..000000000 --- a/Sources/InternalCollectionsUtilities/autogenerated/Specialize.swift +++ /dev/null @@ -1,54 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2023 - 2024 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 -// -//===----------------------------------------------------------------------===// - - -// ############################################################################# -// # # -// # DO NOT EDIT THIS FILE; IT IS AUTOGENERATED. # -// # # -// ############################################################################# - - - -// In single module mode, we need these declarations to be internal, -// but in regular builds we want them to be public. Unfortunately -// the current best way to do this is to duplicate all definitions. -#if COLLECTIONS_SINGLE_MODULE -/// Returns `x` as its concrete type `U`, or `nil` if `x` has a different -/// concrete type. -/// -/// This cast can be useful for dispatching to specializations of generic -/// functions. -@_transparent -@inlinable -internal func _specialize(_ x: T, for: U.Type) -> U? { - // Note: this was ported from recent versions of the Swift stdlib. - guard T.self == U.self else { - return nil - } - return _identityCast(x, to: U.self) -} -#else // !COLLECTIONS_SINGLE_MODULE -/// Returns `x` as its concrete type `U`, or `nil` if `x` has a different -/// concrete type. -/// -/// This cast can be useful for dispatching to specializations of generic -/// functions. -@_transparent -@inlinable -public func _specialize(_ x: T, for: U.Type) -> U? { - // Note: this was ported from recent versions of the Swift stdlib. - guard T.self == U.self else { - return nil - } - return _identityCast(x, to: U.self) -} -#endif // COLLECTIONS_SINGLE_MODULE diff --git a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift index 749f17bc8..ec6a6f824 100644 --- a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift +++ b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeBufferPointer+Extras.swift @@ -22,21 +22,83 @@ // but in regular builds we want them to be public. Unfortunately // the current best way to do this is to duplicate all definitions. #if COLLECTIONS_SINGLE_MODULE -extension UnsafeBufferPointer { +extension UnsafeBufferPointer where Element: ~Copyable { + @inlinable + @inline(__always) + internal static var _empty: Self { + .init(start: nil, count: 0) + } + @inlinable @inline(__always) internal func _ptr(at index: Int) -> UnsafePointer { assert(index >= 0 && index < count) return baseAddress.unsafelyUnwrapped + index } + + @_alwaysEmitIntoClient + internal func _extracting(unchecked bounds: Range) -> Self { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Index out of range") + guard let start = self.baseAddress else { + return Self(start: nil, count: 0) + } + return Self(start: start + bounds.lowerBound, count: bounds.count) + } + + @inlinable + internal func _extracting(first n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (0, n))) + } + + @inlinable + internal func _extracting(last n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (count - n, count))) + } } + #else // !COLLECTIONS_SINGLE_MODULE -extension UnsafeBufferPointer { +extension UnsafeBufferPointer where Element: ~Copyable { + @inlinable + @inline(__always) + public static var _empty: Self { + .init(start: nil, count: 0) + } + @inlinable @inline(__always) public func _ptr(at index: Int) -> UnsafePointer { assert(index >= 0 && index < count) return baseAddress.unsafelyUnwrapped + index } + + @_alwaysEmitIntoClient + public func _extracting(unchecked bounds: Range) -> Self { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Index out of range") + guard let start = self.baseAddress else { + return Self(start: nil, count: 0) + } + return Self(start: start + bounds.lowerBound, count: bounds.count) + } + + @inlinable + public func _extracting(first n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (0, n))) + } + + @inlinable + public func _extracting(last n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (count - n, count))) + } } + #endif // COLLECTIONS_SINGLE_MODULE diff --git a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift index 2661d8ca1..5c8dd9489 100644 --- a/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift +++ b/Sources/InternalCollectionsUtilities/autogenerated/UnsafeMutableBufferPointer+Extras.swift @@ -22,9 +22,48 @@ // but in regular builds we want them to be public. Unfortunately // the current best way to do this is to duplicate all definitions. #if COLLECTIONS_SINGLE_MODULE +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + @inline(__always) + internal static var _empty: Self { + .init(start: nil, count: 0) + } + + @inlinable + @inline(__always) + internal func _ptr(at index: Int) -> UnsafeMutablePointer { + assert(index >= 0 && index < count) + return baseAddress.unsafelyUnwrapped + index + } + + @_alwaysEmitIntoClient + internal func _extracting(unchecked bounds: Range) -> Self { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Index out of range") + guard let start = self.baseAddress else { + return Self(start: nil, count: 0) + } + return Self(start: start + bounds.lowerBound, count: bounds.count) + } + + @inlinable + internal func _extracting(first n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (0, n))) + } + + @inlinable + internal func _extracting(last n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (count - n, count))) + } +} + extension UnsafeMutableBufferPointer { @inlinable - public func initialize(fromContentsOf source: Self) -> Index { + internal func initialize(fromContentsOf source: Self) -> Index { guard source.count > 0 else { return 0 } precondition( source.count <= self.count, @@ -36,7 +75,7 @@ extension UnsafeMutableBufferPointer { } @inlinable - public func initialize(fromContentsOf source: Slice) -> Index { + internal func initialize(fromContentsOf source: Slice) -> Index { let sourceCount = source.count guard sourceCount > 0 else { return 0 } precondition( @@ -51,7 +90,7 @@ extension UnsafeMutableBufferPointer { extension Slice { @inlinable @inline(__always) - public func initialize( + internal func initialize( fromContentsOf source: UnsafeMutableBufferPointer ) -> Index where Base == UnsafeMutableBufferPointer @@ -62,7 +101,7 @@ extension Slice { } @inlinable @inline(__always) - public func initialize( + internal func initialize( fromContentsOf source: Slice> ) -> Index where Base == UnsafeMutableBufferPointer @@ -75,7 +114,7 @@ extension Slice { extension UnsafeMutableBufferPointer { @inlinable @inline(__always) - public func initializeAll( + internal func initializeAll( fromContentsOf source: C ) where C.Element == Element { let i = self.initialize(fromContentsOf: source) @@ -83,25 +122,41 @@ extension UnsafeMutableBufferPointer { } @inlinable @inline(__always) - public func initializeAll(fromContentsOf source: Self) { - let i = self.initialize(fromContentsOf: source) - assert(i == self.endIndex) + internal func initializeAll(fromContentsOf source: UnsafeBufferPointer) { + assert(self.count == source.count) + guard source.count > 0 else { return } + self.baseAddress.unsafelyUnwrapped.initialize( + from: source.baseAddress.unsafelyUnwrapped, + count: source.count) } @inlinable @inline(__always) - public func initializeAll(fromContentsOf source: Slice) { - let i = self.initialize(fromContentsOf: source) - assert(i == self.endIndex) + internal func initializeAll(fromContentsOf source: Slice>) { + self.initializeAll(fromContentsOf: .init(rebasing: source)) } @inlinable @inline(__always) - public func moveInitializeAll(fromContentsOf source: Self) { + internal func initializeAll(fromContentsOf source: Self) { + self.initializeAll(fromContentsOf: UnsafeBufferPointer(source)) + } + + @inlinable @inline(__always) + internal func initializeAll(fromContentsOf source: Slice) { + self.initializeAll(fromContentsOf: UnsafeMutableBufferPointer(rebasing: source)) + } +} + +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable @inline(__always) + internal func moveInitializeAll(fromContentsOf source: Self) { let i = self.moveInitialize(fromContentsOf: source) assert(i == self.endIndex) } +} +extension UnsafeMutableBufferPointer { @inlinable @inline(__always) - public func moveInitializeAll(fromContentsOf source: Slice) { + internal func moveInitializeAll(fromContentsOf source: Slice) { let i = self.moveInitialize(fromContentsOf: source) assert(i == self.endIndex) } @@ -109,7 +164,7 @@ extension UnsafeMutableBufferPointer { extension Slice { @inlinable @inline(__always) - public func initializeAll( + internal func initializeAll( fromContentsOf source: C ) where Base == UnsafeMutableBufferPointer { let i = self.initialize(fromContentsOf: source) @@ -117,7 +172,7 @@ extension Slice { } @inlinable @inline(__always) - public func initializeAll( + internal func initializeAll( fromContentsOf source: UnsafeMutableBufferPointer ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) @@ -125,7 +180,7 @@ extension Slice { } @inlinable @inline(__always) - public func initializeAll( + internal func initializeAll( fromContentsOf source: Slice> ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) @@ -133,7 +188,7 @@ extension Slice { } @inlinable @inline(__always) - public func moveInitializeAll( + internal func moveInitializeAll( fromContentsOf source: UnsafeMutableBufferPointer ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) @@ -141,7 +196,7 @@ extension Slice { } @inlinable @inline(__always) - public func moveInitializeAll( + internal func moveInitializeAll( fromContentsOf source: Slice> ) where Base == UnsafeMutableBufferPointer { let target = UnsafeMutableBufferPointer(rebasing: self) @@ -149,6 +204,45 @@ extension Slice { } } #else // !COLLECTIONS_SINGLE_MODULE +extension UnsafeMutableBufferPointer where Element: ~Copyable { + @inlinable + @inline(__always) + public static var _empty: Self { + .init(start: nil, count: 0) + } + + @inlinable + @inline(__always) + public func _ptr(at index: Int) -> UnsafeMutablePointer { + assert(index >= 0 && index < count) + return baseAddress.unsafelyUnwrapped + index + } + + @_alwaysEmitIntoClient + public func _extracting(unchecked bounds: Range) -> Self { + assert(bounds.lowerBound >= 0 && bounds.upperBound <= count, + "Index out of range") + guard let start = self.baseAddress else { + return Self(start: nil, count: 0) + } + return Self(start: start + bounds.lowerBound, count: bounds.count) + } + + @inlinable + public func _extracting(first n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (0, n))) + } + + @inlinable + public func _extracting(last n: Int) -> Self { + assert(n >= 0) + if n >= count { return self } + return extracting(Range(uncheckedBounds: (count - n, count))) + } +} + extension UnsafeMutableBufferPointer { @inlinable public func initialize(fromContentsOf source: Self) -> Index { @@ -209,24 +303,40 @@ extension UnsafeMutableBufferPointer { assert(i == self.endIndex) } + @inlinable @inline(__always) + public func initializeAll(fromContentsOf source: UnsafeBufferPointer) { + assert(self.count == source.count) + guard source.count > 0 else { return } + self.baseAddress.unsafelyUnwrapped.initialize( + from: source.baseAddress.unsafelyUnwrapped, + count: source.count) + } + + @inlinable @inline(__always) + public func initializeAll(fromContentsOf source: Slice>) { + self.initializeAll(fromContentsOf: .init(rebasing: source)) + } + @inlinable @inline(__always) public func initializeAll(fromContentsOf source: Self) { - let i = self.initialize(fromContentsOf: source) - assert(i == self.endIndex) + self.initializeAll(fromContentsOf: UnsafeBufferPointer(source)) } @inlinable @inline(__always) public func initializeAll(fromContentsOf source: Slice) { - let i = self.initialize(fromContentsOf: source) - assert(i == self.endIndex) + self.initializeAll(fromContentsOf: UnsafeMutableBufferPointer(rebasing: source)) } +} +extension UnsafeMutableBufferPointer where Element: ~Copyable { @inlinable @inline(__always) public func moveInitializeAll(fromContentsOf source: Self) { let i = self.moveInitialize(fromContentsOf: source) assert(i == self.endIndex) } +} +extension UnsafeMutableBufferPointer { @inlinable @inline(__always) public func moveInitializeAll(fromContentsOf source: Slice) { let i = self.moveInitialize(fromContentsOf: source) diff --git a/Tests/BitCollectionsTests/BitArrayTests.swift b/Tests/BitCollectionsTests/BitArrayTests.swift index a1dd95468..1e31a1d32 100644 --- a/Tests/BitCollectionsTests/BitArrayTests.swift +++ b/Tests/BitCollectionsTests/BitArrayTests.swift @@ -485,13 +485,13 @@ final class BitArrayTests: CollectionTestCase { expectEqual(try MinimalDecoder.decode(v4, as: BitArray.self), b4) let v5: MinimalEncoder.Value = .uint64(42) - expectThrows(try MinimalDecoder.decode(v5, as: BitArray.self)) + expectThrows { try MinimalDecoder.decode(v5, as: BitArray.self) } let v6: MinimalEncoder.Value = .array([]) - expectThrows(try MinimalDecoder.decode(v6, as: BitArray.self)) + expectThrows { try MinimalDecoder.decode(v6, as: BitArray.self) } let v7: MinimalEncoder.Value = .array([.uint64(1)]) - expectThrows(try MinimalDecoder.decode(v7, as: BitArray.self)) + expectThrows { try MinimalDecoder.decode(v7, as: BitArray.self) } let v8: MinimalEncoder.Value = .array([ .uint64(1), @@ -499,13 +499,13 @@ final class BitArrayTests: CollectionTestCase { .uint64(0), .uint64(0) ]) - expectThrows(try MinimalDecoder.decode(v8, as: BitArray.self)) + expectThrows { try MinimalDecoder.decode(v8, as: BitArray.self) } let v9: MinimalEncoder.Value = .array([.uint64(100), .uint64(0)]) - expectThrows(try MinimalDecoder.decode(v9, as: BitArray.self)) + expectThrows { try MinimalDecoder.decode(v9, as: BitArray.self) } let v10: MinimalEncoder.Value = .array([.uint64(16), .uint64(UInt64.max)]) - expectThrows(try MinimalDecoder.decode(v10, as: BitArray.self)) + expectThrows { try MinimalDecoder.decode(v10, as: BitArray.self) } } func test_replaceSubrange_Array() { diff --git a/Tests/CollectionsTestSupportTests/MinimalTypeConformances.swift b/Tests/CollectionsTestSupportTests/MinimalTypeConformances.swift index 786e69f30..6251d4630 100644 --- a/Tests/CollectionsTestSupportTests/MinimalTypeConformances.swift +++ b/Tests/CollectionsTestSupportTests/MinimalTypeConformances.swift @@ -12,6 +12,7 @@ import XCTest #if !COLLECTIONS_SINGLE_MODULE import _CollectionsTestSupport +import Future #endif final class MinimalTypeTests: CollectionTestCase { @@ -43,4 +44,14 @@ final class MinimalTypeTests: CollectionTestCase { func testMinimalBidirectionalCollection() { checkBidirectionalCollection(MinimalBidirectionalCollection(0 ..< 50), expectedContents: 0 ..< 50) } + + #if false // FIXME: Track down compiler crash + func testTestContainer() { + guard #available(SwiftStdlib 6.2, *) else { return } + let a = TestContainer( + contents: RigidArray(count: 100, initializedWith: { $0 }), + spanCounts: [3, 1, 7]) + checkContainer(a, expectedContents: 0 ..< 100) + } + #endif } diff --git a/Tests/CollectionsTestSupportTests/StaccatoContainerTests.swift b/Tests/CollectionsTestSupportTests/StaccatoContainerTests.swift new file mode 100644 index 000000000..2e8fa873a --- /dev/null +++ b/Tests/CollectionsTestSupportTests/StaccatoContainerTests.swift @@ -0,0 +1,111 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import _CollectionsTestSupport +import Future + +@available(SwiftStdlib 6.2, *) +class StaccatoContainerTests: CollectionTestCase { + func checkStriding( + _ container: borrowing C, + spanCounts: [Int], + file: StaticString = #file, + line: UInt = #line + ) { + let entry = context.push("checkStriding", file: file, line: line) + defer { context.pop(entry) } + + var i = container.startIndex + var j = 0 + var seen = 0 + while true { + let span = container.nextSpan(after: &i) + if i == container.endIndex { + expectLessThanOrEqual(span.count, spanCounts[j]) + expectEqual(span.count, container.count - seen) + break + } + expectEqual(span.count, spanCounts[j]) + seen += span.count + j += 1 + if j == spanCounts.count { j = 0 } + } + } + + func test_Basic1() { + let items = StaccatoContainer( + contents: RigidArray(copying: 0 ..< 10), + spanCounts: [1]) + checkContainer(items, expectedContents: 0 ..< 10) + } + + func test_Basic2() { + let items = StaccatoContainer( + contents: RigidArray(copying: 0 ..< 10), + spanCounts: [3]) + checkContainer(items, expectedContents: 0 ..< 10) + } + + func test_Basic3() { + let items = StaccatoContainer( + contents: RigidArray(copying: 0 ..< 13), + spanCounts: [1, 2]) + checkContainer(items, expectedContents: 0 ..< 13) + } + + func test_SingleSpec() { + withEvery("c", in: [0, 10, 20]) { c in + withEvery("spanCount", in: 1 ... 20) { spanCount in + let items = StaccatoContainer( + contents: RigidArray(copying: 0 ..< c), + spanCounts: [spanCount]) + + checkStriding(items, spanCounts: [spanCount]) + checkContainer(items, expectedContents: 0 ..< c) + } + } + } + + func test_DoubleSpec() { + withEvery("c", in: [0, 10, 20]) { c in + withEvery("spanCount1", in: 1 ... 10) { spanCount1 in + withEvery("spanCount2", in: 1 ... 10) { spanCount2 in + let spanCounts = [spanCount1, spanCount2] + let items = StaccatoContainer( + contents: RigidArray(copying: 0 ..< c), + spanCounts: spanCounts) + + checkStriding(items, spanCounts: spanCounts) + checkContainer(items, expectedContents: 0 ..< c) + } + } + } + } + + func test_TripleSpec() { + withEvery("c", in: [0, 10, 20]) { c in + withEvery("spanCount1", in: 1 ... 5) { spanCount1 in + withEvery("spanCount2", in: 1 ... 5) { spanCount2 in + withEvery("spanCount3", in: 1 ... 5) { spanCount3 in + let spanCounts = [spanCount1, spanCount2, spanCount3] + let items = StaccatoContainer( + contents: RigidArray(copying: 0 ..< c), + spanCounts: spanCounts) + + checkStriding(items, spanCounts: spanCounts) + checkContainer(items, expectedContents: 0 ..< c) + } + } + } + } + } +} diff --git a/Tests/DequeTests/DequeInternals.swift b/Tests/DequeTests/DequeInternals.swift index c34a3f18f..1ba5ad531 100644 --- a/Tests/DequeTests/DequeInternals.swift +++ b/Tests/DequeTests/DequeInternals.swift @@ -52,6 +52,18 @@ extension Deque { } } +extension RigidDeque { + init>(layout: DequeLayout, contents: C) { + precondition(contents.count == layout.count) + let contents = ContiguousArray(contents) + self.init( + _capacity: layout.capacity, startSlot: layout.startSlot, count: contents.count + ) { + contents[$0] + } + } +} + extension LifetimeTracker { func deque( with layout: DequeLayout @@ -60,15 +72,20 @@ extension LifetimeTracker { let deque = Deque(layout: layout, contents: contents) return (deque, contents) } + + func rigidDeque(with layout: DequeLayout) -> RigidDeque> { + let contents = self.instances(for: layout.valueRange) + return RigidDeque(layout: layout, contents: contents) + } } -func withEveryDeque( +func withEveryDeque( _ label: String, - ofCapacities capacities: C, + ofCapacities capacities: some Collection, startValue: Int = 0, file: StaticString = #file, line: UInt = #line, _ body: (DequeLayout) throws -> Void -) rethrows -> Void where C.Element == Int { +) rethrows -> Void { // Exhaustive tests for all deque layouts of various capacities for capacity in capacities { for startSlot in 0 ..< capacity { diff --git a/Tests/DequeTests/DequeTests.swift b/Tests/DequeTests/DequeTests.swift index 34c947fe2..dea19583a 100644 --- a/Tests/DequeTests/DequeTests.swift +++ b/Tests/DequeTests/DequeTests.swift @@ -17,6 +17,11 @@ import _CollectionsTestSupport @_spi(Testing) import DequeModule #endif +extension Deque { + var _capacity: Int { _unstableCapacity } + var _startSlot: Int { _unstableStartSlot } +} + final class DequeTests: CollectionTestCase { func test_testingSPIs() { let deque = Deque(_capacity: 5, startSlot: 2, contents: [10, 20, 30, 40]) @@ -176,16 +181,18 @@ final class DequeTests: CollectionTestCase { let d1 = Deque>( unsafeUninitializedCapacity: cap, initializingWith: { target, c in - expectNotNil(target.baseAddress) expectEqual(target.count, cap) expectEqual(c, 0) - contents.withUnsafeBufferPointer { source in - precondition(source.count <= target.count) - target.baseAddress!.initialize( - from: source.baseAddress!, - count: source.count) + if target.count > 0 { + expectNotNil(target.baseAddress) + contents.withUnsafeBufferPointer { source in + precondition(source.count <= target.count) + target.baseAddress!.initialize( + from: source.baseAddress!, + count: source.count) + } + c = count } - c = count }) expectEqualElements(d1, contents) } @@ -202,23 +209,25 @@ final class DequeTests: CollectionTestCase { func workaroundSR14134(cap: Int, count: Int, tracker: LifetimeTracker) { // This function works around https://bugs.swift.org/browse/SR-14134 let contents = tracker.instances(for: 0 ..< count) - expectThrows( + expectThrows({ try Deque>( unsafeUninitializedCapacity: cap, initializingWith: { target, c in - expectNotNil(target.baseAddress) expectEqual(target.count, cap) expectEqual(c, 0) - contents.withUnsafeBufferPointer { source in - precondition(source.count <= target.count) - target.baseAddress!.initialize( - from: source.baseAddress!, - count: source.count) + if target.count > 0 { + expectNotNil(target.baseAddress) + contents.withUnsafeBufferPointer { source in + precondition(source.count <= target.count) + target.baseAddress!.initialize( + from: source.baseAddress!, + count: source.count) + } + c = count } - c = count throw TestError(count) }) - ) { error in + }) { error in expectEqual(error as? TestError, TestError(count)) } } diff --git a/Tests/DequeTests/RigidDequeTests.swift b/Tests/DequeTests/RigidDequeTests.swift new file mode 100644 index 000000000..15534d2f9 --- /dev/null +++ b/Tests/DequeTests/RigidDequeTests.swift @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +import XCTest + +#if COLLECTIONS_SINGLE_MODULE +@_spi(Testing) import Collections +#else +import _CollectionsTestSupport +@_spi(Testing) import DequeModule +import Future +#endif + +#if false // FIXME: Debug compiler crash +@available(SwiftStdlib 6.0, *) +class RigidDequeTests: CollectionTestCase { + func test_basic() { + var deque = RigidDeque(capacity: 100) + expectEqual(deque.count, 0) + } + + func test_validate_Container() { + let c = 100 + + withEveryDeque("layout", ofCapacities: [c]) { layout in + withLifetimeTracking { tracker in + let deque = tracker.rigidDeque(with: layout) + let contents = tracker.instances(for: 0 ..< layout.count) + checkContainer(deque, expectedContents: contents) + } + } + } +} +#endif diff --git a/Tests/FutureTests/ArrayTests/ArrayLayout.swift b/Tests/FutureTests/ArrayTests/ArrayLayout.swift new file mode 100644 index 000000000..9a8563ac9 --- /dev/null +++ b/Tests/FutureTests/ArrayTests/ArrayLayout.swift @@ -0,0 +1,96 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +import _CollectionsTestSupport +import Future + +struct ArrayLayout { + var capacity: Int + var count: Int + + init(capacity: Int, count: Int) { + precondition(count >= 0 && count <= capacity) + self.capacity = capacity + self.count = count + } +} + +func withSomeArrayLayouts( + _ label: String, + ofCapacities capacities: some Sequence, + file: StaticString = #file, + line: UInt = #line, + run body: (ArrayLayout) throws(E) -> Void +) throws(E) { + let context = TestContext.current + for capacity in capacities { + var counts: Set = [] + counts.insert(0) + counts.insert(capacity) + counts.insert(capacity / 2) + if capacity >= 1 { + counts.insert(1) + counts.insert(capacity - 1) + } + if capacity >= 2 { + counts.insert(2) + counts.insert(capacity - 2) + } + for count in counts { + let layout = ArrayLayout(capacity: capacity, count: count) + let entry = context.push("\(label): \(layout)", file: file, line: line) + + var done = false + defer { + context.pop(entry) + if !done { + print(context.currentTrace(title: "Throwing trace")) + } + } + try body(layout) + done = true + } + } +} + +extension RigidArray where Element: ~Copyable { + init(layout: ArrayLayout, using generator: (Int) -> Element) { + self.init( + capacity: layout.capacity, + count: layout.count, + initializedWith: generator) + } +} + +extension DynamicArray where Element: ~Copyable { + init(layout: ArrayLayout, using generator: (Int) -> Element) { + self.init(consuming: RigidArray(layout: layout, using: generator)) + } +} + +extension LifetimeTracker { + func rigidArray( + layout: ArrayLayout, + using generator: (Int) -> Element = { $0 } + ) -> RigidArray> { + RigidArray(layout: layout, using: { self.instance(for: generator($0)) }) + } + + func dynamicArray( + layout: ArrayLayout, + using generator: (Int) -> Element = { $0 } + ) -> DynamicArray> { + let contents = RigidArray(layout: layout) { + self.instance(for: generator($0)) + } + return DynamicArray(consuming: contents) + } +} diff --git a/Tests/FutureTests/ArrayTests/DynamicArrayTests.swift b/Tests/FutureTests/ArrayTests/DynamicArrayTests.swift new file mode 100644 index 000000000..2610f5345 --- /dev/null +++ b/Tests/FutureTests/ArrayTests/DynamicArrayTests.swift @@ -0,0 +1,805 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import _CollectionsTestSupport +import Future +import Synchronization + +@available(SwiftStdlib 6.2, *) +class DynamicArrayTests: CollectionTestCase { + func test_validate_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let items = DynamicArray(consuming: tracker.rigidArray(layout: layout)) + let expected = (0 ..< layout.count).map { tracker.instance(for: $0) } + expectEqual(tracker.instances, 2 * layout.count) + checkContainer(items, expectedContents: expected) + } + } + } + + func test_basics() { + withLifetimeTracking { tracker in + typealias Value = LifetimeTrackedStruct + + var array = DynamicArray() + expectTrue(array.isEmpty) + expectEqual(array.count, 0) + expectEqual(array.capacity, 0) + expectEqual(tracker.instances, 0) + + array.append(tracker.structInstance(for: 10)) + expectFalse(array.isEmpty) + expectEqual(array.count, 1) + expectEqual(array.capacity, 1) // This assumes a specific growth behavior + expectEqual(array[0].payload, 10) + expectEqual(tracker.instances, 1) + + array.append(tracker.structInstance(for: 20)) + expectFalse(array.isEmpty) + expectEqual(array.count, 2) + expectEqual(array.capacity, 2) // This assumes a specific growth behavior + expectEqual(array[0].payload, 10) + expectEqual(array[1].payload, 20) + expectEqual(tracker.instances, 2) + + let old = array.remove(at: 0) + expectEqual(old.payload, 10) + expectFalse(array.isEmpty) + expectEqual(array.count, 1) + expectEqual(array.capacity, 2) // This assumes a specific growth behavior + expectEqual(array[0].payload, 20) + expectEqual(tracker.instances, 2) + _ = consume old + expectEqual(tracker.instances, 1) + + let old2 = array.remove(at: 0) + expectEqual(old2.payload, 20) + expectEqual(array.count, 0) + expectEqual(array.capacity, 2) // This assumes a specific growth behavior + expectTrue(array.isEmpty) + expectEqual(tracker.instances, 1) + _ = consume old2 + expectEqual(tracker.instances, 0) + } + } + + func test_init_capacity() { + do { + let a = DynamicArray(capacity: 0) + expectEqual(a.capacity, 0) + expectEqual(a.count, 0) + expectEqual(a.freeCapacity, 0) + expectTrue(a.isEmpty) + } + + do { + let a = DynamicArray(capacity: 10) + expectEqual(a.capacity, 10) + expectEqual(a.count, 0) + expectEqual(a.freeCapacity, 10) + expectTrue(a.isEmpty) + } + } + + func test_init_generator() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.dynamicArray(layout: layout) + expectEqual(a.capacity, layout.capacity) + expectEqual(a.count, layout.count) + expectEqual(a.freeCapacity, layout.capacity - layout.count) + expectEqual(a.isEmpty, layout.count == 0) + expectContainerContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + + func test_init_repeating() { + withEvery("c", in: [0, 10, 100]) { c in + withLifetimeTracking { tracker in + let value = tracker.instance(for: 0) + let a = DynamicArray(repeating: value, count: c) + expectEqual(a.capacity, c) + expectEqual(a.count, c) + expectEqual(a.freeCapacity, 0) + expectEqual(a.isEmpty, c == 0) + for i in 0 ..< c { + expectIdentical(a[i], value) + } + } + } + } + + func test_init_copying_Sequence() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = DynamicArray( + capacity: layout.capacity, + copying: (0 ..< layout.count).map { tracker.instance(for: $0) }) + expectEqual(tracker.instances, layout.count) + expectEqual(a.capacity, layout.capacity) + expectEqual(a.count, layout.count) + expectEqual(a.isEmpty, layout.count == 0) + for i in 0 ..< layout.count { + expectEqual(a[i].payload, i) + } + } + } + } + + func test_init_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("spanCounts", in: [ + [1], + [3, 5, 7], + [10, 3], + ] as [[Int]]) { spanCounts in + withLifetimeTracking { tracker in + let additions = StaccatoContainer( + contents: RigidArray( + copying: (0 ..< layout.count).map { tracker.instance(for: $0) }), + spanCounts: spanCounts) + + let array = DynamicArray( + capacity: layout.capacity, copying: additions) + expectEqual(tracker.instances, layout.count) + expectEqual(array.capacity, layout.capacity) + expectEqual(array.count, layout.count) + for i in 0 ..< layout.count { + expectEqual(array[i].payload, i) + } + } + } + } + } + + func test_span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.dynamicArray(layout: layout) + let span = a.span + expectEqual(span.count, layout.count) + for i in 0 ..< span.count { + expectEqual(span[i].payload, i) + } + } + } + } + + func test_mutableSpan() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + var span = a.mutableSpan + expectEqual(span.count, layout.count) + for i in 0 ..< layout.count { + expectEqual(span[i].payload, i) + span[i] = tracker.instance(for: -i) + } + for i in 0 ..< layout.count { + expectEqual(span[i].payload, -i) + } + for i in 0 ..< layout.count { + expectEqual(a[i].payload, -i) + } + } + } + } + + func test_nextSpan() { + // DynamicArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.dynamicArray(layout: layout) + let whole = a.span + var i = 0 + let first = a.nextSpan(after: &i) + expectEqual(i, layout.count) + expectTrue(first.isIdentical(to: whole)) + let second = a.nextSpan(after: &i) + expectEqual(i, layout.count) + expectTrue(second.isEmpty) + } + } + } + + func test_previousSpan() { + // RigidArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.dynamicArray(layout: layout) + let whole = a.span + var i = layout.count + let first = a.previousSpan(before: &i) + expectEqual(i, 0) + expectTrue(first.isIdentical(to: whole)) + let second = a.previousSpan(before: &i) + expectEqual(i, 0) + expectTrue(second.isEmpty) + } + } + } + + func test_nextMutableSpan() { + // RigidArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + var i = 0 + var span = a.nextMutableSpan(after: &i) + expectEqual(i, layout.count) + expectEqual(span.count, layout.count) + span = a.nextMutableSpan(after: &i) + expectEqual(i, layout.count) + expectTrue(span.isEmpty) + } + } + } + + func test_index_properties() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + let a = DynamicArray(layout: layout, using: { $0 }) + expectEqual(a.startIndex, 0) + expectEqual(a.endIndex, layout.count) + expectEqual(a.indices, 0 ..< layout.count) + } + } + + func test_swapAt() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + withEvery("i", in: 0 ..< layout.count / 2) { i in + a.swapAt(i, layout.count - 1 - i) + } + let expected = (0 ..< layout.count).reversed() + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count) + } + } + } + + func test_borrowElement() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.dynamicArray(layout: layout) + for i in 0 ..< layout.count { + let item = a.borrowElement(at: i) + expectEqual(item[].payload, i) + } + } + } + } + + func test_mutateElement() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + for i in 0 ..< layout.count { + var item = a.mutateElement(at: i) + expectEqual(item[].payload, i) + item[] = tracker.instance(for: -i) + expectEqual(tracker.instances, layout.count) + } + } + } + } + + func test_withUnsafeMutableBufferPointer() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + unsafe a.withUnsafeMutableBufferPointer { buffer, count in + expectEqual(buffer.count, layout.capacity) + expectEqual(count, layout.count) + unsafe buffer.extracting(0 ..< count).deinitialize() + expectEqual(tracker.instances, 0) + count = 0 + } + expectEqual(a.count, 0) + + unsafe a.withUnsafeMutableBufferPointer { buffer, count in + expectEqual(buffer.count, layout.capacity) + expectEqual(count, 0) + for i in 0 ..< buffer.count { + unsafe buffer.initializeElement(at: i, to: tracker.instance(for: -i)) + count += 1 + } + expectEqual(tracker.instances, layout.capacity) + } + expectEqual(a.count, layout.capacity) + + struct TestError: Error {} + + expectThrows({ + unsafe try a.withUnsafeMutableBufferPointer { buffer, count in + expectEqual(tracker.instances, layout.capacity) + while count > 0 { + if count == layout.count { break } + unsafe buffer.deinitializeElement(at: count - 1) + count -= 1 + } + throw TestError() + } + }) { error in + expectTrue(error is TestError) + } + expectContainerContents( + a, + equivalentTo: (0 ..< layout.count).map { -$0 }, + by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count) + } + } + } + + func test_reallocate() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery( + "newCapacity", + in: [ + layout.capacity, layout.count, layout.count + 1, layout.capacity + 1 + ] as Set + ) { newCapacity in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, layout.capacity) + a.reallocate(capacity: newCapacity) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, newCapacity) + expectEqual(tracker.instances, layout.count) + expectContainerContents( + a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + } + + func test_reserveCapacity() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery( + "newCapacity", + in: [ + 0, layout.count - 1, layout.count, layout.count + 1, + layout.capacity, layout.capacity + 1 + ] as Set + ) { newCapacity in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, layout.capacity) + a.reserveCapacity(newCapacity) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, Swift.max(layout.capacity, newCapacity)) + expectEqual(tracker.instances, layout.count) + expectContainerContents( + a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeAll() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + a.removeAll() + expectTrue(a.isEmpty) + expectEqual(a.capacity, 0) + expectEqual(tracker.instances, 0) + } + } + } + + func test_removeAll_keepingCapacity() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + a.removeAll(keepingCapacity: true) + expectTrue(a.isEmpty) + expectEqual(a.capacity, layout.capacity) + expectEqual(tracker.instances, 0) + } + } + } + + func test_removeLast() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + withEvery("i", in: 0 ..< layout.count) { i in + let old = a.removeLast() + expectEqual(old.payload, layout.count - 1 - i) + expectEqual(a.count, layout.count - 1 - i) + expectEqual(a.capacity, layout.capacity) + } + expectEqual(tracker.instances, 0) + } + } + } + + func test_removeLast_k() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("k", in: 0 ..< layout.count) { k in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeLast(k) + + var a = tracker.dynamicArray(layout: layout) + expectEqual(tracker.instances, layout.count) + a.removeLast(k) + expectEqual(tracker.instances, layout.count - k) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_remove_at() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ..< layout.count) { i in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.remove(at: i) + + var a = tracker.dynamicArray(layout: layout) + let old = a.remove(at: i) + expectEqual(old.payload, i) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeSubrange() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeSubrange(range) + + var a = tracker.dynamicArray(layout: layout) + a.removeSubrange(range) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeAll_where() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeAll(where: { $0.isMultiple(of: 2) }) + + var a = tracker.dynamicArray(layout: layout) + a.removeAll(where: { $0.payload.isMultiple(of: 2) }) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + + func test_popLast() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let expectedItem = expected.popLast() + + var a = tracker.dynamicArray(layout: layout) + let item = a.popLast() + + expectEquivalent(item, expectedItem, by: { $0?.payload == $1 }) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + + func test_append_geometric_growth() { + // This test depends on the precise growth curve of DynamicArray, + // which is not part of its stable API. The test may need to be updated + // accordingly. + withLifetimeTracking { tracker in + typealias Value = LifetimeTracked + + var array = DynamicArray() + expectEqual(array.capacity, 0) + + array.append(tracker.instance(for: 0)) + expectEqual(array.capacity, 1) + array.append(tracker.instance(for: 1)) + expectEqual(array.capacity, 2) + array.append(tracker.instance(for: 2)) + expectEqual(array.capacity, 4) + array.append(tracker.instance(for: 3)) + expectEqual(array.capacity, 4) + array.append(tracker.instance(for: 4)) + expectEqual(array.capacity, 8) + array.append(tracker.instance(for: 5)) + expectEqual(array.capacity, 8) + array.append(tracker.instance(for: 6)) + expectEqual(array.capacity, 8) + array.append(tracker.instance(for: 7)) + expectEqual(array.capacity, 8) + array.append(tracker.instance(for: 8)) + expectEqual(array.capacity, 16) + + for i in 9 ..< 100 { + array.append(tracker.instance(for: i)) + } + expectEqual(tracker.instances, 100) + expectEqual(array.count, 100) + expectEqual(array.capacity, 128) + + + array.append( + copying: RigidArray(count: 300) { tracker.instance(for: 100 + $0) }) + expectEqual(array.capacity, 400) + + expectEqual(tracker.instances, 400) + for i in 0 ..< 400 { + expectEqual(array.borrowElement(at: i)[].payload, i) + expectEqual(array[i].payload, i) + } + + _ = consume array + expectEqual(tracker.instances, 0) + } + } + + func test_append() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let c = 2 * layout.capacity + 10 + var a = tracker.dynamicArray(layout: layout) + for i in layout.count ..< c { + a.append(tracker.instance(for: i)) + expectEqual(a.count, i + 1) + expectContainerContents(a, equivalentTo: 0 ..< i + 1, by: { $0.payload == $1 }) + } + expectEqual(tracker.instances, c) + } + } + } + + func test_append_copying_MinimalSequence() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("isContiguous", in: [false, true]) { isContiguous in + withLifetimeTracking { tracker in + let c = 2 * layout.capacity + 10 + var a = tracker.dynamicArray(layout: layout) + a.append(copying: MinimalSequence( + elements: (layout.count ..< c).map { tracker.instance(for: $0) }, + underestimatedCount: .half, + isContiguous: isContiguous)) + expectContainerContents( + a, equivalentTo: 0 ..< c, by: { $0.payload == $1}) + expectEqual(tracker.instances, c) + } + } + } + } + + func test_append_copying_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("additions", in: [0, 1, 10, 100]) { additions in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + let b = RigidArray(count: additions) { + tracker.instance(for: layout.count + $0) + } + a.append(copying: b.span) + let c = layout.count + additions + expectContainerContents( + a, equivalentTo: 0 ..< c, by: { $0.payload == $1 }) + expectEqual(tracker.instances, c) + } + } + } + } + + func test_append_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("additions", in: [0, 1, 10, 100]) { additions in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + var a = tracker.dynamicArray(layout: layout) + + let c = layout.count + additions + let addition = (layout.count ..< c).map { + tracker.instance(for: $0) + } + let b = StaccatoContainer( + contents: RigidArray(copying: addition), + spanCounts: [spanCount]) + a.append(copying: b) + expectContainerContents( + a, equivalentTo: 0 ..< c, by: { $0.payload == $1 }) + expectEqual(tracker.instances, c) + } + } + } + } + } + + func test_insert_at() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.insert(-1, at: i) + + var a = tracker.dynamicArray(layout: layout) + a.insert(tracker.instance(for: -1), at: i) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count + 1) + } + } + } + } + + func test_insert_copying_Collection() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("c", in: [0, 1, 10, 100]) { c in + withEvery("i", in: 0 ... layout.count) { i in + withLifetimeTracking { tracker in + let addition = (layout.count ..< layout.count + c) + + var expected = Array(0 ..< layout.count) + expected.insert(contentsOf: addition, at: i) + + let trackedAddition = addition.map { tracker.instance(for: $0) } + var a = tracker.dynamicArray(layout: layout) + a.insert(copying: trackedAddition, at: i) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count + c) + } + } + } + } + } + + func test_insert_copying_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withEvery("c", in: [0, 1, 10, 100]) { c in + withLifetimeTracking { tracker in + let addition = Array(layout.count ..< layout.count + c) + + var expected = Array(0 ..< layout.count) + expected.insert(contentsOf: addition, at: i) + + let rigidAddition = RigidArray(count: addition.count) { + tracker.instance(for: addition[$0]) + } + var a = tracker.dynamicArray(layout: layout) + a.insert(copying: rigidAddition.span, at: i) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count + c) + } + } + } + } + } + + func test_insert_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withEvery("c", in: [0, 1, 10, 100]) { c in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + + var expected = Array(0 ..< layout.count) + let addition = Array(layout.count ..< layout.count + c) + expected.insert(contentsOf: addition, at: i) + + var a = tracker.dynamicArray(layout: layout) + let rigidAddition = StaccatoContainer( + contents: RigidArray(count: addition.count) { + tracker.instance(for: addition[$0]) + }, + spanCounts: [spanCount]) + a.insert(copying: rigidAddition, at: i) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count + c) + } + } + } + } + } + } + + func test_replaceSubrange_copying_Collection() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: [0, 1, 10, 100]) { c in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.dynamicArray(layout: layout) + let trackedAddition = addition.map { tracker.instance(for: $0) } + a.replaceSubrange(range, copying: trackedAddition) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + + func test_replaceSubrange_copying_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: [0, 1, 10, 100]) { c in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.dynamicArray(layout: layout) + let trackedAddition = RigidArray( + copying: addition.map { tracker.instance(for: $0) }) + a.replaceSubrange(range, copying: trackedAddition.span) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + + func test_replaceSubrange_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: [0, 1, 10, 100]) { c in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.dynamicArray(layout: layout) + let trackedAddition = StaccatoContainer( + contents: RigidArray( + copying: addition.map { tracker.instance(for: $0) }), + spanCounts: [spanCount]) + a.replaceSubrange(range, copying: trackedAddition) + + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + } +} diff --git a/Tests/FutureTests/ArrayTests/RigidArrayTests.swift b/Tests/FutureTests/ArrayTests/RigidArrayTests.swift new file mode 100644 index 000000000..056d5ddf2 --- /dev/null +++ b/Tests/FutureTests/ArrayTests/RigidArrayTests.swift @@ -0,0 +1,714 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import _CollectionsTestSupport +import Future + +@available(SwiftStdlib 6.2, *) +class RigidArrayTests: CollectionTestCase { + func test_validate_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let items = tracker.rigidArray(layout: layout) + let expected = (0 ..< layout.count).map { tracker.instance(for: $0) } + expectEqual(tracker.instances, 2 * layout.count) + checkContainer(items, expectedContents: expected) + } + } + } + + func test_init_capacity() { + do { + let a = RigidArray(capacity: 0) + expectEqual(a.capacity, 0) + expectEqual(a.count, 0) + expectEqual(a.freeCapacity, 0) + expectTrue(a.isEmpty) + expectTrue(a.isFull) + } + + do { + let a = RigidArray(capacity: 10) + expectEqual(a.capacity, 10) + expectEqual(a.count, 0) + expectEqual(a.freeCapacity, 10) + expectTrue(a.isEmpty) + expectFalse(a.isFull) + } + } + + func test_init_generator() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + expectEqual(a.capacity, layout.capacity) + expectEqual(a.count, layout.count) + expectEqual(a.freeCapacity, layout.capacity - layout.count) + expectEqual(a.isEmpty, layout.count == 0) + expectEqual(a.isFull, layout.count == layout.capacity) + expectContainerContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + + func test_init_repeating() { + withEvery("c", in: [0, 10, 100]) { c in + withLifetimeTracking { tracker in + let value = tracker.instance(for: 0) + let a = RigidArray(repeating: value, count: c) + expectEqual(a.capacity, c) + expectEqual(a.count, c) + expectEqual(a.freeCapacity, 0) + expectEqual(a.isEmpty, c == 0) + expectTrue(a.isFull) + for i in 0 ..< c { + expectIdentical(a[i], value) + } + } + } + } + + func test_init_copying_Collection() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = RigidArray( + capacity: layout.capacity, + copying: (0 ..< layout.count).map { tracker.instance(for: $0) }) + expectEqual(tracker.instances, layout.count) + expectEqual(a.capacity, layout.capacity) + expectEqual(a.count, layout.count) + expectEqual(a.freeCapacity, layout.capacity - layout.count) + expectEqual(a.isEmpty, layout.count == 0) + expectEqual(a.isFull, layout.count == layout.capacity) + for i in 0 ..< layout.count { + expectEqual(a[i].payload, i) + } + } + } + } + + func test_init_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("spanCounts", in: [ + [1], + [3, 5, 7], + [10, 3], + ] as [[Int]]) { spanCounts in + withLifetimeTracking { tracker in + let additions = StaccatoContainer( + contents: RigidArray( + copying: (0 ..< layout.count).map { tracker.instance(for: $0) }), + spanCounts: spanCounts) + + let array = RigidArray(capacity: layout.capacity, copying: additions) + expectEqual(tracker.instances, layout.count) + expectEqual(array.capacity, layout.capacity) + expectEqual(array.count, layout.count) + for i in 0 ..< layout.count { + expectEqual(array[i].payload, i) + } + } + } + } + } + + func test_span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + let span = a.span + expectEqual(span.count, layout.count) + for i in 0 ..< span.count { + expectEqual(span[i].payload, i) + } + } + } + } + + func test_mutableSpan() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + var span = a.mutableSpan + expectEqual(span.count, layout.count) + for i in 0 ..< layout.count { + expectEqual(span[i].payload, i) + span[i] = tracker.instance(for: -i) + } + for i in 0 ..< layout.count { + expectEqual(span[i].payload, -i) + } + for i in 0 ..< layout.count { + expectEqual(a[i].payload, -i) + } + } + } + } + + func test_nextSpan() { + // RigidArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + let whole = a.span + var i = 0 + let first = a.nextSpan(after: &i) + expectEqual(i, layout.count) + expectTrue(first.isIdentical(to: whole)) + let second = a.nextSpan(after: &i) + expectEqual(i, layout.count) + expectTrue(second.isEmpty) + } + } + } + + func test_previousSpan() { + // RigidArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + let whole = a.span + var i = layout.count + let first = a.previousSpan(before: &i) + expectEqual(i, 0) + expectTrue(first.isIdentical(to: whole)) + let second = a.previousSpan(before: &i) + expectEqual(i, 0) + expectTrue(second.isEmpty) + } + } + } + + func test_nextMutableSpan() { + // RigidArray is expected to have exactly one span. + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + var i = 0 + var span = a.nextMutableSpan(after: &i) + expectEqual(i, layout.count) + expectEqual(span.count, layout.count) + span = a.nextMutableSpan(after: &i) + expectEqual(i, layout.count) + expectTrue(span.isEmpty) + } + } + } + + func test_index_properties() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + let a = RigidArray(layout: layout, using: { $0 }) + expectEqual(a.startIndex, 0) + expectEqual(a.endIndex, layout.count) + expectEqual(a.indices, 0 ..< layout.count) + } + } + + func test_swapAt() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + withEvery("i", in: 0 ..< layout.count / 2) { i in + a.swapAt(i, layout.count - 1 - i) + } + let expected = (0 ..< layout.count).reversed() + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count) + } + } + } + + func test_borrowElement() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + for i in 0 ..< layout.count { + let item = a.borrowElement(at: i) + expectEqual(item[].payload, i) + } + } + } + } + + func test_mutateElement() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + for i in 0 ..< layout.count { + var item = a.mutateElement(at: i) + expectEqual(item[].payload, i) + item[] = tracker.instance(for: -i) + expectEqual(tracker.instances, layout.count) + } + } + } + } + + func test_withUnsafeMutableBufferPointer() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + unsafe a.withUnsafeMutableBufferPointer { buffer, count in + expectEqual(buffer.count, layout.capacity) + expectEqual(count, layout.count) + unsafe buffer.extracting(0 ..< count).deinitialize() + expectEqual(tracker.instances, 0) + count = 0 + } + expectEqual(a.count, 0) + + unsafe a.withUnsafeMutableBufferPointer { buffer, count in + expectEqual(buffer.count, layout.capacity) + expectEqual(count, 0) + for i in 0 ..< buffer.count { + unsafe buffer.initializeElement(at: i, to: tracker.instance(for: -i)) + count += 1 + } + expectEqual(tracker.instances, layout.capacity) + } + expectEqual(a.count, layout.capacity) + + struct TestError: Error {} + + expectThrows({ + unsafe try a.withUnsafeMutableBufferPointer { buffer, count in + expectEqual(tracker.instances, layout.capacity) + while count > 0 { + if count == layout.count { break } + unsafe buffer.deinitializeElement(at: count - 1) + count -= 1 + } + throw TestError() + } + }) { error in + expectTrue(error is TestError) + } + expectContainerContents( + a, + equivalentTo: (0 ..< layout.count).map { -$0 }, + by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count) + } + } + } + + func test_reallocate() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery( + "newCapacity", + in: [layout.capacity, layout.count, layout.count + 1, layout.capacity + 1] as Set + ) { newCapacity in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, layout.capacity) + a.reallocate(capacity: newCapacity) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, newCapacity) + expectEqual(tracker.instances, layout.count) + expectContainerContents( + a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + } + + func test_reserveCapacity() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery( + "newCapacity", + in: [ + 0, layout.count - 1, layout.count, layout.count + 1, + layout.capacity, layout.capacity + 1 + ] as Set + ) { newCapacity in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, layout.capacity) + a.reserveCapacity(newCapacity) + expectEqual(a.count, layout.count) + expectEqual(a.capacity, Swift.max(layout.capacity, newCapacity)) + expectEqual(tracker.instances, layout.count) + expectContainerContents( + a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + } + + func test_copy() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + let b = a.copy() + expectEqual(b.count, layout.count) + expectEqual(b.capacity, layout.capacity) + expectEqual(tracker.instances, layout.count) + expectContainerContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + expectContainerContents(b, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + + func test_copy_capacity() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery( + "newCapacity", + in: [layout.count, layout.count + 1, layout.capacity, layout.capacity + 1] as Set + ) { newCapacity in + withLifetimeTracking { tracker in + let a = tracker.rigidArray(layout: layout) + let b = a.copy(capacity: newCapacity) + expectEqual(b.count, layout.count) + expectEqual(b.capacity, newCapacity) + expectEqual(tracker.instances, layout.count) + expectContainerContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + expectContainerContents(b, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeAll() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + a.removeAll() + expectTrue(a.isEmpty) + expectEqual(a.capacity, layout.capacity) + expectEqual(tracker.instances, 0) + } + } + } + + func test_removeLast() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + withEvery("i", in: 0 ..< layout.count) { i in + let old = a.removeLast() + expectEqual(old.payload, layout.count - 1 - i) + expectEqual(a.count, layout.count - 1 - i) + expectEqual(a.capacity, layout.capacity) + } + expectEqual(tracker.instances, 0) + } + } + } + + func test_removeLast_k() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("k", in: 0 ..< layout.count) { k in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeLast(k) + + var a = tracker.rigidArray(layout: layout) + expectEqual(tracker.instances, layout.count) + a.removeLast(k) + expectEqual(tracker.instances, layout.count - k) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_remove_at() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ..< layout.count) { i in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.remove(at: i) + + var a = tracker.rigidArray(layout: layout) + let old = a.remove(at: i) + expectEqual(old.payload, i) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeSubrange() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeSubrange(range) + + var a = tracker.rigidArray(layout: layout) + a.removeSubrange(range) + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + } + + func test_removeAll_where() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.removeAll(where: { $0.isMultiple(of: 2) }) + + var a = tracker.rigidArray(layout: layout) + a.removeAll(where: { $0.payload.isMultiple(of: 2) }) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + + func test_popLast() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let expectedItem = expected.popLast() + + var a = tracker.rigidArray(layout: layout) + let item = a.popLast() + + expectEquivalent(item, expectedItem, by: { $0?.payload == $1 }) + expectContainerContents( + a, equivalentTo: expected, by: { $0.payload == $1 }) + } + } + } + + func test_append() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + for i in layout.count ..< layout.capacity { + a.append(tracker.instance(for: i)) + expectEqual(a.count, i + 1) + expectContainerContents(a, equivalentTo: 0 ..< i + 1, by: { $0.payload == $1 }) + } + expectTrue(a.isFull) + expectEqual(tracker.instances, layout.capacity) + } + } + } + + func test_append_copying_MinimalSequence() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("isContiguous", in: [false, true]) { isContiguous in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + a.append(copying: MinimalSequence( + elements: (layout.count ..< layout.capacity).map { tracker.instance(for: $0) }, + underestimatedCount: .half, + isContiguous: isContiguous)) + expectTrue(a.isFull) + expectEqual(tracker.instances, layout.capacity) + } + } + } + } + + func test_append_copying_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + let b = tracker.rigidArray( + layout: ArrayLayout( + capacity: layout.capacity - layout.count, + count: layout.capacity - layout.count), + using: { layout.count + $0 }) + a.append(copying: b.span) + expectTrue(a.isFull) + expectContainerContents(a, equivalentTo: 0 ..< layout.capacity, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } + } + } + + func test_append_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + var a = tracker.rigidArray(layout: layout) + + let addition = (layout.count ..< layout.capacity).map { tracker.instance(for: $0) } + let b = StaccatoContainer( + contents: RigidArray(copying: addition), + spanCounts: [spanCount]) + a.append(copying: b) + expectTrue(a.isFull) + expectContainerContents(a, equivalentTo: 0 ..< layout.capacity, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } + } + } + } + + func test_insert_at() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + guard layout.count < layout.capacity else { return } + withEvery("i", in: 0 ... layout.count) { i in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + expected.insert(-1, at: i) + + var a = tracker.rigidArray(layout: layout) + a.insert(tracker.instance(for: -1), at: i) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count + 1) + } + } + } + } + + func test_insert_copying_Collection() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withEvery("isContiguous", in: [false, true]) { isContiguous in + withLifetimeTracking { tracker in + let addition = (layout.count ..< layout.capacity) + + var expected = Array(0 ..< layout.count) + expected.insert(contentsOf: addition, at: i) + + let trackedAddition = MinimalCollection( + addition.map { tracker.instance(for: $0) }, + isContiguous: isContiguous) + var a = tracker.rigidArray(layout: layout) + a.insert(copying: trackedAddition, at: i) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } + } + } + } + } + + func test_insert_copying_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withLifetimeTracking { tracker in + let addition = Array(layout.count ..< layout.capacity) + + var expected = Array(0 ..< layout.count) + expected.insert(contentsOf: addition, at: i) + + var a = tracker.rigidArray(layout: layout) + let rigidAddition = RigidArray(count: addition.count) { + tracker.instance(for: addition[$0]) + } + a.insert(copying: rigidAddition.span, at: i) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } + } + } + } + + func test_insert_copying_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in + withEvery("i", in: 0 ... layout.count) { i in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + + var expected = Array(0 ..< layout.count) + let addition = Array(layout.count ..< layout.capacity) + expected.insert(contentsOf: addition, at: i) + + var a = tracker.rigidArray(layout: layout) + let rigidAddition = StaccatoContainer( + contents: RigidArray(count: addition.count) { + tracker.instance(for: addition[$0]) + }, + spanCounts: [Swift.max(spanCount, 1)]) + a.insert(copying: rigidAddition, at: i) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.capacity) + } + } + } + } + } + + func test_replaceSubrange_Collection() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("isContiguous", in: [false, true]) { isContiguous in + withEvery("c", in: 0 ..< layout.capacity - layout.count + range.count) { c in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + let trackedAddition = MinimalCollection( + addition.map { tracker.instance(for: $0) }, + isContiguous: isContiguous) + var a = tracker.rigidArray(layout: layout) + a.replaceSubrange(range, copying: trackedAddition) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + } + func test_replaceSubrange_Span() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: 0 ..< layout.capacity - layout.count + range.count) { c in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.rigidArray(layout: layout) + let trackedAddition = RigidArray(copying: addition.map { tracker.instance(for: $0) }) + a.replaceSubrange(range, copying: trackedAddition.span) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + + func test_replaceSubrange_Container() { + withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in + withEveryRange("range", in: 0 ..< layout.count) { range in + withEvery("c", in: 0 ..< layout.capacity - layout.count + range.count) { c in + withEvery("spanCount", in: 1 ... Swift.max(1, layout.capacity - layout.count)) { spanCount in + withLifetimeTracking { tracker in + var expected = Array(0 ..< layout.count) + let addition = (0 ..< c).map { -100 - $0 } + expected.replaceSubrange(range, with: addition) + + var a = tracker.rigidArray(layout: layout) + let trackedAddition = StaccatoContainer( + contents: RigidArray(copying: addition.map { tracker.instance(for: $0) }), + spanCounts: [spanCount]) + a.replaceSubrange(range, copying: trackedAddition) + + expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectEqual(tracker.instances, layout.count - range.count + c) + } + } + } + } + } + } +} diff --git a/Tests/FutureTests/BorrowTests.swift b/Tests/FutureTests/BorrowTests.swift new file mode 100644 index 000000000..2b1414078 --- /dev/null +++ b/Tests/FutureTests/BorrowTests.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future +import Synchronization + +final class BorrowTests: XCTestCase { + @available(SwiftStdlib 6.2, *) + func test_basic() { + let x: Atomic? = Atomic(0) + + if let y = x.borrow() { + XCTAssertEqual(y[].load(ordering: .relaxed), 0) + } + } +} diff --git a/Tests/FutureTests/BoxTests.swift b/Tests/FutureTests/BoxTests.swift new file mode 100644 index 000000000..5230dafdc --- /dev/null +++ b/Tests/FutureTests/BoxTests.swift @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future + +final class BoxTests: XCTestCase { + func test_basic() { + var intOnHeap = Box(0) + + XCTAssertEqual(intOnHeap[], 0) + + intOnHeap[] = 123 + + XCTAssertEqual(intOnHeap[], 123) + + XCTAssertEqual(intOnHeap.copy(), 123) + + var inoutToIntOnHeap = intOnHeap.leak() + + XCTAssertEqual(inoutToIntOnHeap[], 123) + + inoutToIntOnHeap[] = 321 + + XCTAssertEqual(inoutToIntOnHeap[], 321) + } +} diff --git a/Tests/FutureTests/ContiguousStorageTests.swift b/Tests/FutureTests/ContiguousStorageTests.swift new file mode 100644 index 000000000..7d5174699 --- /dev/null +++ b/Tests/FutureTests/ContiguousStorageTests.swift @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future + +@available(SwiftStdlib 6.2, *) +final class ContiguousStorageTests: XCTestCase { + + @available(SwiftStdlib 6.2, *) + func testBorrowArrayStorage() throws { + + let capacity = 10 + var a: [Int] = [] + a = Array(0.. + + var startIndex: Int { 0 } + var endIndex: Int { span.count } + func index(after i: Int) -> Int { i+2 } + + @lifetime(copy contiguous) + init(_ contiguous: borrowing Span) { + span = copy contiguous + } + + subscript(_ p: Int) -> Int { span[p] } + } + + @available(SwiftStdlib 6.2, *) + @inline(never) + @lifetime(borrow array) + private func skip( + along array: borrowing Array + ) -> Skipper { + Skipper(array.span) + } + + @available(SwiftStdlib 6.2, *) + func testSpanWrapper() { + let capacity = 8 + let a = Array(0...stride + + let s0 = span.unsafeLoad(as: String.self) + XCTAssertEqual(s0.contains("0"), true) + let s1 = span.unsafeLoad(fromByteOffset: stride, as: String.self) + XCTAssertEqual(s1.contains("1"), true) + let s2 = span.unsafeLoad(fromUncheckedByteOffset: 2*stride, as: String.self) + XCTAssertEqual(s2.contains("2"), true) + } + } + + func testLoadUnaligned() { + let capacity = 64 + var a = Array(0...stride/2, as: UInt.self + ) + } + XCTAssertEqual(a[0].littleEndian & 0xffff, 0xff) + XCTAssertEqual(a[0].bigEndian & 0xffff, 0xffff) + } + + public func testUpdateFromSequence() { + let capacity = 8 + var a = Array(repeating: Int.max, count: capacity) + XCTAssertEqual(a.allSatisfy({ $0 == .max }), true) + a.withUnsafeMutableBufferPointer { + let empty = UnsafeMutableBufferPointer(start: nil, count: 0) + var span = MutableRawSpan(_unsafeElements: empty) + var (iterator, updated) = span.update(from: 0..<0) + XCTAssertNil(iterator.next()) + XCTAssertEqual(updated, 0) + + span = MutableRawSpan(_unsafeElements: $0) + (iterator, updated) = span.update(from: 0..<0) + XCTAssertNil(iterator.next()) + XCTAssertEqual(updated, 0) + + (iterator, updated) = span.update(from: 0..<10000) + XCTAssertNotNil(iterator.next()) + XCTAssertEqual(updated, capacity*MemoryLayout.stride) + } + XCTAssertEqual(a.elementsEqual(0..()) + XCTAssertEqual(a.allSatisfy({ $0 == .max }), true) + a.withUnsafeMutableBytes { + let emptyPrefix = $0.prefix(0) + var span = MutableRawSpan(_unsafeBytes: emptyPrefix) + var updated = span.update(fromContentsOf: e) + XCTAssertEqual(updated, 0) + + + updated = span.update(fromContentsOf: AnyCollection(e)) + XCTAssertEqual(updated, 0) + + span = MutableRawSpan(_unsafeBytes: $0) + updated = span.update(fromContentsOf: 0...stride) + } + XCTAssertEqual(a.elementsEqual(0...stride) + } + XCTAssertEqual(a.elementsEqual(0...stride) + } + } + XCTAssertEqual(a.allSatisfy({ $0 == Int.min }), true) + + a.withUnsafeMutableBytes { + var span = MutableRawSpan(_unsafeBytes: $0) + let array = Array(0...stride) + } + } + XCTAssertEqual(a.elementsEqual(0..) { + s[0] += 100 + } + + public func testSliceAsExtractingFunction() { + let count = 8 + let b = UnsafeMutableBufferPointer.allocate(capacity: count) + _ = b.initialize(fromContentsOf: 0...allocate(capacity: count) + _ = b.initialize(fromContentsOf: 0..) { + s[s.offset] += 100 + } + + public func testSliceAsWrapperType() { + let count = 8 + let b = UnsafeMutableBufferPointer.allocate(capacity: count) + _ = b.initialize(fromContentsOf: 0...allocate(capacity: count) + _ = b.initialize(fromContentsOf: 0..(_unsafeStart: rp, byteCount: bc) + XCTAssertEqual(b.count, capacity) + + let stride = MemoryLayout.stride + let r = MutableSpan(_unsafeBytes: $0.dropFirst(stride)) + XCTAssertEqual(r.count, (capacity-1)*stride) + XCTAssertEqual(r.count, bc-stride) + } + + let v = UnsafeMutableRawBufferPointer(start: nil, count: 0) + let m = MutableSpan(_unsafeBytes: v) + XCTAssertEqual(m.count, 0) + } + + func testIsEmpty() { + var array = [0, 1, 2] + array.withUnsafeMutableBufferPointer { + let span = MutableSpan(_unsafeElements: $0) + let e = span.isEmpty + XCTAssertFalse(e) + } + + array = [] + array.withUnsafeMutableBufferPointer { + let span = MutableSpan(_unsafeElements: $0) + let e = span.isEmpty + XCTAssertTrue(e) + } + } + + func testSpanFromMutableSpan() { + var array = [0, 1, 2] + array.withUnsafeMutableBufferPointer { + let mutable = MutableSpan(_unsafeElements: $0) + let immutable = Span(_unsafeMutableSpan: mutable) + XCTAssertEqual(mutable.count, immutable.count) + } + } + + func testRawSpanFromMutableSpan() { + let count = 4 + var array = Array(0...stride) + } + } + + func testSpanIndices() { + let capacity = 4 + var a = Array(0..(unsafeUninitializedCapacity: capacity) { + for i in $0.indices { + $0.initializeElement(at: i, to: .random(in: 0..<10)) + } + $1 = $0.count + } + a.withUnsafeMutableBufferPointer { + var v1 = MutableSpan(_unsafeElements: $0) + + XCTAssertEqual(v1._elementsEqual(Span(_unsafeMutableSpan: v1)._extracting(first: 1)), false) + XCTAssertEqual(Span(_unsafeMutableSpan: v1)._extracting(first: 0)._elementsEqual(Span(_unsafeMutableSpan: v1)._extracting(last: 0)), true) + XCTAssertEqual(v1._elementsEqual(v1), true) + XCTAssertEqual(Span(_unsafeMutableSpan: v1)._extracting(first: 3)._elementsEqual(Span(_unsafeMutableSpan: v1)._extracting(last: 3)), false) + + v1[0] = 0 + + let s = v1.span + var b = s.withUnsafeBufferPointer { Array($0) } + b.withUnsafeMutableBufferPointer { + let v2 = MutableSpan(_unsafeElements: $0) + let equal = v1._elementsEqual(v2) + XCTAssertEqual(equal, true) + } + } + } + + func testElementsEqualCollection() { + let capacity = 4 + var a = Array(0..(start: nil, count: 0) + var span = MutableSpan(_unsafeElements: empty) + var (iterator, updated) = span.update(from: 0..<0) + XCTAssertNil(iterator.next()) + XCTAssertEqual(updated, 0) + + span = MutableSpan(_unsafeElements: $0) + (iterator, updated) = span.update(from: 0..<0) + XCTAssertNil(iterator.next()) + XCTAssertEqual(updated, 0) + + (iterator, updated) = span.update(from: 0..<10000) + XCTAssertNotNil(iterator.next()) + XCTAssertEqual(updated, capacity) + } + XCTAssertEqual(a.elementsEqual(0...allocate(capacity: capacity) + + a.withUnsafeMutableBufferPointer { + var span = MutableSpan(_unsafeElements: $0) + + var o = OutputSpan(_initializing: b) + o.append(fromContentsOf: (0...allocate(capacity: count) + _ = b.initialize(fromContentsOf: 0..(start: nil, count: 0) + defer { _ = e } + + var m = MutableSpan(_unsafeElements: b) + m[0] = 100 + XCTAssertEqual(m.count, count) + XCTAssertEqual(m[0], 100) + + var s = m.span + XCTAssertEqual(s.count, m.count) + XCTAssertEqual(s[0], m[0]) + + // we're done using `s` before it gets reassigned + m.update(repeating: 7) + + s = m.span + +// m[0] = -1 // exclusivity violation + + XCTAssertEqual(s.count, m.count) + XCTAssertEqual(s[0], m[0]) + } + + public func testSwapAt() { + let count = 8 + var array = Array(0.. + var span = MutableSpan(_unsafeElements: $0) + XCTAssertEqual(span.count, capacity) + + prefix = span._extracting(first: 1) + XCTAssertEqual(prefix[0], 0) + + prefix = span._extracting(first: capacity) + XCTAssertEqual(prefix[capacity-1], UInt8(capacity-1)) + + prefix = span._extracting(droppingLast: capacity) + XCTAssertEqual(prefix.isEmpty, true) + + prefix = span._extracting(droppingLast: 1) + XCTAssertEqual(prefix[capacity-2], UInt8(capacity-2)) + } + + do { + let b = UnsafeMutableBufferPointer(start: nil, count: 0) + var span = MutableSpan(_unsafeElements: b) + XCTAssertEqual(span.count, b.count) + XCTAssertEqual(span._extracting(first: 1).count, b.count) + XCTAssertEqual(span._extracting(droppingLast: 1).count, b.count) + } + } + + func testSuffix() { + let capacity = 4 + var a = Array(0.. + var span = MutableSpan(_unsafeElements: $0) + XCTAssertEqual(span.count, capacity) + + suffix = span._extracting(last: capacity) + XCTAssertEqual(suffix[0], 0) + + suffix = span._extracting(last: capacity-1) + XCTAssertEqual(suffix[0], 1) + + suffix = span._extracting(last: 1) + XCTAssertEqual(suffix[0], UInt8(capacity-1)) + + suffix = span._extracting(droppingFirst: capacity) + XCTAssertTrue(suffix.isEmpty) + + suffix = span._extracting(droppingFirst: 1) + XCTAssertEqual(suffix[0], 1) + } + + do { + let b = UnsafeMutableBufferPointer(start: nil, count: 0) + var span = MutableSpan(_unsafeElements: b) + XCTAssertEqual(span.count, b.count) + XCTAssertEqual(span._extracting(last: 1).count, b.count) + XCTAssertEqual(span._extracting(droppingFirst: 1).count, b.count) + } + } +} +#endif diff --git a/Tests/FutureTests/SpanTests/OutputSpanTests.swift b/Tests/FutureTests/SpanTests/OutputSpanTests.swift new file mode 100644 index 000000000..3ac9142ee --- /dev/null +++ b/Tests/FutureTests/SpanTests/OutputSpanTests.swift @@ -0,0 +1,268 @@ +//===--- OutputSpanTests.swift --------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future + +#if false +@available(macOS 9999, *) +struct Allocation: ~Copyable { + let allocation: UnsafeMutablePointer + let capacity: Int + var count: Int? = nil + + init(of count: Int = 1, _ t: T.Type) { + precondition(count >= 0) + capacity = count + allocation = UnsafeMutablePointer.allocate(capacity: capacity) + } + + var isEmpty: Bool { (count ?? 0) == 0 } + + mutating func initialize( + _ body: (/* mutating */ inout OutputSpan) throws(E) -> Void + ) throws(E) { + if count != nil { fatalError() } + var outputBuffer = OutputSpan( + _initializing: allocation, capacity: capacity + ) + do { + try body(&outputBuffer) + let initialized = outputBuffer.relinquishBorrowedMemory() + assert(initialized.baseAddress == allocation) + count = initialized.count + } + catch { + outputBuffer.removeAll() + let empty = outputBuffer.relinquishBorrowedMemory() + assert(empty.baseAddress == allocation) + assert(empty.count == 0) + throw error + } + } + + borrowing func withSpan( + _ body: (borrowing Span) throws(E) -> R + ) throws(E) -> R { + try body(Span(_unsafeStart: allocation, count: count ?? 0)) + } + + deinit { + if let count { + allocation.deinitialize(count: count) + } + allocation.deallocate() + } +} + +enum MyTestError: Error { case error } + +@available(macOS 9999, *) +final class OutputSpanTests: XCTestCase { + + func testOutputBufferCreation() { + let c = 48 + let allocation = UnsafeMutablePointer.allocate(capacity: c) + defer { allocation.deallocate() } + + let ob = OutputSpan(_initializing: allocation, capacity: c) + let initialized = ob.relinquishBorrowedMemory() + XCTAssertNotNil(initialized.baseAddress) + XCTAssertEqual(initialized.count, 0) + } + + func testOutputBufferDeinitWithoutRelinquishingMemory() { + let c = 48 + let allocation = UnsafeMutableBufferPointer.allocate(capacity: c) + defer { allocation.deallocate() } + + var ob = OutputSpan(_initializing: Slice(base: allocation, bounds: 0..(_initializing: allocation[8...]) + let r = Int16.max>>2 + ob.append(r) + _ = ob.relinquishBorrowedBytes() + + let o = allocation.load(fromByteOffset: 8, as: Int16.self) + XCTAssertEqual(o, r) + } + + func testInitializeBufferByAppendingRepeatedElements() { + var a = Allocation(of: 48, Int.self) + let c = 10 + a.initialize { + $0.append(repeating: c, count: c) + let oops = $0.removeLast() + XCTAssertEqual(oops, c) + XCTAssertEqual($0.count, c-1) + } + a.withSpan { span in + XCTAssertEqual(span.count, c-1) + XCTAssert(span._elementsEqual(Array(repeating: c, count: c-1))) + } + } + + func testInitializeBufferFromSequence() { + var a = Allocation(of: 48, Int.self) + a.initialize { + var it = $0.append(from: 0..<18) + XCTAssertNil(it.next()) + } + a.withSpan { span in + XCTAssertEqual(span.count, 18) + XCTAssert(span._elementsEqual(0..<18)) + } + } + + func testInitializeBufferFromCollectionNotContiguous() { + var a = Allocation(of: 48, Int.self) + let c = 24 + a.initialize { + $0.append(fromContentsOf: 0...allocate(capacity: c) + for i in 0.. 0) + throw MyTestError.error + } + } + catch MyTestError.error { + XCTAssertEqual(a.isEmpty, true) + } + } + + func testMutateOutputSpan() throws { + let b = UnsafeMutableBufferPointer.allocate(capacity: 10) + defer { b.deallocate() } + + var span = OutputSpan(_initializing: b) + XCTAssertEqual(span.count, 0) + span.append(fromContentsOf: 1...9) + XCTAssertEqual(span.count, 9) + + var mutable = span.mutableSpan +// span.append(20) // exclusivity violation + for i in 0...stride) + XCTAssertFalse(span.isEmpty) + } + } + + func testInitWithEmptySpanOfIntegers() { + let a: [Int] = [] + a.withUnsafeBufferPointer { + let intSpan = Span(_unsafeElements: $0) + let span = RawSpan(_elements: intSpan) + XCTAssertTrue(span.isEmpty) + } + } + + func testInitWithRawBytes() { + let capacity = 4 + var a = Array(0...stride) + } + + a.withUnsafeMutableBytes { + let span = RawSpan(_unsafeBytes: $0) + XCTAssertEqual(span.byteCount, capacity*MemoryLayout.stride) + } + } + + func testWithRawPointer() { + let capacity = 4 + var a = Array(0...stride + ) + XCTAssertEqual(span.byteCount, $0.count) + } + + a.withUnsafeMutableBytes { + let pointer = $0.baseAddress! + let span = RawSpan( + _unsafeStart: pointer, + byteCount: capacity*MemoryLayout.stride + ) + XCTAssertEqual(span.byteCount, $0.count) + } + } + + func testLoad() { + let capacity = 4 + let s = (0...stride + + let s0 = span.unsafeLoad(as: String.self) + XCTAssertEqual(s0.contains("0"), true) + let s1 = span.unsafeLoad(fromByteOffset: stride, as: String.self) + XCTAssertEqual(s1.contains("1"), true) + let s2 = span.unsafeLoad(fromUncheckedByteOffset: 2*stride, as: String.self) + XCTAssertEqual(s2.contains("2"), true) + } + } + + func testLoadUnaligned() { + let capacity = 64 + let a = Array(0..? + bounds = span.byteOffsets(of: subSpan1) + XCTAssertEqual(bounds, span.byteOffsets.prefix(6)) + bounds = span.byteOffsets(of: subSpan2) + XCTAssertEqual(bounds, span.byteOffsets.suffix(6)) + bounds = subSpan2.byteOffsets(of: subSpan1) + XCTAssertNil(bounds) + bounds = subSpan1.byteOffsets(of: subSpan2) + XCTAssertNil(bounds) + bounds = subSpan2.byteOffsets(of: span) + XCTAssertNil(bounds) + bounds = nilSpan.byteOffsets(of: emptySpan) + XCTAssertNil(bounds) + bounds = span.byteOffsets(of: nilSpan) + XCTAssertNil(bounds) + bounds = nilSpan.byteOffsets(of: nilSpan) + XCTAssertEqual(bounds, 0..<0) + } +} diff --git a/Tests/FutureTests/SpanTests/SpanTests.swift b/Tests/FutureTests/SpanTests/SpanTests.swift new file mode 100644 index 000000000..00b333ff0 --- /dev/null +++ b/Tests/FutureTests/SpanTests/SpanTests.swift @@ -0,0 +1,415 @@ +//===--- SpanTests.swift --------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future + +@available(macOS 9999, *) +final class SpanTests: XCTestCase { + + func testInitWithOrdinaryElement() { + let capacity = 4 + var s = (0..(_unsafeBytes: $0) + XCTAssertEqual(b.count, capacity) + + let r = Span(_unsafeBytes: $0) + XCTAssertEqual(r.count, capacity*MemoryLayout.stride) + + let p = $0.baseAddress! + let span = Span( + _unsafeStart: p, byteCount: capacity*MemoryLayout.stride + ) + XCTAssertEqual(span.count, capacity) + } + + a.withUnsafeMutableBytes { + let b = Span(_unsafeBytes: $0) + XCTAssertEqual(b.count, capacity) + + let p = $0.baseAddress! + let span = Span( + _unsafeStart: p, byteCount: capacity*MemoryLayout.stride + ) + XCTAssertEqual(span.count, capacity) + } + } + + func testIsEmpty() { + let capacity = 4 + let a = Array(0...stride) + } + } + + func testSpanIndices() { + let capacity = 4 + let a = Array(0..(unsafeUninitializedCapacity: capacity) { + for i in $0.indices { + $0.initializeElement(at: i, to: .random(in: 0..<10)) + } + $1 = $0.count + } + a.withUnsafeBufferPointer { + let span = Span(_unsafeElements: $0) + + XCTAssertEqual(span._elementsEqual(span._extracting(first: 1)), false) + XCTAssertEqual(span._extracting(0..<0)._elementsEqual(span._extracting(last: 0)), true) + XCTAssertEqual(span._elementsEqual(span), true) + XCTAssertEqual(span._extracting(0..<3)._elementsEqual(span._extracting(last: 3)), false) + + let copy = span.withUnsafeBufferPointer(Array.init) + copy.withUnsafeBufferPointer { + let spanOfCopy = Span(_unsafeElements: $0) + XCTAssertTrue(span._elementsEqual(spanOfCopy)) + } + } + } + + func testElementsEqualCollection() { + let capacity = 4 + let a = Array(0..(start: nil, count: 0) + let span = Span(_unsafeElements: b) + XCTAssertEqual(span.count, b.count) + XCTAssertEqual(span._extracting(first: 1).count, b.count) + XCTAssertEqual(span._extracting(droppingLast: 1).count, b.count) + } + } + + func testSuffix() { + let capacity = 4 + let a = Array(0..(_unsafeElements: $0) + XCTAssertEqual(span.count, capacity) + XCTAssertEqual(span._extracting(last: capacity)[0], 0) + XCTAssertEqual(span._extracting(last: capacity-1)[0], 1) + XCTAssertEqual(span._extracting(last: 1)[0], capacity-1) + XCTAssertEqual(span._extracting(droppingFirst: capacity).count, 0) + XCTAssertEqual(span._extracting(droppingFirst: 1)[0], 1) + } + + do { + let b = UnsafeBufferPointer(start: nil, count: 0) + let span = Span(_unsafeElements: b) + XCTAssertEqual(span.count, b.count) + XCTAssertEqual(span._extracting(last: 1).count, b.count) + XCTAssertEqual(span._extracting(droppingFirst: 1).count, b.count) + } + } + + public func testWithUnsafeBuffer() { + let capacity: UInt8 = 64 + let a = Array(0...allocate(capacity: 8) + _ = b.initialize(fromContentsOf: 0..<8) + defer { b.deallocate() } + + let span = Span(_unsafeElements: b) + let pre = span._extracting(first: 6) + let suf = span._extracting(last: 6) + + XCTAssertFalse( + pre.isIdentical(to: suf) + ) + XCTAssertFalse( + pre.isIdentical(to: span) + ) + XCTAssertTrue( + pre._extracting(last: 4).isIdentical(to: suf._extracting(first: 4)) + ) + } + + func testIndicesOf() { + let b = UnsafeMutableBufferPointer.allocate(capacity: 8) + _ = b.initialize(fromContentsOf: 0..<8) + defer { b.deallocate() } + + let span = Span(_unsafeElements: b) + let subSpan1 = span._extracting(first: 6) + let subSpan2 = span._extracting(last: 6) + let emptySpan = span._extracting(first: 0) +/* This isn't relevant until we can support unaligned spans + let unalignedSpan = RawSpan(_unsafeSpan: span) + ._extracting(droppingFirst: 6) + ._extracting(droppingLast: 2) + .unsafeView(as: Int.self) +*/ + let nilBuffer = UnsafeBufferPointer(start: nil, count: 0) + let nilSpan = Span(_unsafeElements: nilBuffer) + + var bounds: Range? + bounds = span.indices(of: subSpan1) + XCTAssertEqual(bounds, span.indices.prefix(6)) + bounds = span.indices(of: subSpan2) + XCTAssertEqual(bounds, span.indices.suffix(6)) + bounds = subSpan2.indices(of: subSpan1) + XCTAssertNil(bounds) + bounds = subSpan1.indices(of: subSpan2) + XCTAssertNil(bounds) + bounds = subSpan2.indices(of: span) + XCTAssertNil(bounds) + bounds = nilSpan.indices(of: emptySpan) + XCTAssertNil(bounds) + bounds = span.indices(of: nilSpan) + XCTAssertNil(bounds) + bounds = nilSpan.indices(of: nilSpan) + XCTAssertEqual(bounds, 0..<0) + } + +// func testSpanIterator() { +// class C { +// let id: Int +// init(id: Int) { self.id = id } +// } +// +// let b = UnsafeMutableBufferPointer.allocate(capacity: 8) +// _ = b.initialize(fromContentsOf: (0..<8).map(C.init(id:))) +// defer { +// b.deinitialize() +// b.deallocate() +// } +// +// let span = Span(_unsafeElements: b) +// var iterator = Span.Iterator(from: span) +// var i = 0 +// while let c = iterator.next() { +// XCTAssertEqual(i, c.id) +// i += 1 +// } +// } + + func testTypeErasedSpanOfBitwiseCopyable() { + let b = UnsafeMutableRawBufferPointer.allocate(byteCount: 64, alignment: 8) + defer { b.deallocate() } + let initialized = b.initializeMemory(as: UInt8.self, fromContentsOf: 0..<64) + XCTAssertEqual(initialized.count, 64) + defer { initialized.deinitialize() } + + func test(_ span: Span) -> T { + span[0] + } + + // let span = Span(_unsafeBytes: b.dropFirst().dropLast(7)) + + let suffix = b.dropFirst(4) + let span = Span(_unsafeBytes: suffix) + let first = test(span) + XCTAssertEqual(first, 0x07060504) + } +} diff --git a/Tests/FutureTests/SpanTests/StdlibOutputSpanExtensionTests.swift b/Tests/FutureTests/SpanTests/StdlibOutputSpanExtensionTests.swift new file mode 100644 index 000000000..ed5707b12 --- /dev/null +++ b/Tests/FutureTests/SpanTests/StdlibOutputSpanExtensionTests.swift @@ -0,0 +1,56 @@ +//===--- StdlibOutputSpanExtensionTests.swift -----------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 - 2025 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Foundation +import Future + +#if false +@available(macOS 9999, *) +final class StdlibOutputSpanExtensionTests: XCTestCase { + + func testArrayInitializationExample() { + var array: [UInt8] + array = Array(capacity: 32, initializingWith: { output in + for i in 0..<(output.capacity/2) { + output.append(UInt8(clamping: i)) + } + }) + XCTAssertEqual(array.count, 16) + XCTAssert(array.elementsEqual(0..<16)) + XCTAssertGreaterThanOrEqual(array.capacity, 32) + } + + func testDataInitializationExample() { + var data: Data + data = Data(capacity: 32, initializingWith: { output in + for i in 0..<(output.capacity/2) { + output.append(UInt8(clamping: i)) + } + }) + XCTAssertEqual(data.count, 16) + XCTAssert(data.elementsEqual(0..<16)) + } + + func testStringInitializationExample() { + var string: String + let c = UInt8(ascii: "A") + string = String(utf8Capacity: 32, initializingWith: { output in + for i in 0..<(output.capacity/2) { + output.append(c + UInt8(clamping: i)) + } + }) + XCTAssertEqual(string.utf8.count, 16) + XCTAssert(string.utf8.elementsEqual(c..<(c+16))) + } +} +#endif diff --git a/Tests/HashTreeCollectionsTests/TreeDictionary Tests.swift b/Tests/HashTreeCollectionsTests/TreeDictionary Tests.swift index 247deb70a..28ec8ec5c 100644 --- a/Tests/HashTreeCollectionsTests/TreeDictionary Tests.swift +++ b/Tests/HashTreeCollectionsTests/TreeDictionary Tests.swift @@ -386,7 +386,7 @@ class TreeDictionaryTests: CollectionTestCase { let v5: MinimalEncoder.Value = .dictionary([ "This is not a number": .string("bus"), "42": .string("train")]) - expectThrows(try MinimalDecoder.decode(v5, as: PD.self)) { + expectThrows({ try MinimalDecoder.decode(v5, as: PD.self) }) { expectTrue($0 is DecodingError) } @@ -394,10 +394,10 @@ class TreeDictionaryTests: CollectionTestCase { let v6: MinimalEncoder.Value = .dictionary([ "This is not a number": .string("bus"), "42": .string("train")]) - expectThrows( + expectThrows({ try MinimalDecoder.decode( v6, as: PD.self) - ) { + }) { expectTrue($0 is DecodingError) } @@ -416,7 +416,7 @@ class TreeDictionaryTests: CollectionTestCase { let v8: MinimalEncoder.Value = .array([ .int32(1), .string("bike"), .int32(2), ]) - expectThrows(try MinimalDecoder.decode(v8, as: PD.self)) + expectThrows({ try MinimalDecoder.decode(v8, as: PD.self) }) } func test_CustomReflectable() { diff --git a/Tests/HashTreeCollectionsTests/TreeSet Tests.swift b/Tests/HashTreeCollectionsTests/TreeSet Tests.swift index ce018b6e1..fb2274232 100644 --- a/Tests/HashTreeCollectionsTests/TreeSet Tests.swift +++ b/Tests/HashTreeCollectionsTests/TreeSet Tests.swift @@ -752,9 +752,9 @@ class TreeSetTests: CollectionTestCase { expectEqual(try MinimalDecoder.decode(v4, as: TreeSet.self), s4) let v5: MinimalEncoder.Value = .array([.int(0), .int(1), .int(0)]) - expectThrows( + expectThrows({ try MinimalDecoder.decode(v5, as: TreeSet.self) - ) { error in + }) { error in expectNotNil(error as? DecodingError) { error in guard case .dataCorrupted(let context) = error else { expectFailure("Unexpected error \(error)") @@ -766,9 +766,9 @@ class TreeSetTests: CollectionTestCase { } let v6: MinimalEncoder.Value = .array([.int16(42)]) - expectThrows( + expectThrows({ try MinimalDecoder.decode(v6, as: TreeSet.self) - ) { error in + }) { error in expectNotNil(error as? DecodingError) { error in guard case .typeMismatch(_, _) = error else { expectFailure("Unexpected error \(error)") diff --git a/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary Tests.swift b/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary Tests.swift index eacd60980..b6633174c 100644 --- a/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary Tests.swift +++ b/Tests/OrderedCollectionsTests/OrderedDictionary/OrderedDictionary Tests.swift @@ -952,7 +952,7 @@ class OrderedDictionaryTests: CollectionTestCase { expectEqual(try MinimalDecoder.decode(v4, as: OD.self), d4) let v5: MinimalEncoder.Value = .array([.int(0), .int(1), .int(2)]) - expectThrows(try MinimalDecoder.decode(v5, as: OD.self)) { error in + expectThrows({ try MinimalDecoder.decode(v5, as: OD.self) }) { error in guard case DecodingError.dataCorrupted(let context) = error else { expectFailure("Unexpected error \(error)") return @@ -963,7 +963,7 @@ class OrderedDictionaryTests: CollectionTestCase { } let v6: MinimalEncoder.Value = .array([.int(0), .int(1), .int(0), .int(2)]) - expectThrows(try MinimalDecoder.decode(v6, as: OD.self)) { error in + expectThrows({ try MinimalDecoder.decode(v6, as: OD.self) }) { error in guard case DecodingError.dataCorrupted(let context) = error else { expectFailure("Unexpected error \(error)") return diff --git a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSet Diffing Tests.swift b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSet Diffing Tests.swift index b5ea03f20..9d012a8f0 100644 --- a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSet Diffing Tests.swift +++ b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSet Diffing Tests.swift @@ -18,13 +18,13 @@ import _CollectionsTestSupport #endif class MeasuringHashable: Hashable { - static var equalityChecks = 0 + nonisolated(unsafe) static var equalityChecks = 0 static func == (lhs: MeasuringHashable, rhs: MeasuringHashable) -> Bool { MeasuringHashable.equalityChecks += 1 return lhs._inner == rhs._inner } - static var hashChecks = 0 + nonisolated(unsafe) static var hashChecks = 0 func hash(into hasher: inout Hasher) { MeasuringHashable.hashChecks += 1 _inner.hash(into: &hasher) diff --git a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift index 53e5cd77d..bd122a1ee 100644 --- a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift +++ b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift @@ -277,10 +277,10 @@ class OrderedSetTests: CollectionTestCase { let v3: MinimalEncoder.Value = .array((0 ..< 100).map { .int($0) }) expectEqual(try MinimalDecoder.decode(v3, as: OrderedSet.self), s3) - expectThrows(try MinimalDecoder.decode(.int(0), as: OrderedSet.self)) + expectThrows({ try MinimalDecoder.decode(.int(0), as: OrderedSet.self) }) let v4: MinimalEncoder.Value = .array([.int(0), .int(1), .int(0)]) - expectThrows(try MinimalDecoder.decode(v4, as: OrderedSet.self)) { error in + expectThrows({ try MinimalDecoder.decode(v4, as: OrderedSet.self) }) { error in expectNotNil(error as? DecodingError) { error in guard case .dataCorrupted(let context) = error else { expectFailure("Unexpected error \(error)") diff --git a/Tests/SortedCollectionsTests/SortedSet/SortedSet Tests.swift b/Tests/SortedCollectionsTests/SortedSet/SortedSet Tests.swift index 378479e5f..a4a4fc3b7 100644 --- a/Tests/SortedCollectionsTests/SortedSet/SortedSet Tests.swift +++ b/Tests/SortedCollectionsTests/SortedSet/SortedSet Tests.swift @@ -185,10 +185,10 @@ class SortedSetTests: CollectionTestCase { let v3: MinimalEncoder.Value = .array((0 ..< 100).map { .int($0) }) expectEqual(try MinimalDecoder.decode(v3, as: SortedSet.self), s3) - expectThrows(try MinimalDecoder.decode(.int(0), as: SortedSet.self)) + expectThrows({ try MinimalDecoder.decode(.int(0), as: SortedSet.self) }) let v4: MinimalEncoder.Value = .array([.int(0), .int(1), .int(0)]) - expectThrows(try MinimalDecoder.decode(v4, as: SortedSet.self)) { error in + expectThrows({ try MinimalDecoder.decode(v4, as: SortedSet.self) }) { error in expectNotNil(error as? DecodingError) { error in guard case .dataCorrupted(let context) = error else { expectFailure("Unexpected error \(error)") diff --git a/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift new file mode 100644 index 000000000..81816702b --- /dev/null +++ b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift @@ -0,0 +1,131 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2025 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 +// +//===----------------------------------------------------------------------===// + +import Future + +@available(SwiftStdlib 6.2, *) +public func expectContainersWithEquivalentElements< + C1: Container & ~Copyable & ~Escapable, + C2: Container & ~Copyable & ~Escapable +>( + _ left: borrowing C1, + _ right: borrowing C2, + by areEquivalent: (borrowing C1.Element, borrowing C2.Element) -> Bool, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + if left.elementsEqual(right, by: areEquivalent) { return } + _expectFailure( + "Containers do not have equivalent elements", + message, trapping: trapping, file: file, line: line) +} + +/// Check if `left` and `right` contain equal elements in the same order. +@available(SwiftStdlib 6.2, *) +public func expectContainersWithEqualElements< + Element: Equatable, + C1: Container & ~Copyable & ~Escapable, + C2: Container & ~Copyable & ~Escapable, +>( + _ left: borrowing C1, + _ right: borrowing C2, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + if left.elementsEqual(right) { return } + _expectFailure( + "Containers do not have equal elements", + message, trapping: trapping, file: file, line: line) +} + +/// Check if `left` and `right` contain equal elements in the same order. +@available(SwiftStdlib 6.2, *) +public func expectContainerContents< + Element: Equatable, + C1: Container & ~Copyable & ~Escapable, + C2: Collection, +>( + _ left: borrowing C1, + equalTo right: C2, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + var i = left.startIndex + var it = right.makeIterator() + while i < left.endIndex { + let a = left[i] + guard let b = it.next() else { + _expectFailure( + "Container is longer than expected", + message, trapping: trapping, file: file, line: line) + return + } + guard a == b else { + _expectFailure( + "'\(a)' at index \(i) is not equal to '\(b)'", + message, trapping: trapping, file: file, line: line) + return + } + left.formIndex(after: &i) + } + guard it.next() == nil else { + _expectFailure( + "Container is shorter than expected", + message, trapping: trapping, file: file, line: line) + return + } +} + +/// Check if `left` and `right` contain equal elements in the same order. +@available(SwiftStdlib 6.2, *) +public func expectContainerContents< + C1: Container & ~Copyable & ~Escapable, + C2: Collection, +>( + _ left: borrowing C1, + equivalentTo right: C2, + by areEquivalent: (borrowing C1.Element, C2.Element) -> Bool, + _ message: @autoclosure () -> String = "", + trapping: Bool = false, + file: StaticString = #file, + line: UInt = #line +) { + var i = left.startIndex + var it = right.makeIterator() + while i < left.endIndex { + guard let b = it.next() else { + _expectFailure( + "Container is longer than expected", + message, trapping: trapping, file: file, line: line) + return + } + guard areEquivalent(left[i], b) else { + _expectFailure( + "Element at index \(i) is not equal to '\(b)'", + message, trapping: trapping, file: file, line: line) + return + } + left.formIndex(after: &i) + } + guard it.next() == nil else { + _expectFailure( + "Container is shorter than expected", + message, trapping: trapping, file: file, line: line) + return + } +} diff --git a/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift index 7224be07c..1a7955e06 100644 --- a/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift +++ b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions.swift @@ -15,7 +15,7 @@ import XCTest public func expectFailure( _ message: @autoclosure () -> String = "", trapping: Bool = false, - file: StaticString = #file, + file: StaticString = (#file), line: UInt = #line ) { let message = message() @@ -384,18 +384,18 @@ public func expectStrictlyMonotonicallyIncreasing( } } -public func expectThrows( - _ expression: @autoclosure () throws -> T, +public func expectThrows( + _ expression: () throws(E) -> T, _ message: @autoclosure () -> String = "", trapping: Bool = false, file: StaticString = #file, line: UInt = #line, - _ errorHandler: (Error) -> Void = { _ in } + handler errorHandler: (E) -> Void = { _ in } ) { do { let result = try expression() expectFailure("Expression did not throw" - + (T.self == Void.self ? "" : " (returned '\(result)' instead)"), + + (T.self == Void.self ? "" : " (returned '\(result)' instead)"), trapping: trapping, file: file, line: line) } catch { diff --git a/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift b/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift index ad3b89af4..d5eaef556 100644 --- a/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift +++ b/Tests/_CollectionsTestSupport/AssertionContexts/TestContext.swift @@ -20,7 +20,7 @@ public final class TestContext { internal var _trace: [Entry] = [] // FIXME: This ought to be a thread-local variable. - internal static var _current: TestContext? + nonisolated(unsafe) internal static var _current: TestContext? public init() {} } @@ -208,7 +208,7 @@ extension TestContext { @inline(never) public func debuggerBreak( _ message: String, - file: StaticString = #file, + file: StaticString = (#file), line: UInt = #line ) { XCTFail(message, file: file, line: line) diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckBidirectionalCollection.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckBidirectionalCollection.swift index d7c20c106..4a992a34b 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckBidirectionalCollection.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckBidirectionalCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 @@ -17,6 +17,7 @@ import XCTest extension BidirectionalCollection { + @inlinable func _indicesByIndexBefore() -> [Index] { var result: [Index] = [] var i = endIndex @@ -28,6 +29,7 @@ extension BidirectionalCollection { return result } + @inlinable func _indicesByFormIndexBefore() -> [Index] { var result: [Index] = [] var i = endIndex @@ -40,7 +42,11 @@ extension BidirectionalCollection { } } -public func checkBidirectionalCollection( +@inlinable +public func checkBidirectionalCollection< + C: BidirectionalCollection, + S: Sequence +>( _ collection: C, expectedContents: S, maxSamples: Int? = nil, @@ -56,6 +62,7 @@ public func checkBidirectionalCollection( _ collection: C, expectedContents: S, @@ -80,6 +87,7 @@ public func checkBidirectionalCollection( @@ -135,6 +143,7 @@ public func _checkBidirectionalCollection_indexOffsetBy< } } +@inlinable public func _checkBidirectionalCollection( _ collection: C, expectedContents: S, diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckCollection.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckCollection.swift index 80e95d012..6a2610b81 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckCollection.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 @@ -19,7 +19,8 @@ import XCTest // FIXME: Port the collection validation tests from the Swift compiler codebase. extension Sequence { - func _contentsByIterator() -> [Element] { + @inlinable + internal func _contentsByIterator() -> [Element] { var result: [Element] = [] var it = makeIterator() while let item = it.next() { @@ -28,7 +29,8 @@ extension Sequence { return result } - func _contentsByCopyContents(_ count: Int? = nil) -> [Element] { + @inlinable + internal func _contentsByCopyContents(_ count: Int? = nil) -> [Element] { var it: Iterator? var result = Array( unsafeUninitializedCapacity: count ?? self.underestimatedCount @@ -43,7 +45,8 @@ extension Sequence { } extension Collection { - func _indicesByIndexAfter() -> [Index] { + @inlinable + internal func _indicesByIndexAfter() -> [Index] { var result: [Index] = [] var i = startIndex while i != endIndex { @@ -53,7 +56,8 @@ extension Collection { return result } - func _indicesByFormIndexAfter() -> [Index] { + @inlinable + internal func _indicesByFormIndexAfter() -> [Index] { var result: [Index] = [] var i = startIndex while i != endIndex { @@ -64,6 +68,7 @@ extension Collection { } } +@inlinable public func checkCollection( _ collection: C, expectedContents: Expected, @@ -99,6 +104,7 @@ public func checkCollection( } } +@inlinable public func checkCollection( _ collection: C, expectedContents: Expected, @@ -120,6 +126,7 @@ public func checkCollection( file: file, line: line) } +@inlinable public func _checkCollection( _ collection: C, expectedContents: Expected, diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckComparable.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckComparable.swift index 8bde8a7de..b5486a39a 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckComparable.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckComparable.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 @@ -12,9 +12,11 @@ // Loosely adapted from https://github.com/apple/swift/tree/main/stdlib/private/StdlibUnittest +@frozen public enum ExpectedComparisonResult: Hashable { case lt, eq, gt + @inlinable public func flip() -> ExpectedComparisonResult { switch self { case .lt: @@ -26,6 +28,7 @@ public enum ExpectedComparisonResult: Hashable { } } + @inlinable public static func comparing(_ left: C, _ right: C) -> Self { left < right ? .lt : left > right ? .gt @@ -46,6 +49,7 @@ extension ExpectedComparisonResult: CustomStringConvertible { } } +@inlinable public func checkComparable( sortedEquivalenceClasses: [[Instance]], maxSamples: Int? = nil, @@ -68,6 +72,7 @@ public func checkComparable( /// Test that the elements of `instances` satisfy the semantic /// requirements of `Comparable`, using `oracle` to generate comparison /// expectations from pairs of positions in `instances`. +@inlinable public func checkComparable( _ instances: Instances, oracle: (Instances.Index, Instances.Index) -> ExpectedComparisonResult, @@ -86,6 +91,7 @@ public func checkComparable( file: file, line: line) } +@inlinable public func checkComparable( expected: ExpectedComparisonResult, _ lhs: T, _ rhs: T, @@ -99,6 +105,7 @@ public func checkComparable( /// Same as `checkComparable(_:oracle:file:line:)` but doesn't check /// `Equatable` conformance. Useful for preventing duplicate testing. +@inlinable public func _checkComparable( _ instances: Instances, oracle: (Instances.Index, Instances.Index) -> ExpectedComparisonResult, diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift new file mode 100644 index 000000000..335bd4fb9 --- /dev/null +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift @@ -0,0 +1,229 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +import XCTest +import Future + +@available(SwiftStdlib 6.2, *) +extension Container where Self: ~Copyable & ~Escapable { + @inlinable + internal func _indicesByIndexAfter() -> [Index] { + var result: [Index] = [] + var i = startIndex + while i != endIndex { + result.append(i) + i = index(after: i) + } + return result + } + + @inlinable + internal func _indicesByFormIndexAfter() -> [Index] { + var result: [Index] = [] + var i = startIndex + while i != endIndex { + result.append(i) + formIndex(after: &i) + } + return result + } +} + +@available(SwiftStdlib 6.2, *) +@inlinable +public func checkContainer< + C: Container & ~Copyable & ~Escapable, + Expected: Sequence +>( + _ container: borrowing C, + expectedContents: Expected, + file: StaticString = #file, + line: UInt = #line +) where C.Element: Equatable { + checkContainer( + container, + expectedContents: expectedContents, + by: ==, + file: file, line: line) +} + +@available(SwiftStdlib 6.2, *) +@inlinable +public func checkContainer< + C: Container & ~Copyable & ~Escapable, + Expected: Sequence +>( + _ container: borrowing C, + expectedContents: Expected, + by areEquivalent: (C.Element, C.Element) -> Bool, + file: StaticString = #file, + line: UInt = #line +) where C.Element: Equatable { + let entry = TestContext.current.push("checkContainer", file: file, line: line) + defer { TestContext.current.pop(entry) } + + let expectedContents = Array(expectedContents) + expectEqual(container.isEmpty, expectedContents.isEmpty) + let actualCount = container.count + expectEqual(actualCount, expectedContents.count) + + let validIndices = container._indicesByIndexAfter() + + expectEqual( + container._indicesByIndexAfter(), validIndices, + "Container does not have stable indices") + + // Check that `index(after:)` produces the same results as `formIndex(after:)` + do { + let indicesByFormIndexAfter = container._indicesByFormIndexAfter() + expectEqual(indicesByFormIndexAfter, validIndices) + } + + expectEqual(container.index(container.startIndex, offsetBy: container.count), container.endIndex) + + // Check contents using indexing. + let actualContents = validIndices.map { container[$0] } + expectEquivalentElements(actualContents, expectedContents, by: areEquivalent) + + let allIndices = validIndices + [container.endIndex] + do { + var last: C.Index? = nil + for index in allIndices { + if let last { + // The indices must be monotonically increasing. + expectGreaterThan( + index, last, + "Index \(index) is not greater than immediately preceding index \(last)") + + // Aligning a valid index must not change it. + let nearestDown = container.index(alignedDown: index) + expectEqual(nearestDown, index, "Aligning a valid index down must not change its position") + let nearestUp = container.index(alignedUp: index) + expectEqual(nearestUp, index, "Aligning a valid index up must not change its position") + } + last = index + } + } + + // Check the `Comparable` conformance of the Index type. + if C.Index.self != Int.self { + checkComparable(allIndices, oracle: { .comparing($0, $1) }) + } + + withEveryRange("range", in: 0 ..< expectedContents.count) { range in + let i = range.lowerBound + let j = range.upperBound + + // Check `index(_,offsetBy:)` + let e = container.index(allIndices[i], offsetBy: j - i) + expectEqual(e, allIndices[j]) + if j < expectedContents.count { + expectEquivalent(container[e], expectedContents[j], by: areEquivalent) + } + + // Check `distance(from:to:)` + let d = container.distance(from: allIndices[i], to: allIndices[j]) + expectEqual(d, j - i) + } + + // Check `formIndex(_,offsetBy:limitedBy:)` + let limits = Set([0, allIndices.count - 1, allIndices.count / 2]).sorted() + withEvery("limit", in: limits) { limit in + withEvery("i", in: 0 ..< allIndices.count) { i in + let max = allIndices.count - i + (limit >= i ? 2 : 0) + withEvery("delta", in: 0 ..< max) { delta in + let target = i + delta + var index = allIndices[i] + var d = delta + container.formIndex(&index, offsetBy: &d, limitedBy: allIndices[limit]) + if i > limit { + expectEqual(d, 0, "Nonzero remainder after jump opposite limit") + expectEqual(index, allIndices[target], "Jump opposite limit landed in wrong position") + } else if target <= limit { + expectEqual(d, 0, "Nonzero remainder after jump within limit") + expectEqual(index, allIndices[target], "Jump within limit landed in wrong position") + } else { + expectEqual(d, target - limit, "Unexpected remainder after jump beyond limit") + expectEqual(index, allIndices[limit], "Jump beyond limit landed in unexpected position") + } + } + } + } + + // Check that the spans seem plausibly sized and that the indices are monotonic. + let spanShapes: [(offsetRange: Range, indexRange: Range)] = { + var r: [(offsetRange: Range, indexRange: Range)] = [] + var pos = 0 + var index = container.startIndex + while true { + let origIndex = index + let origPos = pos + let span = container.nextSpan(after: &index) + pos += span.count + if span.isEmpty { + expectEqual(origIndex, container.endIndex) + expectEqual(index, origIndex, "nextSpan is not expected to move the end index") + break + } + expectGreaterThan( + index, origIndex, "nextSpan does not monotonically increase the index") + expectEqual( + index, allIndices[pos], "nextSpan does not advance the index by the size of the returned span") + r.append((origPos ..< pos, origIndex ..< index)) + } + return r + }() + expectEqual( + spanShapes.reduce(into: 0, { $0 += $1.offsetRange.count }), actualCount, + "Container's count does not match the sum of its spans") + + + // Check that the spans have stable sizes and the expected contents. + do { + var pos = 0 + var index = container.startIndex + var spanIndex = 0 + while true { + let span = container.nextSpan(after: &index) + if span.isEmpty { break } + expectEqual( + span.count, spanShapes[spanIndex].offsetRange.count, + "Container has nondeterministic span sizes") + expectEqual( + index, spanShapes[spanIndex].indexRange.upperBound, + "Container has nondeterministic span boundaries") + for i in 0 ..< span.count { + expectEqual(span[i], expectedContents[pos]) + pos += 1 + } + spanIndex += 1 + } + expectEqual(spanIndex, spanShapes.endIndex) + expectEqual(pos, expectedContents.count) + } + + // Check that we can get a span beginning at every index, and that it extends as much as possible. + do { + for spanIndex in spanShapes.indices { + let (offsetRange, indexRange) = spanShapes[spanIndex] + for pos in offsetRange { + let start = validIndices[pos] + var i = start + let span = container.nextSpan(after: &i) + + expectEqual(span.count, offsetRange.upperBound - pos, + "Unexpected span size at offset \(pos), index \(start)") + expectEqual(i, indexRange.upperBound, + "Unexpected span upper bound at offset \(pos), index \(start)") + } + } + } +} diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckEquatable.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckEquatable.swift index 4bc8586fc..5002b931f 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckEquatable.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckEquatable.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 @@ -12,6 +12,7 @@ // Loosely adapted from https://github.com/apple/swift/tree/main/stdlib/private/StdlibUnittest +@inlinable public func checkEquatable( equivalenceClasses: [[Instance]], maxSamples: Int? = nil, @@ -23,6 +24,7 @@ public func checkEquatable( checkEquatable(instances, oracle: { oracle[$0] == oracle[$1] }, file: file, line: line) } +@inlinable public func checkEquatable( _ instances: C, oracle: (C.Index, C.Index) -> Bool, @@ -37,6 +39,7 @@ public func checkEquatable( file: file, line: line) } +@inlinable public func checkEquatable( expectedEqual: Bool, _ lhs: T, _ rhs: T, file: StaticString = #file, line: UInt = #line @@ -47,6 +50,7 @@ public func checkEquatable( file: file, line: line) } +@inlinable public func checkEquatable( _ instances: [Instance], oracle: (Int, Int) -> Bool, diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckHashable.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckHashable.swift index 91c329a58..d8496912e 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckHashable.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckHashable.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 @@ -16,7 +16,8 @@ /// `Hasher`. This is always done by calling the `hash(into:)` method. /// If a non-nil `seed` is given, it is used to perturb the hasher state; /// this is useful for resolving accidental hash collisions. -private func _hash(_ value: H, seed: Int? = nil) -> Int { +@inlinable +internal func _hash(_ value: H, seed: Int? = nil) -> Int { var hasher = Hasher() if let seed = seed { hasher.combine(seed) @@ -28,6 +29,7 @@ private func _hash(_ value: H, seed: Int? = nil) -> Int { /// Test that the elements of `equivalenceClasses` consist of instances that /// satisfy the semantic requirements of `Hashable`, with each group defining /// a distinct equivalence class under `==`. +@inlinable public func checkHashable( equivalenceClasses: [[Instance]], file: StaticString = #file, line: UInt = #line @@ -41,6 +43,7 @@ public func checkHashable( file: file, line: line) } +@inlinable public func checkHashable( expectedEqual: Bool, _ lhs: T, _ rhs: T, file: StaticString = #file, line: UInt = #line @@ -52,6 +55,7 @@ public func checkHashable( /// Test that the elements of `instances` satisfy the semantic requirements of /// `Hashable`, using `equalityOracle` to generate equality and hashing /// expectations from pairs of positions in `instances`. +@inlinable public func checkHashable( _ instances: Instances, equalityOracle: (Instances.Index, Instances.Index) -> Bool, @@ -63,6 +67,7 @@ public func checkHashable( /// Same as `checkHashable(_:equalityOracle:file:line:)` but doesn't check /// `Equatable` conformance. Useful for preventing duplicate testing. +@inlinable public func _checkHashable( _ instances: Instances, equalityOracle: (Instances.Index, Instances.Index) -> Bool, diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckSequence.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckSequence.swift index da6869502..714ad9cad 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckSequence.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckSequence.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 @@ -18,6 +18,7 @@ import XCTest // FIXME: Port the collection validation tests from the Swift compiler codebase. +@inlinable public func checkSequence( _ sequenceGenerator: () -> S, expectedContents: Expected, @@ -32,6 +33,7 @@ public func checkSequence( line: line) } +@inlinable public func checkSequence( _ sequenceGenerator: () -> S, expectedContents: Expected, diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalBidirectionalCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalBidirectionalCollection.swift index 02b3dbba0..688a44794 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalBidirectionalCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalBidirectionalCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 @@ -29,9 +29,14 @@ public struct MinimalBidirectionalCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -51,6 +56,13 @@ extension MinimalBidirectionalCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return unsafe try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalBidirectionalCollection: BidirectionalCollection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalCollection.swift index 50d0d9868..009cbc3cc 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 @@ -28,9 +28,14 @@ public struct MinimalCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -50,6 +55,13 @@ extension MinimalCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return unsafe try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalCollection: Collection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRandomAccessCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRandomAccessCollection.swift index 24998e2be..2b8d8c020 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRandomAccessCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRandomAccessCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 @@ -30,9 +30,14 @@ public struct MinimalMutableRandomAccessCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -52,6 +57,13 @@ extension MinimalMutableRandomAccessCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return unsafe try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalMutableRandomAccessCollection: RandomAccessCollection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRangeReplaceableRandomAccessCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRangeReplaceableRandomAccessCollection.swift index b3eedec23..31e7809df 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRangeReplaceableRandomAccessCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalMutableRangeReplaceableRandomAccessCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 @@ -30,9 +30,14 @@ public struct MinimalMutableRangeReplaceableRandomAccessCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -52,6 +57,13 @@ extension MinimalMutableRangeReplaceableRandomAccessCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return unsafe try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalMutableRangeReplaceableRandomAccessCollection: RandomAccessCollection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRandomAccessCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRandomAccessCollection.swift index bf666b8f9..d0405a683 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRandomAccessCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRandomAccessCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 @@ -26,9 +26,14 @@ public struct MinimalRandomAccessCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -48,6 +53,13 @@ extension MinimalRandomAccessCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return unsafe try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalRandomAccessCollection: RandomAccessCollection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRangeReplaceableRandomAccessCollection.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRangeReplaceableRandomAccessCollection.swift index 4a119be33..262d56c2a 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRangeReplaceableRandomAccessCollection.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalRangeReplaceableRandomAccessCollection.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 @@ -30,9 +30,14 @@ public struct MinimalRangeReplaceableRandomAccessCollection { public init( _ elements: S, context: TestContext = TestContext.current, + isContiguous: Bool = false, underestimatedCount: UnderestimatedCountBehavior = .value(0) ) where S.Element == Element { - self._core = _MinimalCollectionCore(context: context, elements: elements, underestimatedCount: underestimatedCount) + self._core = _MinimalCollectionCore( + context: context, + elements: elements, + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } var _context: TestContext { @@ -52,6 +57,13 @@ extension MinimalRangeReplaceableRandomAccessCollection: Sequence { timesUnderestimatedCountCalled.increment() return _core.underestimatedCount } + + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + guard _core.isContiguous else { return nil } + return unsafe try _core.elements.withUnsafeBufferPointer(body) + } } extension MinimalRangeReplaceableRandomAccessCollection: RandomAccessCollection { diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalSequence.swift b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalSequence.swift index 66f6924b3..c3b9e10e1 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/MinimalSequence.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/MinimalSequence.swift @@ -44,17 +44,17 @@ public enum UnderestimatedCountBehavior { /// narrow way possible. /// /// This sequence is consumed when its iterator is advanced. -public struct MinimalSequence: Sequence, CustomDebugStringConvertible { +public struct MinimalSequence: Sequence, CustomDebugStringConvertible { public let timesMakeIteratorCalled = ResettableValue(0) - internal let _sharedState: _MinimalIteratorSharedState + internal let _sharedState: _MinimalIteratorSharedState internal let _isContiguous: Bool public init( elements: S, underestimatedCount: UnderestimatedCountBehavior = .value(0), isContiguous: Bool = false - ) where S.Element == T { + ) where S.Element == Element { let data = Array(elements) self._sharedState = _MinimalIteratorSharedState(data) self._isContiguous = isContiguous @@ -73,7 +73,7 @@ public struct MinimalSequence: Sequence, CustomDebugStringConvertible { } } - public func makeIterator() -> MinimalIterator { + public func makeIterator() -> MinimalIterator { timesMakeIteratorCalled.value += 1 return MinimalIterator(_sharedState) } @@ -87,10 +87,10 @@ public struct MinimalSequence: Sequence, CustomDebugStringConvertible { } public func withContiguousStorageIfAvailable( - _ body: (UnsafeBufferPointer) throws -> R + _ body: (UnsafeBufferPointer) throws -> R ) rethrows -> R? { guard _isContiguous else { return nil } - return try _sharedState.data[_sharedState.i...] + return unsafe try _sharedState.data[_sharedState.i...] .withContiguousStorageIfAvailable(body) } } diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift b/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift new file mode 100644 index 000000000..ae3517bef --- /dev/null +++ b/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift @@ -0,0 +1,107 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2025 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 +// +//===----------------------------------------------------------------------===// + +import Future + +/// A container type with user-defined contents and storage chunks. Useful for testing. +public struct StaccatoContainer: ~Copyable { + internal let _contents: RigidArray + internal let _spanCounts: [Int] + internal let _modulus: Int + + public init(contents: consuming RigidArray, spanCounts: [Int]) { + // FIXME: Make this take an arbitrary consumable container + precondition(!spanCounts.isEmpty && spanCounts.allSatisfy { $0 > 0 }) + self._contents = contents + self._spanCounts = spanCounts + self._modulus = spanCounts.reduce(into: 0, { $0 += $1 }) + } +} + +public struct _StaccatoContainerIndex: Comparable { + internal var _offset: Int + + internal init(_offset: Int) { + self._offset = _offset + } + + public static func == (left: Self, right: Self) -> Bool { left._offset == right._offset } + public static func < (left: Self, right: Self) -> Bool { left._offset < right._offset } +} + +extension StaccatoContainer where Element: ~Copyable { + public typealias Index = _StaccatoContainerIndex // rdar://150240032 + + func _spanCoordinates(atOffset offset: Int) -> (spanIndex: Int, spanOffset: Int) { + precondition(offset >= 0 && offset <= _contents.count) + var remainder = offset % _modulus + var i = 0 + while i < _spanCounts.count { + let c = _spanCounts[i] + if remainder < c { break } + remainder -= c + i += 1 + } + return (i, remainder) + } +} + +extension StaccatoContainer where Element: ~Copyable { + public var isEmpty: Bool { _contents.isEmpty } + public var count: Int { _contents.count } + public var startIndex: Index { Index(_offset: 0) } + public var endIndex: Index { Index(_offset: _contents.count) } + public func index(after index: Index) -> Index { + precondition(index >= startIndex && index < endIndex) + return Index(_offset: index._offset + 1) + } + public func formIndex(after i: inout Index) { + i = index(after: i) + } + public func index(_ index: Index, offsetBy n: Int) -> Index { + precondition(index._offset >= 0 && index._offset <= _contents.count) + let offset = index._offset + n + precondition(offset >= 0 && offset <= _contents.count) + return Index(_offset: offset) + } + + public func distance(from start: Index, to end: Index) -> Int { + precondition(start >= startIndex && start <= endIndex) + precondition(end >= startIndex && end <= endIndex) + return end._offset - start._offset + } +} + +@available(SwiftStdlib 6.2, *) +extension StaccatoContainer: Container where Element: ~Copyable { + public func formIndex(_ i: inout Index, offsetBy distance: inout Int, limitedBy limit: Index) { + precondition(i >= startIndex && i <= endIndex) + _contents.formIndex(&i._offset, offsetBy: &distance, limitedBy: limit._offset) + } + + @lifetime(borrow self) + public func borrowElement(at index: Index) -> Future.Borrow { + precondition(index >= startIndex && index < endIndex, "Index out of bounds") + return _contents.borrowElement(at: index._offset) + } + + @lifetime(borrow self) + public func nextSpan(after index: inout Index) -> Span { + precondition(index >= startIndex && index <= endIndex) + let span = _contents.span + let (spanIndex, spanOffset) = _spanCoordinates(atOffset: index._offset) + let c = _spanCounts[spanIndex] - spanOffset + let startOffset = index._offset + let endOffset = Swift.min(startOffset + c, span.count) + index._offset = endOffset + return span._extracting(startOffset ..< endOffset) + } +} diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/_MinimalCollectionCore.swift b/Tests/_CollectionsTestSupport/MinimalTypes/_MinimalCollectionCore.swift index 6e6d86668..4d348ac77 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/_MinimalCollectionCore.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/_MinimalCollectionCore.swift @@ -2,7 +2,7 @@ // // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2025 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 @@ -13,23 +13,31 @@ struct _MinimalCollectionCore { var state: _CollectionState var elements: [Element] + let isContiguous: Bool var underestimatedCount: Int public init( context: TestContext, elements: S, + isContiguous: Bool, underestimatedCount: UnderestimatedCountBehavior? = nil ) where S.Element == Element { - self.init(context: context, elements: Array(elements), underestimatedCount: underestimatedCount) + self.init( + context: context, + elements: Array(elements), + isContiguous: isContiguous, + underestimatedCount: underestimatedCount) } public init( context: TestContext, elements: [Element], + isContiguous: Bool, underestimatedCount: UnderestimatedCountBehavior? = nil ) { self.state = _CollectionState(context: context, parent: nil, count: elements.count) self.elements = elements + self.isContiguous = isContiguous self.underestimatedCount = underestimatedCount?.value(forCount: elements.count) ?? elements.count } diff --git a/Tests/_CollectionsTestSupport/Utilities/LifetimeTrackedStruct.swift b/Tests/_CollectionsTestSupport/Utilities/LifetimeTrackedStruct.swift new file mode 100644 index 000000000..b1b32aae8 --- /dev/null +++ b/Tests/_CollectionsTestSupport/Utilities/LifetimeTrackedStruct.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 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 type that tracks the number of live instances. +/// +/// `LifetimeTracked` is useful to check for leaks in algorithms and data +/// structures. The easiest way to produce instances is to use the +/// `withLifetimeTracking` function: +/// +/// class FooTests: XCTestCase { +/// func testFoo() { +/// withLifetimeTracking([1, 2, 3]) { instances in +/// _ = instances.sorted(by: >) +/// } +/// } +/// } +public struct LifetimeTrackedStruct: ~Copyable { + public let tracker: LifetimeTracker + internal var serialNumber: Int = 0 + public var payload: Payload + + public init(_ payload: consuming Payload, for tracker: LifetimeTracker) { + tracker.instances += 1 + tracker._nextSerialNumber += 1 + self.tracker = tracker + self.serialNumber = tracker._nextSerialNumber + self.payload = payload + } + + deinit { + precondition(serialNumber != 0, "Double deinit") + tracker.instances -= 1 + } +} + diff --git a/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift b/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift index 1728cff69..0321f8dfb 100644 --- a/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift +++ b/Tests/_CollectionsTestSupport/Utilities/LifetimeTracker.swift @@ -37,6 +37,12 @@ public class LifetimeTracker { LifetimeTracked(payload, for: self) } + public func structInstance( + for payload: consuming Payload + ) -> LifetimeTrackedStruct { + LifetimeTrackedStruct(payload, for: self) + } + public func instances(for items: S) -> [LifetimeTracked] { return items.map { LifetimeTracked($0, for: self) } } @@ -49,11 +55,11 @@ public class LifetimeTracker { } @inlinable -public func withLifetimeTracking( +public func withLifetimeTracking( file: StaticString = #file, line: UInt = #line, - _ body: (LifetimeTracker) throws -> R -) rethrows -> R { + _ body: (LifetimeTracker) throws(E) -> R +) throws(E) -> R { let tracker = LifetimeTracker() defer { tracker.check(file: file, line: line) } return try body(tracker) diff --git a/Xcode/Collections.xcodeproj/project.pbxproj b/Xcode/Collections.xcodeproj/project.pbxproj index 2eaac6343..637baf8bf 100644 --- a/Xcode/Collections.xcodeproj/project.pbxproj +++ b/Xcode/Collections.xcodeproj/project.pbxproj @@ -7,13 +7,27 @@ objects = { /* Begin PBXBuildFile section */ + 7D380E052C6ABF6800AD8F58 /* NewArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E042C6ABF6000AD8F58 /* NewArray.swift */; }; + 7D380E072C6BFB9B00AD8F58 /* LifetimeOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E062C6BFB9500AD8F58 /* LifetimeOverride.swift */; }; + 7D380E0D2C6D565300AD8F58 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E0C2C6D565000AD8F58 /* Shared.swift */; }; + 7D9514292C750E08009C37DD /* RigidDeque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC142C5AB45600A1BF15 /* RigidDeque.swift */; }; + 7D95142A2C753862009C37DD /* Deque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBB29CA70F3004483EB /* Deque.swift */; }; + 7D95142B2C753955009C37DD /* Deque+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC829CA70F3004483EB /* Deque+Equatable.swift */; }; + 7D95142C2C7539B9009C37DD /* Deque+Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB829CA70F3004483EB /* Deque+Collection.swift */; }; + 7D95142D2C753C1B009C37DD /* Deque+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC129CA70F3004483EB /* Deque+Codable.swift */; }; + 7D95142E2C753C2E009C37DD /* Deque+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBA29CA70F3004483EB /* Deque+CustomReflectable.swift */; }; + 7D95142F2C753C34009C37DD /* Deque+Descriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC529CA70F3004483EB /* Deque+Descriptions.swift */; }; + 7D9514302C753C45009C37DD /* Deque+ExpressibleByArrayLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC729CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift */; }; + 7D9514312C753C4A009C37DD /* Deque+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC629CA70F3004483EB /* Deque+Extras.swift */; }; + 7D9514322C753C55009C37DD /* Deque+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBD29CA70F3004483EB /* Deque+Hashable.swift */; }; + 7D9514332C753C5B009C37DD /* Deque+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC229CA70F3004483EB /* Deque+Testing.swift */; }; + 7D9514342C753C63009C37DD /* DynamicDeque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC132C5AB45600A1BF15 /* DynamicDeque.swift */; }; + 7D9514352C753FC1009C37DD /* Deque+Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D380E022C6AAA9300AD8F58 /* Deque+Container.swift */; }; 7D9B859729E4F74400B291CD /* BitArray+Shifts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859129E4F74400B291CD /* BitArray+Shifts.swift */; }; 7D9B859829E4F74400B291CD /* BitArray+Descriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859229E4F74400B291CD /* BitArray+Descriptions.swift */; }; 7D9B859929E4F74400B291CD /* BitArray+LosslessStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859329E4F74400B291CD /* BitArray+LosslessStringConvertible.swift */; }; 7D9B859A29E4F74400B291CD /* BitArray+RandomBits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859429E4F74400B291CD /* BitArray+RandomBits.swift */; }; 7D9B859B29E4F74400B291CD /* BitArray+ExpressibleByStringLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D9B859529E4F74400B291CD /* BitArray+ExpressibleByStringLiteral.swift */; }; - 7DB0AE762B6E06B300602A20 /* Specialize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DB0AE752B6E06B300602A20 /* Specialize.swift */; }; - 7DB0AE772B6E06B300602A20 /* Specialize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DB0AE752B6E06B300602A20 /* Specialize.swift */; }; 7DE91B2F29CA6721004483EB /* Collections.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7DE91B2629CA6721004483EB /* Collections.framework */; }; 7DE9200D29CA70F3004483EB /* OrderedDictionary+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91E7529CA70F3004483EB /* OrderedDictionary+Equatable.swift */; }; 7DE9200E29CA70F3004483EB /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91E7629CA70F3004483EB /* OrderedDictionary+ExpressibleByDictionaryLiteral.swift */; }; @@ -76,23 +90,8 @@ 7DE9204729CA70F3004483EB /* _HashTable+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB229CA70F3004483EB /* _HashTable+Testing.swift */; }; 7DE9204829CA70F3004483EB /* _HashTable+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB329CA70F3004483EB /* _HashTable+CustomStringConvertible.swift */; }; 7DE9204929CA70F3004483EB /* _HashTable+BucketIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB429CA70F3004483EB /* _HashTable+BucketIterator.swift */; }; - 7DE9204B29CA70F3004483EB /* _DequeBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB729CA70F3004483EB /* _DequeBuffer.swift */; }; - 7DE9204C29CA70F3004483EB /* Deque+Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EB829CA70F3004483EB /* Deque+Collection.swift */; }; - 7DE9204E29CA70F3004483EB /* Deque+CustomReflectable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBA29CA70F3004483EB /* Deque+CustomReflectable.swift */; }; - 7DE9204F29CA70F3004483EB /* Deque.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBB29CA70F3004483EB /* Deque.swift */; }; 7DE9205029CA70F3004483EB /* _DequeSlot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBC29CA70F3004483EB /* _DequeSlot.swift */; }; - 7DE9205129CA70F3004483EB /* Deque+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBD29CA70F3004483EB /* Deque+Hashable.swift */; }; - 7DE9205229CA70F3004483EB /* _UnsafeWrappedBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBE29CA70F3004483EB /* _UnsafeWrappedBuffer.swift */; }; - 7DE9205329CA70F3004483EB /* _DequeBufferHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBF29CA70F3004483EB /* _DequeBufferHeader.swift */; }; - 7DE9205429CA70F3004483EB /* Deque+Sendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC029CA70F3004483EB /* Deque+Sendable.swift */; }; - 7DE9205529CA70F3004483EB /* Deque+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC129CA70F3004483EB /* Deque+Codable.swift */; }; - 7DE9205629CA70F3004483EB /* Deque+Testing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC229CA70F3004483EB /* Deque+Testing.swift */; }; - 7DE9205829CA70F3004483EB /* Deque._UnsafeHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC429CA70F3004483EB /* Deque._UnsafeHandle.swift */; }; - 7DE9205929CA70F3004483EB /* Deque+Descriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC529CA70F3004483EB /* Deque+Descriptions.swift */; }; - 7DE9205A29CA70F3004483EB /* Deque+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC629CA70F3004483EB /* Deque+Extras.swift */; }; - 7DE9205B29CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC729CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift */; }; - 7DE9205C29CA70F3004483EB /* Deque+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC829CA70F3004483EB /* Deque+Equatable.swift */; }; - 7DE9205D29CA70F4004483EB /* Deque._Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EC929CA70F3004483EB /* Deque._Storage.swift */; }; + 7DE9205229CA70F3004483EB /* _UnsafeDequeSegments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91EBE29CA70F3004483EB /* _UnsafeDequeSegments.swift */; }; 7DE9205E29CA70F4004483EB /* BigString+Metrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91ECD29CA70F3004483EB /* BigString+Metrics.swift */; }; 7DE9205F29CA70F4004483EB /* BigString+Index.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91ECE29CA70F3004483EB /* BigString+Index.swift */; }; 7DE9206029CA70F4004483EB /* BigString+Summary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91ECF29CA70F3004483EB /* BigString+Summary.swift */; }; @@ -354,9 +353,6 @@ 7DE9221C29CA8576004483EB /* RangeReplaceableCollectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE921F729CA8576004483EB /* RangeReplaceableCollectionTests.swift */; }; 7DE9221D29CA8576004483EB /* DequeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE921F829CA8576004483EB /* DequeTests.swift */; }; 7DE9221E29CA8576004483EB /* MutableCollectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE921F929CA8576004483EB /* MutableCollectionTests.swift */; }; - 7DEBDAF229CBEE5300ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDACD29CBEE5200ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift */; }; - 7DEBDAF429CBEE5300ADC226 /* UnsafeMutablePointer+SE-0370.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDACF29CBEE5200ADC226 /* UnsafeMutablePointer+SE-0370.swift */; }; - 7DEBDAF529CBEE5300ADC226 /* UnsafeRawPointer extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAD029CBEE5200ADC226 /* UnsafeRawPointer extensions.swift */; }; 7DEBDAF829CBEE5300ADC226 /* UnsafeMutableBufferPointer+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAD429CBEE5300ADC226 /* UnsafeMutableBufferPointer+Extras.swift */; }; 7DEBDAF929CBEE5300ADC226 /* UnsafeBufferPointer+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAD529CBEE5300ADC226 /* UnsafeBufferPointer+Extras.swift */; }; 7DEBDAFA29CBEE5300ADC226 /* RandomAccessCollection+Offsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAD629CBEE5300ADC226 /* RandomAccessCollection+Offsets.swift */; }; @@ -373,9 +369,6 @@ 7DEBDB0F29CBF68900ADC226 /* UInt+reversed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAE829CBEE5300ADC226 /* UInt+reversed.swift */; }; 7DEBDB1029CBF68900ADC226 /* UInt+first and last set bit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAEA29CBEE5300ADC226 /* UInt+first and last set bit.swift */; }; 7DEBDB1129CBF68900ADC226 /* FixedWidthInteger+roundUpToPowerOfTwo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAEB29CBEE5300ADC226 /* FixedWidthInteger+roundUpToPowerOfTwo.swift */; }; - 7DEBDB1229CBF68D00ADC226 /* UnsafeMutablePointer+SE-0370.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDACF29CBEE5200ADC226 /* UnsafeMutablePointer+SE-0370.swift */; }; - 7DEBDB1429CBF68D00ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDACD29CBEE5200ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift */; }; - 7DEBDB1529CBF68D00ADC226 /* UnsafeRawPointer extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAD029CBEE5200ADC226 /* UnsafeRawPointer extensions.swift */; }; 7DEBDB1629CBF69F00ADC226 /* _UnsafeBitSet+Index.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDADF29CBEE5300ADC226 /* _UnsafeBitSet+Index.swift */; }; 7DEBDB1729CBF69F00ADC226 /* _UnsafeBitSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAE029CBEE5300ADC226 /* _UnsafeBitSet.swift */; }; 7DEBDB1829CBF69F00ADC226 /* _UnsafeBitSet+_Word.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDAE129CBEE5300ADC226 /* _UnsafeBitSet+_Word.swift */; }; @@ -422,6 +415,25 @@ 7DEBDB9129CCE44A00ADC226 /* CollectionTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDB2429CCE43600ADC226 /* CollectionTestCase.swift */; }; 7DEBDB9229CCE44A00ADC226 /* StringConvertibleValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBDB2B29CCE43600ADC226 /* StringConvertibleValue.swift */; }; 7DEBDB9D29CCE73D00ADC226 /* Collections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DE91F7B29CA70F3004483EB /* Collections.swift */; }; + 7DEBEC172C5AB45600A1BF15 /* _UnsafeDequeHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC122C5AB45600A1BF15 /* _UnsafeDequeHandle.swift */; }; + 7DEBEC252C5AE43C00A1BF15 /* CMakeLists.txt in Resources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1E2C5AE43C00A1BF15 /* CMakeLists.txt */; }; + 7DEBEC262C5AE43C00A1BF15 /* RigidArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1A2C5AE43C00A1BF15 /* RigidArray.swift */; }; + 7DEBEC272C5AE43C00A1BF15 /* DynamicArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC192C5AE43C00A1BF15 /* DynamicArray.swift */; }; + 7DEBEC282C5AE43C00A1BF15 /* Inout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC202C5AE43C00A1BF15 /* Inout.swift */; }; + 7DEBEC292C5AE43C00A1BF15 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC182C5AE43C00A1BF15 /* Container.swift */; }; + 7DEBEC2A2C5AE43C00A1BF15 /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1C2C5AE43C00A1BF15 /* Box.swift */; }; + 7DEBEC2B2C5AE43C00A1BF15 /* UnsafeBufferPointer+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC232C5AE43C00A1BF15 /* UnsafeBufferPointer+Additions.swift */; }; + 7DEBEC2C2C5AE43C00A1BF15 /* RawSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC212C5AE43C00A1BF15 /* RawSpan.swift */; }; + 7DEBEC2D2C5AE43C00A1BF15 /* Cell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1D2C5AE43C00A1BF15 /* Cell.swift */; }; + 7DEBEC2E2C5AE43C00A1BF15 /* Span.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC222C5AE43C00A1BF15 /* Span.swift */; }; + 7DEBEC2F2C5AE43C00A1BF15 /* ContiguousStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DEBEC1F2C5AE43C00A1BF15 /* ContiguousStorage.swift */; }; + BBD683962C7F857900B567A9 /* SpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD683942C7F857900B567A9 /* SpanTests.swift */; }; + BBD683972C7F857900B567A9 /* Inout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD683922C7F857900B567A9 /* Inout.swift */; }; + BBD683982C7F857900B567A9 /* BoxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD6838E2C7F857900B567A9 /* BoxTests.swift */; }; + BBD683992C7F857900B567A9 /* CellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD6838F2C7F857900B567A9 /* CellTests.swift */; }; + BBD6839A2C7F857900B567A9 /* RawSpanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD683932C7F857900B567A9 /* RawSpanTests.swift */; }; + BBD6839B2C7F857900B567A9 /* DynamicArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD683912C7F857900B567A9 /* DynamicArrayTests.swift */; }; + BBD6839C2C7F857900B567A9 /* ContiguousStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBD683902C7F857900B567A9 /* ContiguousStorageTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -435,6 +447,10 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 7D380E022C6AAA9300AD8F58 /* Deque+Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Deque+Container.swift"; sourceTree = ""; }; + 7D380E042C6ABF6000AD8F58 /* NewArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewArray.swift; sourceTree = ""; }; + 7D380E062C6BFB9500AD8F58 /* LifetimeOverride.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LifetimeOverride.swift; sourceTree = ""; }; + 7D380E0C2C6D565000AD8F58 /* Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = ""; }; 7D5A64D229CCE8CC00CB2595 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 7D5A64D329CCEE9A00CB2595 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 7D5A64D429CCEF1500CB2595 /* generate-sources.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "generate-sources.sh"; sourceTree = ""; }; @@ -448,8 +464,6 @@ 7D9B859429E4F74400B291CD /* BitArray+RandomBits.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BitArray+RandomBits.swift"; sourceTree = ""; }; 7D9B859529E4F74400B291CD /* BitArray+ExpressibleByStringLiteral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BitArray+ExpressibleByStringLiteral.swift"; sourceTree = ""; }; 7D9B859C29E4F77D00B291CD /* Collections.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Collections.xctestplan; sourceTree = ""; }; - 7DB0AE712B6E067A00602A20 /* Specialize.swift.gyb */ = {isa = PBXFileReference; lastKnownFileType = text; path = Specialize.swift.gyb; sourceTree = ""; }; - 7DB0AE752B6E06B300602A20 /* Specialize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Specialize.swift; sourceTree = ""; }; 7DE91B2629CA6721004483EB /* Collections.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Collections.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7DE91B2E29CA6721004483EB /* CollectionsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CollectionsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 7DE91E7229CA70F3004483EB /* OrderedCollections.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = OrderedCollections.docc; sourceTree = ""; }; @@ -516,25 +530,20 @@ 7DE91EB329CA70F3004483EB /* _HashTable+CustomStringConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "_HashTable+CustomStringConvertible.swift"; sourceTree = ""; }; 7DE91EB429CA70F3004483EB /* _HashTable+BucketIterator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "_HashTable+BucketIterator.swift"; sourceTree = ""; }; 7DE91EB529CA70F3004483EB /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; - 7DE91EB729CA70F3004483EB /* _DequeBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _DequeBuffer.swift; sourceTree = ""; }; 7DE91EB829CA70F3004483EB /* Deque+Collection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Collection.swift"; sourceTree = ""; }; 7DE91EB929CA70F3004483EB /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; 7DE91EBA29CA70F3004483EB /* Deque+CustomReflectable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+CustomReflectable.swift"; sourceTree = ""; }; 7DE91EBB29CA70F3004483EB /* Deque.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Deque.swift; sourceTree = ""; }; 7DE91EBC29CA70F3004483EB /* _DequeSlot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _DequeSlot.swift; sourceTree = ""; }; 7DE91EBD29CA70F3004483EB /* Deque+Hashable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Hashable.swift"; sourceTree = ""; }; - 7DE91EBE29CA70F3004483EB /* _UnsafeWrappedBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _UnsafeWrappedBuffer.swift; sourceTree = ""; }; - 7DE91EBF29CA70F3004483EB /* _DequeBufferHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _DequeBufferHeader.swift; sourceTree = ""; }; - 7DE91EC029CA70F3004483EB /* Deque+Sendable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Sendable.swift"; sourceTree = ""; }; + 7DE91EBE29CA70F3004483EB /* _UnsafeDequeSegments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _UnsafeDequeSegments.swift; sourceTree = ""; }; 7DE91EC129CA70F3004483EB /* Deque+Codable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Codable.swift"; sourceTree = ""; }; 7DE91EC229CA70F3004483EB /* Deque+Testing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Testing.swift"; sourceTree = ""; }; 7DE91EC329CA70F3004483EB /* DequeModule.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = DequeModule.docc; sourceTree = ""; }; - 7DE91EC429CA70F3004483EB /* Deque._UnsafeHandle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Deque._UnsafeHandle.swift; sourceTree = ""; }; 7DE91EC529CA70F3004483EB /* Deque+Descriptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Descriptions.swift"; sourceTree = ""; }; 7DE91EC629CA70F3004483EB /* Deque+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Extras.swift"; sourceTree = ""; }; 7DE91EC729CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+ExpressibleByArrayLiteral.swift"; sourceTree = ""; }; 7DE91EC829CA70F3004483EB /* Deque+Equatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Deque+Equatable.swift"; sourceTree = ""; }; - 7DE91EC929CA70F3004483EB /* Deque._Storage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Deque._Storage.swift; sourceTree = ""; }; 7DE91ECD29CA70F3004483EB /* BigString+Metrics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BigString+Metrics.swift"; sourceTree = ""; }; 7DE91ECE29CA70F3004483EB /* BigString+Index.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BigString+Index.swift"; sourceTree = ""; }; 7DE91ECF29CA70F3004483EB /* BigString+Summary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BigString+Summary.swift"; sourceTree = ""; }; @@ -815,12 +824,6 @@ 7DE921F729CA8576004483EB /* RangeReplaceableCollectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RangeReplaceableCollectionTests.swift; sourceTree = ""; }; 7DE921F829CA8576004483EB /* DequeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DequeTests.swift; sourceTree = ""; }; 7DE921F929CA8576004483EB /* MutableCollectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutableCollectionTests.swift; sourceTree = ""; }; - 7DEBDAC829CBEE5200ADC226 /* UnsafeMutablePointer+SE-0370.swift.gyb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "UnsafeMutablePointer+SE-0370.swift.gyb"; sourceTree = ""; }; - 7DEBDAC929CBEE5200ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift.gyb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "UnsafeMutableBufferPointer+SE-0370.swift.gyb"; sourceTree = ""; }; - 7DEBDACB29CBEE5200ADC226 /* UnsafeRawPointer extensions.swift.gyb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "UnsafeRawPointer extensions.swift.gyb"; sourceTree = ""; }; - 7DEBDACD29CBEE5200ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UnsafeMutableBufferPointer+SE-0370.swift"; sourceTree = ""; }; - 7DEBDACF29CBEE5200ADC226 /* UnsafeMutablePointer+SE-0370.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UnsafeMutablePointer+SE-0370.swift"; sourceTree = ""; }; - 7DEBDAD029CBEE5200ADC226 /* UnsafeRawPointer extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UnsafeRawPointer extensions.swift"; sourceTree = ""; }; 7DEBDAD129CBEE5200ADC226 /* Descriptions.swift.gyb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Descriptions.swift.gyb; sourceTree = ""; }; 7DEBDAD229CBEE5200ADC226 /* RandomAccessCollection+Offsets.swift.gyb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "RandomAccessCollection+Offsets.swift.gyb"; sourceTree = ""; }; 7DEBDAD429CBEE5300ADC226 /* UnsafeMutableBufferPointer+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UnsafeMutableBufferPointer+Extras.swift"; sourceTree = ""; }; @@ -885,6 +888,27 @@ 7DEBDB9729CCE4A600ADC226 /* CollectionsTests.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = CollectionsTests.xcconfig; sourceTree = ""; }; 7DEBDB9829CCE4A600ADC226 /* Shared.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = ""; }; 7DEBDB9929CCE4A600ADC226 /* Collections.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Collections.xcconfig; sourceTree = ""; }; + 7DEBEC122C5AB45600A1BF15 /* _UnsafeDequeHandle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _UnsafeDequeHandle.swift; sourceTree = ""; }; + 7DEBEC132C5AB45600A1BF15 /* DynamicDeque.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicDeque.swift; sourceTree = ""; }; + 7DEBEC142C5AB45600A1BF15 /* RigidDeque.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RigidDeque.swift; sourceTree = ""; }; + 7DEBEC182C5AE43C00A1BF15 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; + 7DEBEC192C5AE43C00A1BF15 /* DynamicArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicArray.swift; sourceTree = ""; }; + 7DEBEC1A2C5AE43C00A1BF15 /* RigidArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RigidArray.swift; sourceTree = ""; }; + 7DEBEC1C2C5AE43C00A1BF15 /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = ""; }; + 7DEBEC1D2C5AE43C00A1BF15 /* Cell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cell.swift; sourceTree = ""; }; + 7DEBEC1E2C5AE43C00A1BF15 /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; + 7DEBEC1F2C5AE43C00A1BF15 /* ContiguousStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContiguousStorage.swift; sourceTree = ""; }; + 7DEBEC202C5AE43C00A1BF15 /* Inout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inout.swift; sourceTree = ""; }; + 7DEBEC212C5AE43C00A1BF15 /* RawSpan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawSpan.swift; sourceTree = ""; }; + 7DEBEC222C5AE43C00A1BF15 /* Span.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Span.swift; sourceTree = ""; }; + 7DEBEC232C5AE43C00A1BF15 /* UnsafeBufferPointer+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UnsafeBufferPointer+Additions.swift"; sourceTree = ""; }; + BBD6838E2C7F857900B567A9 /* BoxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxTests.swift; sourceTree = ""; }; + BBD6838F2C7F857900B567A9 /* CellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellTests.swift; sourceTree = ""; }; + BBD683902C7F857900B567A9 /* ContiguousStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContiguousStorageTests.swift; sourceTree = ""; }; + BBD683912C7F857900B567A9 /* DynamicArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicArrayTests.swift; sourceTree = ""; }; + BBD683922C7F857900B567A9 /* Inout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inout.swift; sourceTree = ""; }; + BBD683932C7F857900B567A9 /* RawSpanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawSpanTests.swift; sourceTree = ""; }; + BBD683942C7F857900B567A9 /* SpanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpanTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -938,13 +962,14 @@ children = ( 7DE91F2229CA70F3004483EB /* InternalCollectionsUtilities */, 7DE91F3929CA70F3004483EB /* BitCollections */, + 7DE91EB529CA70F3004483EB /* CMakeLists.txt */, 7DE91F7929CA70F3004483EB /* Collections */, 7DE91EB629CA70F3004483EB /* DequeModule */, + 7DEBEC242C5AE43C00A1BF15 /* Future */, 7DE91FA729CA70F3004483EB /* HashTreeCollections */, 7DE9200229CA70F3004483EB /* HeapModule */, 7DE91E7129CA70F3004483EB /* OrderedCollections */, 7DE91ECA29CA70F3004483EB /* RopeModule */, - 7DE91EB529CA70F3004483EB /* CMakeLists.txt */, ); name = Sources; path = ../Sources; @@ -1055,25 +1080,24 @@ 7DE91EB629CA70F3004483EB /* DequeModule */ = { isa = PBXGroup; children = ( - 7DE91EB729CA70F3004483EB /* _DequeBuffer.swift */, - 7DE91EBF29CA70F3004483EB /* _DequeBufferHeader.swift */, 7DE91EBC29CA70F3004483EB /* _DequeSlot.swift */, - 7DE91EBE29CA70F3004483EB /* _UnsafeWrappedBuffer.swift */, - 7DE91EC929CA70F3004483EB /* Deque._Storage.swift */, - 7DE91EC429CA70F3004483EB /* Deque._UnsafeHandle.swift */, + 7DEBEC122C5AB45600A1BF15 /* _UnsafeDequeHandle.swift */, + 7DE91EBE29CA70F3004483EB /* _UnsafeDequeSegments.swift */, + 7DE91EB929CA70F3004483EB /* CMakeLists.txt */, 7DE91EBB29CA70F3004483EB /* Deque.swift */, 7DE91EC129CA70F3004483EB /* Deque+Codable.swift */, 7DE91EB829CA70F3004483EB /* Deque+Collection.swift */, + 7D380E022C6AAA9300AD8F58 /* Deque+Container.swift */, 7DE91EBA29CA70F3004483EB /* Deque+CustomReflectable.swift */, 7DE91EC529CA70F3004483EB /* Deque+Descriptions.swift */, 7DE91EC829CA70F3004483EB /* Deque+Equatable.swift */, 7DE91EC729CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift */, 7DE91EC629CA70F3004483EB /* Deque+Extras.swift */, 7DE91EBD29CA70F3004483EB /* Deque+Hashable.swift */, - 7DE91EC029CA70F3004483EB /* Deque+Sendable.swift */, 7DE91EC229CA70F3004483EB /* Deque+Testing.swift */, 7DE91EC329CA70F3004483EB /* DequeModule.docc */, - 7DE91EB929CA70F3004483EB /* CMakeLists.txt */, + 7DEBEC132C5AB45600A1BF15 /* DynamicDeque.swift */, + 7DEBEC142C5AB45600A1BF15 /* RigidDeque.swift */, ); path = DequeModule; sourceTree = ""; @@ -1253,7 +1277,6 @@ isa = PBXGroup; children = ( 7DEBDAD329CBEE5300ADC226 /* autogenerated */, - 7DEBDAC729CBEE5200ADC226 /* Compatibility */, 7DEBDAE229CBEE5300ADC226 /* IntegerTricks */, 7DEBDADA29CBEE5300ADC226 /* UnsafeBitSet */, 7DE91F3029CA70F3004483EB /* _SortedCollection.swift */, @@ -1261,7 +1284,6 @@ 7DEBDAED29CBEE5300ADC226 /* Debugging.swift.gyb */, 7DEBDAD129CBEE5200ADC226 /* Descriptions.swift.gyb */, 7DEBDAD229CBEE5200ADC226 /* RandomAccessCollection+Offsets.swift.gyb */, - 7DB0AE712B6E067A00602A20 /* Specialize.swift.gyb */, 7DEBDAD929CBEE5300ADC226 /* UnsafeBufferPointer+Extras.swift.gyb */, 7DEBDAEC29CBEE5300ADC226 /* UnsafeMutableBufferPointer+Extras.swift.gyb */, 7DE91F2E29CA70F3004483EB /* CMakeLists.txt */, @@ -1524,6 +1546,7 @@ 7DE921CA29CA8575004483EB /* Tests */ = { isa = PBXGroup; children = ( + BBD683952C7F857900B567A9 /* FutureTests */, 7DEBDB1F29CCE43600ADC226 /* _CollectionsTestSupport */, 7DE921DA29CA8575004483EB /* BitCollectionsTests */, 7DE921CB29CA8575004483EB /* CollectionsTestSupportTests */, @@ -1647,34 +1670,12 @@ path = DequeTests; sourceTree = ""; }; - 7DEBDAC729CBEE5200ADC226 /* Compatibility */ = { - isa = PBXGroup; - children = ( - 7DEBDACC29CBEE5200ADC226 /* autogenerated */, - 7DEBDAC929CBEE5200ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift.gyb */, - 7DEBDAC829CBEE5200ADC226 /* UnsafeMutablePointer+SE-0370.swift.gyb */, - 7DEBDACB29CBEE5200ADC226 /* UnsafeRawPointer extensions.swift.gyb */, - ); - path = Compatibility; - sourceTree = ""; - }; - 7DEBDACC29CBEE5200ADC226 /* autogenerated */ = { - isa = PBXGroup; - children = ( - 7DEBDACD29CBEE5200ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift */, - 7DEBDACF29CBEE5200ADC226 /* UnsafeMutablePointer+SE-0370.swift */, - 7DEBDAD029CBEE5200ADC226 /* UnsafeRawPointer extensions.swift */, - ); - path = autogenerated; - sourceTree = ""; - }; 7DEBDAD329CBEE5300ADC226 /* autogenerated */ = { isa = PBXGroup; children = ( 7DEBDAD829CBEE5300ADC226 /* Debugging.swift */, 7DEBDAD729CBEE5300ADC226 /* Descriptions.swift */, 7DEBDAD629CBEE5300ADC226 /* RandomAccessCollection+Offsets.swift */, - 7DB0AE752B6E06B300602A20 /* Specialize.swift */, 7DEBDAD529CBEE5300ADC226 /* UnsafeBufferPointer+Extras.swift */, 7DEBDAD429CBEE5300ADC226 /* UnsafeMutableBufferPointer+Extras.swift */, ); @@ -1813,6 +1814,49 @@ name = Xcode; sourceTree = ""; }; + 7DEBEC1B2C5AE43C00A1BF15 /* Containers */ = { + isa = PBXGroup; + children = ( + 7DEBEC182C5AE43C00A1BF15 /* Container.swift */, + 7D380E0C2C6D565000AD8F58 /* Shared.swift */, + 7DEBEC192C5AE43C00A1BF15 /* DynamicArray.swift */, + 7DEBEC1A2C5AE43C00A1BF15 /* RigidArray.swift */, + 7D380E042C6ABF6000AD8F58 /* NewArray.swift */, + ); + path = Containers; + sourceTree = ""; + }; + 7DEBEC242C5AE43C00A1BF15 /* Future */ = { + isa = PBXGroup; + children = ( + 7DEBEC1B2C5AE43C00A1BF15 /* Containers */, + 7DEBEC1C2C5AE43C00A1BF15 /* Box.swift */, + 7D380E062C6BFB9500AD8F58 /* LifetimeOverride.swift */, + 7DEBEC1D2C5AE43C00A1BF15 /* Cell.swift */, + 7DEBEC1E2C5AE43C00A1BF15 /* CMakeLists.txt */, + 7DEBEC1F2C5AE43C00A1BF15 /* ContiguousStorage.swift */, + 7DEBEC202C5AE43C00A1BF15 /* Inout.swift */, + 7DEBEC212C5AE43C00A1BF15 /* RawSpan.swift */, + 7DEBEC222C5AE43C00A1BF15 /* Span.swift */, + 7DEBEC232C5AE43C00A1BF15 /* UnsafeBufferPointer+Additions.swift */, + ); + path = Future; + sourceTree = ""; + }; + BBD683952C7F857900B567A9 /* FutureTests */ = { + isa = PBXGroup; + children = ( + BBD6838E2C7F857900B567A9 /* BoxTests.swift */, + BBD6838F2C7F857900B567A9 /* CellTests.swift */, + BBD683902C7F857900B567A9 /* ContiguousStorageTests.swift */, + BBD683912C7F857900B567A9 /* DynamicArrayTests.swift */, + BBD683922C7F857900B567A9 /* Inout.swift */, + BBD683932C7F857900B567A9 /* RawSpanTests.swift */, + BBD683942C7F857900B567A9 /* SpanTests.swift */, + ); + path = FutureTests; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -1870,7 +1914,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1430; - LastUpgradeCheck = 1520; + LastUpgradeCheck = 1600; TargetAttributes = { 7DE91B2529CA6721004483EB = { CreatedOnToolsVersion = 14.3; @@ -1904,6 +1948,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7DEBEC252C5AE43C00A1BF15 /* CMakeLists.txt in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1924,12 +1969,11 @@ 7DE920F129CA70F4004483EB /* BitArray+Initializers.swift in Sources */, 7DE9214529CA70F4004483EB /* _HashNode+Structural symmetricDifference.swift in Sources */, 7DE9201029CA70F3004483EB /* OrderedDictionary+Deprecations.swift in Sources */, - 7DE9205929CA70F3004483EB /* Deque+Descriptions.swift in Sources */, - 7DEBDAF529CBEE5300ADC226 /* UnsafeRawPointer extensions.swift in Sources */, 7DE9212729CA70F4004483EB /* TreeSet+Equatable.swift in Sources */, 7DE9213429CA70F4004483EB /* TreeSet+ExpressibleByArrayLiteral.swift in Sources */, 7DE9209729CA70F4004483EB /* _RopePath.swift in Sources */, 7DE9216629CA70F4004483EB /* _HashNode+Storage.swift in Sources */, + 7D9514312C753C4A009C37DD /* Deque+Extras.swift in Sources */, 7DE920CE29CA70F4004483EB /* BitSet+CustomReflectable.swift in Sources */, 7DE9215029CA70F4004483EB /* _UnmanagedHashNode.swift in Sources */, 7DE920EF29CA70F4004483EB /* BitArray+Codable.swift in Sources */, @@ -1944,7 +1988,6 @@ 7DE9216229CA70F4004483EB /* _AncestorHashSlots.swift in Sources */, 7DE9216B29CA70F4004483EB /* TreeDictionary+Codable.swift in Sources */, 7DE9201A29CA70F3004483EB /* OrderedDictionary+Sendable.swift in Sources */, - 7DE9205D29CA70F4004483EB /* Deque._Storage.swift in Sources */, 7DE9203B29CA70F3004483EB /* OrderedSet+Partial SetAlgebra subtracting.swift in Sources */, 7DE9213A29CA70F4004483EB /* TreeSet.swift in Sources */, 7DE9213829CA70F4004483EB /* TreeSet+Filter.swift in Sources */, @@ -1953,6 +1996,7 @@ 7DE9213629CA70F4004483EB /* TreeSet+SetAlgebra basics.swift in Sources */, 7DE9202C29CA70F3004483EB /* OrderedSet+Initializers.swift in Sources */, 7DE9213229CA70F4004483EB /* TreeSet+SetAlgebra formSymmetricDifference.swift in Sources */, + 7D380E072C6BFB9B00AD8F58 /* LifetimeOverride.swift in Sources */, 7DE9203429CA70F3004483EB /* OrderedSet+Partial SetAlgebra formUnion.swift in Sources */, 7DE9214A29CA70F4004483EB /* _HashNode+Subtree Insertions.swift in Sources */, 7DE9215829CA70F4004483EB /* _HashNode+Structural union.swift in Sources */, @@ -1966,15 +2010,17 @@ 7DE9203129CA70F3004483EB /* OrderedSet+Partial MutableCollection.swift in Sources */, 7DE9209B29CA70F4004483EB /* Rope+Split.swift in Sources */, 7DE9206629CA70F4004483EB /* BigString+Debugging.swift in Sources */, - 7DE9205229CA70F3004483EB /* _UnsafeWrappedBuffer.swift in Sources */, + 7DE9205229CA70F3004483EB /* _UnsafeDequeSegments.swift in Sources */, 7DE9201129CA70F3004483EB /* OrderedDictionary+Initializers.swift in Sources */, 7DE9212B29CA70F4004483EB /* TreeSet+SetAlgebra subtracting.swift in Sources */, 7DE920F629CA70F4004483EB /* BitArray+Equatable.swift in Sources */, 7D9B859829E4F74400B291CD /* BitArray+Descriptions.swift in Sources */, + 7D95142C2C7539B9009C37DD /* Deque+Collection.swift in Sources */, 7DE9212329CA70F4004483EB /* TreeSet+SetAlgebra isEqualSet.swift in Sources */, 7DE9214629CA70F4004483EB /* _HashSlot.swift in Sources */, 7DEBDB0129CBEE5300ADC226 /* _UnsafeBitSet+Index.swift in Sources */, 7DE9204129CA70F3004483EB /* _UnsafeBitset.swift in Sources */, + 7D9514352C753FC1009C37DD /* Deque+Container.swift in Sources */, 7DE9208F29CA70F4004483EB /* RopeElement.swift in Sources */, 7DE9207229CA70F4004483EB /* BigString+Managing Breaks.swift in Sources */, 7DE9213F29CA70F4004483EB /* TreeSet+SetAlgebra isDisjoint.swift in Sources */, @@ -1995,6 +2041,7 @@ 7DE9209A29CA70F4004483EB /* Rope+Append.swift in Sources */, 7DE9203F29CA70F3004483EB /* OrderedSet+Partial SetAlgebra isDisjoint.swift in Sources */, 7DE9208629CA70F4004483EB /* BigString+LosslessStringConvertible.swift in Sources */, + 7D95142B2C753955009C37DD /* Deque+Equatable.swift in Sources */, 7DE9204329CA70F3004483EB /* _HashTable+Bucket.swift in Sources */, 7DE9216A29CA70F4004483EB /* TreeDictionary.swift in Sources */, 7DEBDB9D29CCE73D00ADC226 /* Collections.swift in Sources */, @@ -2005,7 +2052,6 @@ 7DE9217629CA70F4004483EB /* TreeDictionary+Initializers.swift in Sources */, 7DE9207E29CA70F4004483EB /* BigString+UTF16View.swift in Sources */, 7DEBDAFA29CBEE5300ADC226 /* RandomAccessCollection+Offsets.swift in Sources */, - 7DB0AE762B6E06B300602A20 /* Specialize.swift in Sources */, 7DE9216F29CA70F4004483EB /* TreeDictionary+CustomReflectable.swift in Sources */, 7DE920D729CA70F4004483EB /* BitSet+SetAlgebra isSuperset.swift in Sources */, 7DE920F429CA70F4004483EB /* BitArray+Hashable.swift in Sources */, @@ -2013,6 +2059,7 @@ 7DE9206A29CA70F4004483EB /* BigString+Chunk+Counts.swift in Sources */, 7DE9207529CA70F4004483EB /* BigString+Insert.swift in Sources */, 7DE9216129CA70F4004483EB /* _HashNode+Structural merge.swift in Sources */, + 7D9514332C753C5B009C37DD /* Deque+Testing.swift in Sources */, 7DE9202F29CA70F3004483EB /* OrderedSet+ExpressibleByArrayLiteral.swift in Sources */, 7DEBDAFC29CBEE5300ADC226 /* Debugging.swift in Sources */, 7DE9202B29CA70F3004483EB /* OrderedSet+Equatable.swift in Sources */, @@ -2026,8 +2073,11 @@ 7DE9215129CA70F4004483EB /* _HashNode+Subtree Modify.swift in Sources */, 7DE9204729CA70F3004483EB /* _HashTable+Testing.swift in Sources */, 7DE920E929CA70F4004483EB /* BitArray+Invariants.swift in Sources */, + 7D95142D2C753C1B009C37DD /* Deque+Codable.swift in Sources */, 7DE9215429CA70F4004483EB /* _HashNode+UnsafeHandle.swift in Sources */, + 7D9514302C753C45009C37DD /* Deque+ExpressibleByArrayLiteral.swift in Sources */, 7DE9209829CA70F4004483EB /* Rope+_Node.swift in Sources */, + 7DEBEC172C5AB45600A1BF15 /* _UnsafeDequeHandle.swift in Sources */, 7DE9204529CA70F3004483EB /* _HashTable+Constants.swift in Sources */, 7DE9214229CA70F4004483EB /* _HashStack.swift in Sources */, 7DE9206F29CA70F4004483EB /* BigString+Chunk+Breaks.swift in Sources */, @@ -2036,12 +2086,10 @@ 7DE920B529CA70F4004483EB /* _SortedCollection.swift in Sources */, 7DE9206929CA70F4004483EB /* BigString+Chunk+Indexing by UTF16.swift in Sources */, 7DE9202A29CA70F3004483EB /* OrderedSet+Partial SetAlgebra isSuperset.swift in Sources */, - 7DEBDAF229CBEE5300ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift in Sources */, 7DE9207D29CA70F4004483EB /* BigSubstring+UTF16View.swift in Sources */, 7DEBDAF829CBEE5300ADC226 /* UnsafeMutableBufferPointer+Extras.swift in Sources */, 7DE9217229CA70F4004483EB /* TreeDictionary+Sendable.swift in Sources */, 7DE9207729CA70F4004483EB /* Range+BigString.swift in Sources */, - 7DE9204B29CA70F3004483EB /* _DequeBuffer.swift in Sources */, 7DE920DF29CA70F4004483EB /* Slice+Utilities.swift in Sources */, 7DE9215729CA70F4004483EB /* _RawHashNode.swift in Sources */, 7DE9209529CA70F4004483EB /* RopeSummary.swift in Sources */, @@ -2067,13 +2115,13 @@ 7DE920EB29CA70F4004483EB /* BitArray+Fill.swift in Sources */, 7DE9201929CA70F3004483EB /* OrderedDictionary+Values.swift in Sources */, 7DE9213929CA70F4004483EB /* TreeSet+SetAlgebra Initializers.swift in Sources */, + 7D9514292C750E08009C37DD /* RigidDeque.swift in Sources */, 7DE9217729CA70F4004483EB /* TreeDictionary+Keys.swift in Sources */, 7DE920DA29CA70F4004483EB /* BitSet._UnsafeHandle.swift in Sources */, 7DE920D129CA70F4004483EB /* BitSet.Index.swift in Sources */, 7DE9207429CA70F4004483EB /* BigString+ReplaceSubrange.swift in Sources */, 7DE920DC29CA70F4004483EB /* BitSet+Invariants.swift in Sources */, 7DE9216729CA70F4004483EB /* TreeDictionary+Equatable.swift in Sources */, - 7DE9204C29CA70F3004483EB /* Deque+Collection.swift in Sources */, 7DE9201F29CA70F3004483EB /* OrderedSet+RandomAccessCollection.swift in Sources */, 7DE9202E29CA70F3004483EB /* OrderedSet+UnorderedView.swift in Sources */, 7DE9215F29CA70F4004483EB /* _UnsafePath.swift in Sources */, @@ -2101,14 +2149,24 @@ 7DE9203229CA70F3004483EB /* OrderedSet+Sendable.swift in Sources */, 7DE9206E29CA70F4004483EB /* BigString+Chunk+Splitting.swift in Sources */, 7DE920D829CA70F4004483EB /* BitSet+SetAlgebra subtracting.swift in Sources */, + 7DEBEC262C5AE43C00A1BF15 /* RigidArray.swift in Sources */, + 7DEBEC272C5AE43C00A1BF15 /* DynamicArray.swift in Sources */, + 7DEBEC282C5AE43C00A1BF15 /* Inout.swift in Sources */, + 7DEBEC292C5AE43C00A1BF15 /* Container.swift in Sources */, + 7DEBEC2A2C5AE43C00A1BF15 /* Box.swift in Sources */, + 7DEBEC2B2C5AE43C00A1BF15 /* UnsafeBufferPointer+Additions.swift in Sources */, + 7DEBEC2C2C5AE43C00A1BF15 /* RawSpan.swift in Sources */, + 7DEBEC2D2C5AE43C00A1BF15 /* Cell.swift in Sources */, + 7DEBEC2E2C5AE43C00A1BF15 /* Span.swift in Sources */, + 7DEBEC2F2C5AE43C00A1BF15 /* ContiguousStorage.swift in Sources */, 7DE9209E29CA70F4004483EB /* Rope+ForEachWhile.swift in Sources */, 7DE9217B29CA70F4004483EB /* Heap+Invariants.swift in Sources */, 7DE9212F29CA70F4004483EB /* TreeSet+SetAlgebra formIntersection.swift in Sources */, 7DE920A329CA70F4004483EB /* Rope+Index.swift in Sources */, 7DE9207329CA70F4004483EB /* BigString+RemoveSubrange.swift in Sources */, - 7DE9205829CA70F3004483EB /* Deque._UnsafeHandle.swift in Sources */, 7DE9213529CA70F4004483EB /* TreeSet+SetAlgebra symmetricDifference.swift in Sources */, 7DE920D429CA70F4004483EB /* BitSet+SetAlgebra union.swift in Sources */, + 7D9514322C753C55009C37DD /* Deque+Hashable.swift in Sources */, 7DE920C329CA70F4004483EB /* BitSet+SetAlgebra formUnion.swift in Sources */, 7DE9217429CA70F4004483EB /* TreeDictionary+Collection.swift in Sources */, 7DE9214729CA70F4004483EB /* _HashNode+Primitive Replacement.swift in Sources */, @@ -2134,6 +2192,7 @@ 7DE9212929CA70F4004483EB /* TreeSet+SetAlgebra formUnion.swift in Sources */, 7DE920C129CA70F4004483EB /* BitSet+SetAlgebra subtract.swift in Sources */, 7DE9201B29CA70F3004483EB /* OrderedDictionary+Descriptions.swift in Sources */, + 7D95142A2C753862009C37DD /* Deque.swift in Sources */, 7DE9213C29CA70F4004483EB /* TreeSet+Codable.swift in Sources */, 7DE9207129CA70F4004483EB /* BigString+Split.swift in Sources */, 7DE9214F29CA70F4004483EB /* _Bucket.swift in Sources */, @@ -2146,14 +2205,15 @@ 7DE9207A29CA70F4004483EB /* BigString+UTF8View.swift in Sources */, 7DE9201329CA70F3004483EB /* OrderedDictionary+CustomReflectable.swift in Sources */, 7DE9201C29CA70F3004483EB /* OrderedDictionary+Partial MutableCollection.swift in Sources */, + 7D9514342C753C63009C37DD /* DynamicDeque.swift in Sources */, + 7D380E0D2C6D565300AD8F58 /* Shared.swift in Sources */, 7DE9202729CA70F3004483EB /* OrderedSet+Insertions.swift in Sources */, - 7DE9205B29CA70F3004483EB /* Deque+ExpressibleByArrayLiteral.swift in Sources */, 7DE9208729CA70F4004483EB /* BigString+Sequence.swift in Sources */, 7DE9202229CA70F3004483EB /* OrderedSet+Diffing.swift in Sources */, 7DE920CA29CA70F4004483EB /* BitSet+CustomStringConvertible.swift in Sources */, + 7D380E052C6ABF6800AD8F58 /* NewArray.swift in Sources */, 7DE9212E29CA70F4004483EB /* TreeSet+SetAlgebra isStrictSuperset.swift in Sources */, 7DE920A829CA70F4004483EB /* String.Index+ABI.swift in Sources */, - 7DE9204F29CA70F3004483EB /* Deque.swift in Sources */, 7DE9214129CA70F4004483EB /* _HashNode+Initializers.swift in Sources */, 7D9B859929E4F74400B291CD /* BitArray+LosslessStringConvertible.swift in Sources */, 7DE9202D29CA70F3004483EB /* OrderedSet.swift in Sources */, @@ -2161,15 +2221,12 @@ 7DE9216329CA70F4004483EB /* _HashLevel.swift in Sources */, 7D9B859729E4F74400B291CD /* BitArray+Shifts.swift in Sources */, 7DE9216C29CA70F4004483EB /* TreeDictionary+Descriptions.swift in Sources */, - 7DE9205429CA70F3004483EB /* Deque+Sendable.swift in Sources */, 7DE920A229CA70F4004483EB /* Rope+RemoveSubrange.swift in Sources */, + 7D95142F2C753C34009C37DD /* Deque+Descriptions.swift in Sources */, 7DE920C729CA70F4004483EB /* BitSet+ExpressibleByArrayLiteral.swift in Sources */, 7DE9214929CA70F4004483EB /* _HashNode+Structural isEqualSet.swift in Sources */, 7DE9206729CA70F4004483EB /* BigString+Builder.swift in Sources */, 7DE9208B29CA70F4004483EB /* RopeMetric.swift in Sources */, - 7DE9205A29CA70F3004483EB /* Deque+Extras.swift in Sources */, - 7DEBDAF429CBEE5300ADC226 /* UnsafeMutablePointer+SE-0370.swift in Sources */, - 7DE9205129CA70F3004483EB /* Deque+Hashable.swift in Sources */, 7DE9209929CA70F4004483EB /* Rope+Extract.swift in Sources */, 7DE9202629CA70F3004483EB /* OrderedSet+Partial SetAlgebra isStrictSubset.swift in Sources */, 7DE920E729CA70F4004483EB /* BitArray+CustomReflectable.swift in Sources */, @@ -2178,14 +2235,12 @@ 7DEBDB0229CBEE5300ADC226 /* _UnsafeBitSet.swift in Sources */, 7DE9214329CA70F4004483EB /* _HashNode+Subtree Removals.swift in Sources */, 7DE9207929CA70F4004483EB /* BigString+UnicodeScalarView.swift in Sources */, - 7DE9205329CA70F3004483EB /* _DequeBufferHeader.swift in Sources */, 7DE9202429CA70F3004483EB /* OrderedSet+Invariants.swift in Sources */, 7DE920A729CA70F4004483EB /* _CharacterRecognizer.swift in Sources */, 7DE920A429CA70F4004483EB /* Rope+Sequence.swift in Sources */, 7DE9201829CA70F3004483EB /* OrderedDictionary+Invariants.swift in Sources */, 7DE920CD29CA70F4004483EB /* BitSet+CustomDebugStringConvertible.swift in Sources */, 7DE9212529CA70F4004483EB /* TreeSet+SetAlgebra isStrictSubset.swift in Sources */, - 7DE9205C29CA70F3004483EB /* Deque+Equatable.swift in Sources */, 7DE9209229CA70F4004483EB /* Rope+_Storage.swift in Sources */, 7DE9203929CA70F3004483EB /* OrderedSet+Partial RangeReplaceableCollection.swift in Sources */, 7DE9206329CA70F4004483EB /* BigString+Contents.swift in Sources */, @@ -2193,10 +2248,10 @@ 7DE920CC29CA70F4004483EB /* BitSet.Counted.swift in Sources */, 7DE9207F29CA70F4004483EB /* BigSubstring+UTF8View.swift in Sources */, 7DE920A129CA70F4004483EB /* Rope+Remove.swift in Sources */, + 7D95142E2C753C2E009C37DD /* Deque+CustomReflectable.swift in Sources */, 7DE9215929CA70F4004483EB /* _HashNode+Structural isSubset.swift in Sources */, 7DE920C629CA70F4004483EB /* BitSet+SetAlgebra formSymmetricDifference.swift in Sources */, 7DE9202129CA70F3004483EB /* OrderedSet+Partial SetAlgebra isEqualSet.swift in Sources */, - 7DE9205529CA70F3004483EB /* Deque+Codable.swift in Sources */, 7DE9206229CA70F4004483EB /* BigString+Iterators.swift in Sources */, 7DE9206029CA70F4004483EB /* BigString+Summary.swift in Sources */, 7DE9203829CA70F3004483EB /* OrderedSet+Partial SetAlgebra formIntersection.swift in Sources */, @@ -2235,7 +2290,6 @@ 7DE9206C29CA70F4004483EB /* BigString+Chunk.swift in Sources */, 7DE920F329CA70F4004483EB /* BitArray+Copy.swift in Sources */, 7DE9214029CA70F4004483EB /* _Hash.swift in Sources */, - 7DE9205629CA70F3004483EB /* Deque+Testing.swift in Sources */, 7DEBDB0829CBEE5300ADC226 /* UInt+reversed.swift in Sources */, 7DE9209329CA70F4004483EB /* Rope+Invariants.swift in Sources */, 7DE920EE29CA70F4004483EB /* BitArray+ExpressibleByArrayLiteral.swift in Sources */, @@ -2243,7 +2297,6 @@ 7DE9214C29CA70F4004483EB /* _HashNode+Primitive Insertions.swift in Sources */, 7DE920BE29CA70F4004483EB /* BitSet+SetAlgebra formIntersection.swift in Sources */, 7DE9217329CA70F4004483EB /* TreeDictionary+Merge.swift in Sources */, - 7DE9204E29CA70F3004483EB /* Deque+CustomReflectable.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2259,7 +2312,6 @@ 7DE9221429CA8576004483EB /* OrderedSet Diffing Tests.swift in Sources */, 7DEBDB1D29CBF6B200ADC226 /* RandomAccessCollection+Offsets.swift in Sources */, 7DEBDB8529CCE44A00ADC226 /* AllOnesRandomNumberGenerator.swift in Sources */, - 7DEBDB1429CBF68D00ADC226 /* UnsafeMutableBufferPointer+SE-0370.swift in Sources */, 7DE921FD29CA8576004483EB /* CombinatoricsChecks.swift in Sources */, 7DEBDB8B29CCE44A00ADC226 /* _CollectionState.swift in Sources */, 7DE9220229CA8576004483EB /* TreeDictionary Tests.swift in Sources */, @@ -2288,12 +2340,10 @@ 7DEBDB7D29CCE44A00ADC226 /* MinimalRangeReplaceableRandomAccessCollection.swift in Sources */, 7DEBDB8E29CCE44A00ADC226 /* Integer Square Root.swift in Sources */, 7DE9221E29CA8576004483EB /* MutableCollectionTests.swift in Sources */, - 7DEBDB1229CBF68D00ADC226 /* UnsafeMutablePointer+SE-0370.swift in Sources */, 7DE9221529CA8576004483EB /* RandomAccessCollection+Extras.swift in Sources */, 7DEBDB7929CCE44A00ADC226 /* LifetimeTracked.swift in Sources */, 7DE921FC29CA8576004483EB /* IndexRangeCollectionTests.swift in Sources */, 7DE9221229CA8576004483EB /* OrderedDictionary+Elements Tests.swift in Sources */, - 7DB0AE772B6E06B300602A20 /* Specialize.swift in Sources */, 7DEBDB7629CCE44A00ADC226 /* MinimalIndex.swift in Sources */, 7DE9221329CA8576004483EB /* OrderedSetInternals.swift in Sources */, 7DE9220329CA8576004483EB /* Colliders.swift in Sources */, @@ -2325,12 +2375,18 @@ 7DEBDB1829CBF69F00ADC226 /* _UnsafeBitSet+_Word.swift in Sources */, 7DE921B929CA81DC004483EB /* _SortedCollection.swift in Sources */, 7DEBDB1629CBF69F00ADC226 /* _UnsafeBitSet+Index.swift in Sources */, - 7DEBDB1529CBF68D00ADC226 /* UnsafeRawPointer extensions.swift in Sources */, 7DEBDB7B29CCE44A00ADC226 /* IndexRangeCollection.swift in Sources */, 7DE9221929CA8576004483EB /* HeapTests.swift in Sources */, 7DEBDB1729CBF69F00ADC226 /* _UnsafeBitSet.swift in Sources */, 7DEBDB7E29CCE44A00ADC226 /* CheckComparable.swift in Sources */, 7DEBDB7C29CCE44A00ADC226 /* MinimalDecoder.swift in Sources */, + BBD683962C7F857900B567A9 /* SpanTests.swift in Sources */, + BBD683972C7F857900B567A9 /* Inout.swift in Sources */, + BBD683982C7F857900B567A9 /* BoxTests.swift in Sources */, + BBD683992C7F857900B567A9 /* CellTests.swift in Sources */, + BBD6839A2C7F857900B567A9 /* RawSpanTests.swift in Sources */, + BBD6839B2C7F857900B567A9 /* DynamicArrayTests.swift in Sources */, + BBD6839C2C7F857900B567A9 /* ContiguousStorageTests.swift in Sources */, 7DE9220829CA8576004483EB /* BitSetTests.swift in Sources */, 7DEBDB7329CCE44A00ADC226 /* _MinimalCollectionCore.swift in Sources */, 7DE921C829CA81DC004483EB /* _UniqueCollection.swift in Sources */, @@ -2388,7 +2444,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7DEBDB9729CCE4A600ADC226 /* CollectionsTests.xcconfig */; buildSettings = { - DEAD_CODE_STRIPPING = YES; }; name = Debug; }; @@ -2396,7 +2451,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7DEBDB9729CCE4A600ADC226 /* CollectionsTests.xcconfig */; buildSettings = { - DEAD_CODE_STRIPPING = YES; }; name = Release; }; diff --git a/Xcode/Collections.xcodeproj/xcshareddata/xcschemes/Collections.xcscheme b/Xcode/Collections.xcodeproj/xcshareddata/xcschemes/Collections.xcscheme index 3266c5aac..88230e523 100644 --- a/Xcode/Collections.xcodeproj/xcshareddata/xcschemes/Collections.xcscheme +++ b/Xcode/Collections.xcodeproj/xcshareddata/xcschemes/Collections.xcscheme @@ -1,6 +1,6 @@