diff --git a/Package.swift b/Package.swift index 8a1903e5d..945deb23a 100644 --- a/Package.swift +++ b/Package.swift @@ -12,7 +12,7 @@ import PackageDescription -#if false // FIXME: Disabled while we're debugging a runtime crash (rdar://150240032) +// FIXME: This can sometimes induce a runtime crash (rdar://150240032) let _traits: Set = [ .default( enabledTraits: [ @@ -35,7 +35,6 @@ let _traits: Set = [ types before they ship. """), ] -#endif // This package recognizes the conditional compilation flags listed below. // To use enable them, uncomment the corresponding lines or define them @@ -43,9 +42,9 @@ let _traits: Set = [ // // swift build -Xswiftc -DCOLLECTIONS_INTERNAL_CHECKS var defines: [SwiftSetting] = [ -// .define( -// "COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW", -// .when(traits: ["UnstableContainersPreview"])), + .define( + "COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW", + .when(traits: ["UnstableContainersPreview"])), .define( "COLLECTIONS_UNSTABLE_SORTED_COLLECTIONS", .when(traits: ["UnstableSortedCollections"])), @@ -104,8 +103,8 @@ let extraSettings: [SwiftSetting] = [ .enableExperimentalFeature("Lifetimes"), .enableExperimentalFeature("InoutLifetimeDependence"), .enableExperimentalFeature("SuppressedAssociatedTypes"), -// .enableExperimentalFeature("AddressableParameters"), -// .enableExperimentalFeature("AddressableTypes"), + .enableExperimentalFeature("AddressableParameters"), + .enableExperimentalFeature("AddressableTypes"), // Note: if you touch these, please make sure to also update the similar lists in // CMakeLists.txt and Xcode/Shared.xcconfig. @@ -359,6 +358,6 @@ let _targets: [Target] = targets.map { $0.toTarget() } let package = Package( name: "swift-collections", products: _products, - traits: [], //_traits, + traits: _traits, targets: _targets ) diff --git a/Sources/BasicContainers/RigidArray+Append.swift b/Sources/BasicContainers/RigidArray+Append.swift index f1c57ee43..3f045f742 100644 --- a/Sources/BasicContainers/RigidArray+Append.swift +++ b/Sources/BasicContainers/RigidArray+Append.swift @@ -274,9 +274,9 @@ extension RigidArray { #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW @inlinable internal mutating func _append< - Source: Container & ~Copyable & ~Escapable + Source: Iterable & ~Copyable & ~Escapable >( - copyingContainer newElements: borrowing Source + copyingIterable newElements: borrowing Source ) { let target = _freeSpace _count += newElements._copyContents(intoPrefixOf: target) @@ -296,11 +296,11 @@ extension RigidArray { @_alwaysEmitIntoClient @inline(__always) public mutating func append< - Source: Container & ~Copyable & ~Escapable + Source: Iterable & ~Copyable & ~Escapable >( copying newElements: borrowing Source ) { - _append(copyingContainer: newElements) + _append(copyingIterable: newElements) } #endif @@ -338,9 +338,9 @@ extension RigidArray { @_alwaysEmitIntoClient @inline(__always) public mutating func append< - Source: Container & Sequence + Source: Iterable & Sequence >(copying newElements: Source) { - _append(copyingContainer: newElements) + _append(copyingIterable: newElements) } #endif } diff --git a/Sources/BasicContainers/RigidArray+Initializers.swift b/Sources/BasicContainers/RigidArray+Initializers.swift index d9d8fc72e..3fd7f2e7e 100644 --- a/Sources/BasicContainers/RigidArray+Initializers.swift +++ b/Sources/BasicContainers/RigidArray+Initializers.swift @@ -98,52 +98,55 @@ extension RigidArray /*where Element: Copyable*/ { /// The container must not contain more than `capacity` elements. @_alwaysEmitIntoClient @inline(__always) - public init & ~Copyable & ~Escapable>( - capacity: Int? = nil, + public init & ~Copyable & ~Escapable>( + capacity: Int, copying contents: borrowing Source ) { - self.init(capacity: capacity ?? contents.count) + self.init(capacity: capacity) self.append(copying: contents) } + #endif - + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW /// Creates a new array with the specified capacity, holding a copy - /// of the contents of a given collection. + /// of the contents of a given container. /// /// - Parameters: /// - capacity: The storage capacity of the new array, or nil to allocate /// just enough capacity to store the contents. - /// - contents: The collection whose contents to copy into the new array. - /// The collection must not contain more than `capacity` elements. + /// - contents: The container whose contents to copy into the new array. + /// The container must not contain more than `capacity` elements. @_alwaysEmitIntoClient @inline(__always) - public init( - capacity: Int? = nil, - copying contents: some Collection + public init & Sequence>( + capacity: Int, + copying contents: Source ) { - self.init(capacity: capacity ?? contents.count) + self.init(capacity: capacity) self.append(copying: contents) } +#endif + + // FIXME: Add a version that's generic over `Container`, with an optional capacity -#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW /// Creates a new array with the specified capacity, holding a copy - /// of the contents of a given container. + /// of the contents of a given collection. /// /// - Parameters: /// - capacity: The storage capacity of the new array, or nil to allocate /// just enough capacity to store the contents. - /// - contents: The container whose contents to copy into the new array. - /// The container must not contain more than `capacity` elements. + /// - contents: The collection whose contents to copy into the new array. + /// The collection must not contain more than `capacity` elements. @_alwaysEmitIntoClient @inline(__always) - public init & Sequence>( + public init( capacity: Int? = nil, - copying contents: Source + copying contents: some Collection ) { self.init(capacity: capacity ?? contents.count) self.append(copying: contents) } -#endif } // FIXME: Add init(moving:), init(consuming:) diff --git a/Sources/BasicContainers/RigidArray+Insertions.swift b/Sources/BasicContainers/RigidArray+Insertions.swift index 669a4fc10..856aebd48 100644 --- a/Sources/BasicContainers/RigidArray+Insertions.swift +++ b/Sources/BasicContainers/RigidArray+Insertions.swift @@ -341,6 +341,7 @@ extension RigidArray { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: This needs a container with an exact count. @inlinable internal mutating func _insertContainer< C: Container & ~Copyable & ~Escapable @@ -359,6 +360,7 @@ extension RigidArray { } } } +#endif #endif @inlinable @@ -388,6 +390,7 @@ extension RigidArray { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: This needs a container with an exact count. /// Copies the elements of a container into this array at the specified /// position. /// @@ -417,6 +420,7 @@ extension RigidArray { _insertContainer( at: index, copying: newElements, newCount: newElements.count) } +#endif #endif /// Copies the elements of a collection into this array at the specified @@ -448,6 +452,7 @@ extension RigidArray { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: This needs a container with an exact count. /// Copies the elements of a container into this array at the specified /// position. /// @@ -478,6 +483,7 @@ extension RigidArray { at: index, copying: newElements, newCount: newElements.count) } #endif +#endif } #endif diff --git a/Sources/BasicContainers/RigidArray+Replacements.swift b/Sources/BasicContainers/RigidArray+Replacements.swift index 654cca7dc..387175925 100644 --- a/Sources/BasicContainers/RigidArray+Replacements.swift +++ b/Sources/BasicContainers/RigidArray+Replacements.swift @@ -382,6 +382,7 @@ extension RigidArray { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: This needs a container with an exact count. @inlinable internal mutating func _replaceSubrange< C: Container & ~Copyable & ~Escapable @@ -399,6 +400,7 @@ extension RigidArray { } } } +#endif #endif @inlinable @@ -429,6 +431,7 @@ extension RigidArray { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: This needs a container with an exact count. /// Replaces the specified subrange of elements by copying the elements of /// the given container. /// @@ -466,6 +469,7 @@ extension RigidArray { _replaceSubrange( subrange, copyingContainer: newElements, newCount: newElements.count) } +#endif #endif /// Replaces the specified subrange of elements by copying the elements of @@ -505,6 +509,7 @@ extension RigidArray { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false /// Replaces the specified subrange of elements by copying the elements of /// the given container. /// @@ -544,6 +549,7 @@ extension RigidArray { subrange, copyingContainer: newElements, newCount: newElements.count) } #endif +#endif } #endif diff --git a/Sources/BasicContainers/RigidArray.swift b/Sources/BasicContainers/RigidArray.swift index 6d78ed8c5..6c4ab14b6 100644 --- a/Sources/BasicContainers/RigidArray.swift +++ b/Sources/BasicContainers/RigidArray.swift @@ -286,13 +286,49 @@ extension RigidArray where Element: ~Copyable { #if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW @available(SwiftStdlib 5.0, *) -extension RigidArray: Container where Element: ~Copyable { - public typealias BorrowIterator = Span - +extension RigidArray: Iterable where Element: ~Copyable { + @frozen + public struct BorrowIterator: ~Copyable, ~Escapable, BorrowIteratorProtocol { + @usableFromInline + internal let _span: Span + + @usableFromInline + internal var _offset: Int + + @inlinable + @_lifetime(copy span) + internal init(_span span: Span, offset: Int) { + self._span = span + self._offset = offset + } + + @_lifetime(&self) + @_lifetime(self: copy self) + public mutating func nextSpan(maximumCount: Int) -> Span { + let c = Swift.min(maximumCount, _span.count - _offset) + let end = _offset &+ c + let result = _span.extracting(Range(uncheckedBounds: (_offset, end))) + _offset = end + return result + } + + @_lifetime(self: copy self) + public mutating func skip(by offset: Int) -> Int { + let c = Swift.min(offset, _span.count &- _offset) + _offset += offset + return c + } + } + + @inlinable + public var estimatedCount: EstimatedCount { + .exactly(count) + } + @_alwaysEmitIntoClient @inline(__always) - public func startBorrowIteration() -> Span { - self.span + public func startBorrowIteration() -> BorrowIterator { + .init(_span: self.span, offset: 0) } } #endif diff --git a/Sources/BasicContainers/UniqueArray+Append.swift b/Sources/BasicContainers/UniqueArray+Append.swift index 7cb3c56d2..bb1145973 100644 --- a/Sources/BasicContainers/UniqueArray+Append.swift +++ b/Sources/BasicContainers/UniqueArray+Append.swift @@ -231,11 +231,32 @@ extension UniqueArray { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW - /// Copies the elements of a container to the end of this array. + @inlinable + internal mutating func _append< + Source: Iterable & ~Copyable & ~Escapable + >( + copyingIterable newElements: borrowing Source + ) { + _ensureFreeCapacity(newElements.underestimatedCount) + var it = newElements.startBorrowIteration() + while true { + let span = it.nextSpan() + if span.isEmpty { break } + _ensureFreeCapacity(span.count) + _storage.append(copying: span) + } + } + // FIXME: Add _append(copyingContainer:), forwarding to the same method on RigidArray +#endif + +#if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + /// Copies the elements of an iterable 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, using - /// a geometric growth rate. + /// a geometric growth rate. If the input sequence does not provide a precise + /// estimate of its count, then the array's storage may need to be resized + /// more than once. /// /// - Parameters /// - newElements: A container whose contents to copy into the array. @@ -245,12 +266,11 @@ extension UniqueArray { @_alwaysEmitIntoClient @inline(__always) public mutating func append< - Source: Container & ~Copyable & ~Escapable + Source: Iterable & ~Copyable & ~Escapable >( copying newElements: borrowing Source ) { - _ensureFreeCapacity(newElements.count) - _storage._append(copyingContainer: newElements) + self._append(copyingIterable: newElements) } #endif @@ -259,7 +279,7 @@ extension UniqueArray { /// /// If the array does not have sufficient capacity to hold enough elements, /// then this reallocates the array's storage to extend its capacity, using - /// a geometric growth rate. If the input sequence does not provide a correct + /// a geometric growth rate. If the input sequence does not provide a precise /// estimate of its count, then the array's storage may need to be resized /// more than once. /// @@ -300,10 +320,9 @@ extension UniqueArray { @_alwaysEmitIntoClient @inline(__always) public mutating func append< - Source: Container & Sequence + Source: Iterable & Sequence >(copying newElements: Source) { - _ensureFreeCapacity(newElements.count) - _storage._append(copyingContainer: newElements) + self._append(copyingIterable: newElements) } #endif } diff --git a/Sources/BasicContainers/UniqueArray+Initializers.swift b/Sources/BasicContainers/UniqueArray+Initializers.swift index f0110a85f..ee731b617 100644 --- a/Sources/BasicContainers/UniqueArray+Initializers.swift +++ b/Sources/BasicContainers/UniqueArray+Initializers.swift @@ -70,16 +70,16 @@ extension UniqueArray where Element: ~Copyable { extension UniqueArray /*where Element: Copyable*/ { #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW /// Creates a new array with the specified initial capacity, holding a copy - /// of the contents of a given container. + /// of the contents of a given iterable. /// /// - Parameters: /// - capacity: The storage capacity of the new array, or nil to allocate /// just enough capacity to store the contents. - /// - contents: The container whose contents to copy into the new array. - /// The container must not contain more than `capacity` elements. + /// - contents: An iterable whose contents to copy into the new array. + /// The iterable must not contain more than `capacity` elements. @_alwaysEmitIntoClient @inline(__always) - public init & ~Copyable & ~Escapable>( + public init & ~Copyable & ~Escapable>( capacity: Int? = nil, copying contents: borrowing Source ) { @@ -115,7 +115,7 @@ extension UniqueArray /*where Element: Copyable*/ { /// - contents: The container whose contents to copy into the new array. @_alwaysEmitIntoClient @inline(__always) - public init & Sequence>( + public init & Sequence>( capacity: Int? = nil, copying contents: Source ) { diff --git a/Sources/BasicContainers/UniqueArray+Insertions.swift b/Sources/BasicContainers/UniqueArray+Insertions.swift index 4e9fbf27f..8180b00c4 100644 --- a/Sources/BasicContainers/UniqueArray+Insertions.swift +++ b/Sources/BasicContainers/UniqueArray+Insertions.swift @@ -301,6 +301,7 @@ extension UniqueArray { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: This needs a container with an exact count. /// Copies the elements of a container into this array at the specified /// position. /// @@ -334,6 +335,7 @@ extension UniqueArray { _ensureFreeCapacity(c) _storage._insertContainer(at: index, copying: newElements, newCount: c) } +#endif #endif /// Copies the elements of a collection into this array at the specified @@ -368,6 +370,7 @@ extension UniqueArray { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: This needs a container with an exact count. /// Copies the elements of a container into this array at the specified /// position. /// @@ -402,6 +405,7 @@ extension UniqueArray { _storage._insertContainer(at: index, copying: newElements, newCount: c) } #endif +#endif } #endif diff --git a/Sources/BasicContainers/UniqueArray+Replacements.swift b/Sources/BasicContainers/UniqueArray+Replacements.swift index b52b020df..f2b85f0ed 100644 --- a/Sources/BasicContainers/UniqueArray+Replacements.swift +++ b/Sources/BasicContainers/UniqueArray+Replacements.swift @@ -378,6 +378,7 @@ extension UniqueArray { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: This needs a container with an exact count. /// Replaces the specified subrange of elements by copying the elements of /// the given container. /// @@ -420,6 +421,7 @@ extension UniqueArray { _storage._replaceSubrange( subrange, copyingContainer: newElements, newCount: c) } +#endif #endif /// Replaces the specified subrange of elements by copying the elements of @@ -464,6 +466,7 @@ extension UniqueArray { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: This needs a container with an exact count. /// Replaces the specified subrange of elements by copying the elements of /// the given container. /// @@ -507,5 +510,6 @@ extension UniqueArray { subrange, copyingContainer: newElements, newCount: c) } #endif +#endif } #endif diff --git a/Sources/BasicContainers/UniqueArray.swift b/Sources/BasicContainers/UniqueArray.swift index 8bf93ab11..0c3122ad9 100644 --- a/Sources/BasicContainers/UniqueArray.swift +++ b/Sources/BasicContainers/UniqueArray.swift @@ -177,12 +177,16 @@ extension UniqueArray where Element: ~Copyable { #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW @available(SwiftStdlib 5.0, *) -extension UniqueArray: Container where Element: ~Copyable { +extension UniqueArray: Iterable where Element: ~Copyable { public typealias BorrowIterator = RigidArray.BorrowIterator + public var estimatedCount: ContainersPreview.EstimatedCount { + .exactly(count) + } + @_alwaysEmitIntoClient @inline(__always) - public func startBorrowIteration() -> Span { + public func startBorrowIteration() -> BorrowIterator { self._storage.startBorrowIteration() } } diff --git a/Sources/ContainersPreview/BorrowIteratorProtocol.swift b/Sources/ContainersPreview/BorrowIteratorProtocol.swift index f34525122..b13a01478 100644 --- a/Sources/ContainersPreview/BorrowIteratorProtocol.swift +++ b/Sources/ContainersPreview/BorrowIteratorProtocol.swift @@ -12,17 +12,31 @@ #if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW @available(SwiftStdlib 5.0, *) public protocol BorrowIteratorProtocol: ~Copyable, ~Escapable { - associatedtype Element: ~Copyable /*& ~Escapable*/ + associatedtype Element: ~Copyable - /// Advance the iterator to the next storage chunk, returning a span over it. + // FIXME: This ought to be a core requirement, but `Ref` is not a thing yet. +// @_lifetime(&self) +// @_lifetime(self: copy self) +// mutating func next() -> Ref? + + /// Advance the iterator, returning an ephemeral span over the elements + /// that are ready to be visited. + /// + /// If the underlying iterable is a container type, then the returned span + /// typically directly addresses one of its storage buffers. On the other + /// hand, if the underlying iterable materializes its elements on demand, + /// then the returned span addresses some temporary buffer associated with + /// the iterator itself. Consequently, the returned span is tied to this + /// particular invocation of `nextSpan`, and it cannot survive until the next + /// invocation of it. /// - /// If the iterator has not yet reached the end of the underlying container, - /// then this method returns a non-empty span over the container's storage - /// that begins with the element at the iterator's current position and - /// extends to the end of the contiguous storage chunk that contains that - /// item, but at most `maximumCount` items. On return, the iterator's current - /// position is updated to the slot following the last item in the returned - /// span. + /// If the iterator has not yet reached the end of the underlying iterable, + /// then this method returns a non-empty span of at most `maximumCount` + /// elements, and updates the iterator's current position to the element + /// following the last item in the returned span (or the end, if there is + /// none). The `maximumCount` argument allows callers to avoid getting more + /// items that they are able to process in one go, simplifying usage, and + /// avoiding materializing more elements than needed. /// /// If the iterator's current position is at the end of the container, then /// this method returns an empty span without updating the position. @@ -39,30 +53,50 @@ public protocol BorrowIteratorProtocol: ~Copyable, ~Escapable { /// } /// /// 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, for example to repeat their contents. + /// Iterators that materialize elements on demand typically reuse the same + /// buffer over and over again; and even some proper containers may link to a + /// single storage chunk (or parts of a storage chunk) multiple times, for + /// example to repeat their contents. /// /// Note: Repeatedly iterating over the same container is expected to return /// the same items (collected in similarly sized span instances), but the - /// returned spans are not guaranteed to be identical. (For example, this is - /// the case with containers that can store contents within their direct - /// representation. Such containers may not always have a unique address in - /// memory, and so the locations of the spans exposed by this method may vary - /// between different borrows of the same container.) + /// returned spans are not guaranteed to be identical. For example, this is + /// the case with containers that store some of their contents within their + /// direct representation. Such containers may not always have a unique + /// address in memory, and so the locations of the spans exposed by this + /// method may vary between different borrows of the same container.) + @_lifetime(&self) + @_lifetime(self: copy self) + mutating func nextSpan(maximumCount: Int) -> Span + + /// Advance the position of this iterator by the specified offset, or until + /// the end of the underlying iterable. /// - /// - Parameter maximumCount: The maximum count of items the caller is ready - /// to process, or nil if the caller is prepared to accept an arbitrarily - /// large span. If non-nil, the maximum must be greater than zero. - /// - Returns: A span over a piece of contiguous storage in the underlying - /// container. It the iterator is at the end of the container, then - /// this returns an empty span. Otherwise the result will contain at least - /// one element. - @_lifetime(copy self) + /// Returns the number of items that were skipped. If this is less than + /// `maximumOffset`, then the iterable did not have enough elements left to + /// skip the requested number of items. In this case, the iterator's current + /// position is set to the end of the iterable. + /// + /// `maximumOffset` must be nonnegative, unless this is a bidirectional + /// or random-access iterator. @_lifetime(self: copy self) - mutating func nextSpan(maximumCount: Int?) -> Span + mutating func skip(by maximumOffset: Int) -> Int + // FIXME: Add BidirectionalBorrowIteratorProtocol and RandomAccessBorrowIteratorProtocol. + // BidirectionalBorrowIteratorProtocol would need to have a `previousSpan` + // method, which considerably complicates implementation. + // Perhaps these would be better left to as variants of protocol Container, + // which do not need a separate iterator concept. +} + +@available(SwiftStdlib 5.0, *) +extension BorrowIteratorProtocol where Self: ~Copyable & ~Escapable { + @_lifetime(&self) @_lifetime(self: copy self) - mutating func skip(by offset: Int) -> Int + @_transparent + public mutating func nextSpan() -> Span { + nextSpan(maximumCount: Int.max) + } } @available(SwiftStdlib 5.0, *) @@ -82,24 +116,60 @@ extension BorrowIteratorProtocol where Self: ~Copyable & ~Escapable { @available(SwiftStdlib 5.0, *) extension BorrowIteratorProtocol where Self: ~Copyable & ~Escapable { - @_lifetime(copy self) +#if false // FIXME: This doesn't work, but it should? + @_lifetime(&self) @_lifetime(self: copy self) - @_transparent - public mutating func nextSpan() -> Span { - nextSpan(maximumCount: nil) + public mutating func next() -> Ref? { + let span = nextSpan(maximumCount: 1) + guard !span.isEmpty else { return nil } + return Ref(_borrowing: span[unchecked: 0]) } +#endif + + // @_lifetime(&self) + // @_lifetime(self: copy self) + // public mutating func nextSpan(maximumCount: Int) -> Span { + // guard let ref = next() else { return Span() } + // return ref.span // FIXME + // } } @available(SwiftStdlib 5.0, *) extension Span: BorrowIteratorProtocol where Element: ~Copyable { @_lifetime(copy self) @_lifetime(self: copy self) - public mutating func nextSpan(maximumCount: Int?) -> Span { - let c = maximumCount ?? self.count - let result = self.extracting(first: c) - self = self.extracting(droppingFirst: c) + @inlinable + public mutating func nextSpan(maximumCount: Int) -> Span { + let result = self.extracting(first: maximumCount) + self = self.extracting(droppingFirst: maximumCount) return result } } +// FIXME: Decide if we want UnsafeBufferPointer types to be borrow iterators + +#if false // FIXME: Implement this +@available(SwiftStdlib 5.0, *) +extension MutableSpan: BorrowIteratorProtocol where Element: ~Copyable { + @_lifetime(copy self) + @_lifetime(self: copy self) + @inlinable + public mutating func nextSpan(maximumCount: Int) -> Span { + //??? + } +} +#endif + +#if false // FIXME: Implement this +@available(SwiftStdlib 5.0, *) +extension OutputSpan: BorrowIteratorProtocol where Element: ~Copyable { + @_lifetime(copy self) + @_lifetime(self: copy self) + @inlinable + public mutating func nextSpan(maximumCount: Int) -> Span { + //??? + } +} +#endif + #endif diff --git a/Sources/ContainersPreview/Box.swift b/Sources/ContainersPreview/Box.swift index f486879b5..64f376933 100644 --- a/Sources/ContainersPreview/Box.swift +++ b/Sources/ContainersPreview/Box.swift @@ -103,6 +103,7 @@ extension Box where T: ~Copyable { #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW /// Return a borrowing reference to the contents of this box. + @available(SwiftStdlib 5.0, *) @_alwaysEmitIntoClient @_transparent @_lifetime(borrow self) diff --git a/Sources/ContainersPreview/Container+Utilities.swift b/Sources/ContainersPreview/Container+Utilities.swift deleted file mode 100644 index 407594ad1..000000000 --- a/Sources/ContainersPreview/Container+Utilities.swift +++ /dev/null @@ -1,135 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 - -#if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW - -@available(SwiftStdlib 5.0, *) -extension Container where Self: ~Copyable & ~Escapable, Element: Copyable { - @inlinable - package func _copyContents( - intoPrefixOf buffer: UnsafeMutableBufferPointer - ) -> Int { - var target = buffer - var it = self.startBorrowIteration() - while target.count != 0 { - let span = it.nextSpan(maximumCount: target.count) - if span.isEmpty { - return buffer.count - target.count - } - target._initializeAndDropPrefix(copying: span) - } - let test = it.nextSpan() - precondition(test.isEmpty, "Contents do not fit in target buffer") - return buffer.count - } -} - -@available(SwiftStdlib 5.0, *) -extension Container where Self: ~Copyable & ~Escapable { - @inlinable - @discardableResult - internal func _spanwiseZip< - Other: Container & ~Copyable & ~Escapable, - State: ~Copyable, E: Error - >( - state: inout State, - with other: borrowing Other, - by process: (inout State, Span, Span) throws(E) -> Bool - ) throws(E) -> Int { - var it1 = self.startBorrowIteration() - var it2 = other.startBorrowIteration() - var a = it1.nextSpan() - var b = it2.nextSpan() - var offset = 0 // Offset of the start of the current spans - loop: - while true { - if a.isEmpty { - a = it1.nextSpan() - } - if b.isEmpty { - b = it2.nextSpan() - } - if a.isEmpty || b.isEmpty { - return offset - } - - let c = Swift.min(a.count, b.count) - guard try process(&state, a.extracting(first: c), b.extracting(first: c)) else { - return offset - } - a = a.extracting(droppingFirst: c) - b = b.extracting(droppingFirst: c) - offset += c - } - } - - /// 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 longer of the input containers. - @inlinable - public func borrowingElementsEqual< - E: Error, - Other: Container & ~Copyable & ~Escapable - >( - _ other: borrowing Other, - by areEquivalent: (borrowing Element, borrowing Other.Element) throws(E) -> Bool - ) throws(E) -> Bool { - guard self.count == other.count else { return false } - var result = true - 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 - } -} - -@available(SwiftStdlib 5.0, *) -extension Container where Self: ~Copyable & ~Escapable, Element: Equatable { - @inlinable - public func borrowingElementsEqual< - Other: Container & ~Copyable & ~Escapable - >( - _ other: borrowing Other, - ) -> Bool { - self.borrowingElementsEqual(other, by: ==) - } -} - -#endif diff --git a/Sources/ContainersPreview/Container.swift b/Sources/ContainersPreview/Container.swift index a804e5aa3..3856fe76c 100644 --- a/Sources/ContainersPreview/Container.swift +++ b/Sources/ContainersPreview/Container.swift @@ -11,77 +11,69 @@ #if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // Unfortunately this does not work well with Iterable yet. + @available(SwiftStdlib 5.0, *) -public protocol Container: ~Copyable, ~Escapable { - associatedtype Element: ~Copyable /*& ~Escapable*/ - associatedtype BorrowIterator: BorrowIteratorProtocol & ~Copyable & ~Escapable +public struct ContainerIterator< + Base: Container & ~Copyable /*& ~Escapable*/ +>: ~Copyable, ~Escapable { + let _base: Ref + var _position: Base.Index - var isEmpty: Bool { get } - var count: Int { get } + @_lifetime(borrow base) + init(_borrowing base: borrowing @_addressable Base, from position: Base.Index) { + self._base = Ref(_borrowing: base) + self._position = position + } +} + +@available(SwiftStdlib 5.0, *) +extension ContainerIterator: BorrowIteratorProtocol where Base: ~Copyable { + public typealias Element = Base.Element + @_lifetime(&self) + public mutating func nextSpan(maximumCount: Int) -> Span { + let r = _base[].nextSpan(after: &self._position, maximumCount: maximumCount) + return r + } +} + +@available(SwiftStdlib 5.0, *) +public protocol Container: Iterable, ~Copyable, ~Escapable { + associatedtype Index + + var count: Int { get } + + var startIndex: Index { get } + var endIndex: Index { get } + func index(after index: Index) -> Index + func index(_ index: Index, offsetBy delta: Int) -> Index + // ... + +// subscript(index: Index) -> Element { borrow } + @_lifetime(borrow self) - borrowing func startBorrowIteration() -> BorrowIterator + func nextSpan(after index: inout Index, maximumCount: Int) -> Span } @available(SwiftStdlib 5.0, *) extension Container where Self: ~Copyable & ~Escapable { - /// Implementation demo of what borrowing for-in loops would need to expand into. - @inlinable - public func _borrowingForEach( - _ body: (borrowing Element) throws(E) -> Void - ) throws(E) -> Void { - var it = startBorrowIteration() - while true { - let span = it.nextSpan() - if span.isEmpty { break } - var i = 0 - while i < span.count { - try body(span[unchecked: i]) - i &+= 1 - } - } - } + @_transparent + public var estimatedCount: EstimatedCount { .exactly(count) } } @available(SwiftStdlib 5.0, *) -extension Container where Self: ~Copyable & ~Escapable { - @inlinable - public func borrowingReduce( - into initial: consuming Result, - _ update: (inout Result, borrowing Self.Element) throws(E) -> () - ) throws(E) -> Result { - var result = initial - try self._borrowingForEach { item throws(E) in - try update(&result, item) - } - return result +extension Container +where + Self: ~Copyable /*& ~Escapable*/, + BorrowIterator == ContainerIterator +{ + @_lifetime(borrow self) + public func startBorrowIteration() -> BorrowIterator { + ContainerIterator(_borrowing: self, from: self.startIndex) } +} - @inlinable - public func borrowingReduce( - _ initial: consuming Result, - _ next: (consuming Result, borrowing Self.Element) throws(E) -> Result - ) throws(E) -> Result { - var result = initial -#if false // FIXME: missing reinitialization of closure capture 'result' after consume - try self._borrowingForEach { item throws(E) in - result = try next(result, item) - } -#else - var it = startBorrowIteration() - while true { - let span = it.nextSpan() - if span.isEmpty { break } - var i = 0 - while i < span.count { - result = try next(result, span[unchecked: i]) - i &+= 1 - } - } #endif - return result - } - -} #endif diff --git a/Sources/ContainersPreview/FactoryIterator.swift b/Sources/ContainersPreview/FactoryIterator.swift new file mode 100644 index 000000000..beedad569 --- /dev/null +++ b/Sources/ContainersPreview/FactoryIterator.swift @@ -0,0 +1,62 @@ +//===----------------------------------------------------------------------===// +// +// 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 compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + +#if !COLLECTIONS_SINGLE_MODULE +import InternalCollectionsUtilities +#endif + +@available(SwiftStdlib 5.0, *) +public protocol FactoryIterator: ~Copyable, ~Escapable { + associatedtype Element: ~Copyable /* ~Escapable */ + + @_lifetime(target: copy target) + mutating func generate(into target: inout OutputSpan) +} + +//------------------------------------------------------------------------------ + +@available(SwiftStdlib 5.0, *) +public protocol ConsumingIterator: FactoryIterator, ~Copyable, ~Escapable { + @_lifetime(&self) + mutating func nextSpan(maximumCount: Int?) -> InputSpan +} + +@available(SwiftStdlib 5.0, *) +extension ConsumingIterator where Self: ~Copyable & ~Escapable { + @_lifetime(target: copy target) + mutating func generate(into target: inout OutputSpan) { + var remainder = target.count + while true { + var source = self.nextSpan(maximumCount: target.count) + remainder -= source.count + target._append(moving: &source) + } + } +} + +@available(SwiftStdlib 5.0, *) +extension OutputSpan where Element: ~Copyable { + @_lifetime(source: copy source) + mutating func _append(moving source: inout InputSpan) { + self.withUnsafeMutableBufferPointer { target, targetCount in + source.withUnsafeMutableBufferPointer { source, sourceCount in + precondition(sourceCount <= targetCount, "OutputSpan capacity overflow") + target + .extracting(targetCount ..< targetCount + sourceCount) + .moveInitializeAll(fromContentsOf: source._extracting(last: sourceCount)) + } + } + } +} + +#endif diff --git a/Sources/ContainersPreview/Iterable+Standard Conformances.swift b/Sources/ContainersPreview/Iterable+Standard Conformances.swift new file mode 100644 index 000000000..22bc7dbd1 --- /dev/null +++ b/Sources/ContainersPreview/Iterable+Standard Conformances.swift @@ -0,0 +1,98 @@ +//===----------------------------------------------------------------------===// +// +// 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 compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + +@available(SwiftStdlib 5.0, *) +extension Span: Iterable where Element: ~Copyable { + // FIXME: This simple definition cannot also be a backward (or bidirectional) + // iterator, nor a random-access iterator. If we want to go in that direction, + // we'll need to rather introduce a type more like `RigidArray.BorrowIterator`. + public typealias BorrowIterator = Self + + @inlinable + public var estimatedCount: EstimatedCount { + .exactly(count) + } + + @_lifetime(copy self) + @inlinable + public func startBorrowIteration() -> Span { + self + } +} + +@available(SwiftStdlib 5.0, *) +extension MutableSpan: Iterable where Element: ~Copyable { + public typealias BorrowIterator = Span.BorrowIterator + + @inlinable + public var estimatedCount: EstimatedCount { + .exactly(count) + } + + @_lifetime(borrow self) + @inlinable + public func startBorrowIteration() -> Span { + self.span + } +} + +@available(SwiftStdlib 5.0, *) +extension OutputSpan: Iterable where Element: ~Copyable { + public typealias BorrowIterator = Span.BorrowIterator + + @inlinable + public var estimatedCount: EstimatedCount { + .exactly(count) + } + + @_lifetime(borrow self) + @inlinable + public func startBorrowIteration() -> Span { + self.span + } +} + +@available(SwiftStdlib 5.0, *) +extension InputSpan: Iterable where Element: ~Copyable { + public typealias BorrowIterator = Span.BorrowIterator + + @inlinable + public var estimatedCount: EstimatedCount { + .exactly(count) + } + + @_lifetime(borrow self) + @inlinable + public func startBorrowIteration() -> Span { + self.span + } +} + +@available(SwiftStdlib 6.2, *) +extension Array: Iterable { + public typealias BorrowIterator = Span.BorrowIterator + + @inlinable + public var estimatedCount: EstimatedCount { + .exactly(count) + } + + @_lifetime(borrow self) + @inlinable + public func startBorrowIteration() -> Span { + self.span + } +} + + +#endif diff --git a/Sources/ContainersPreview/Iterable+Utilities.swift b/Sources/ContainersPreview/Iterable+Utilities.swift new file mode 100644 index 000000000..31a86d5ee --- /dev/null +++ b/Sources/ContainersPreview/Iterable+Utilities.swift @@ -0,0 +1,279 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +#if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + +@available(SwiftStdlib 5.0, *) +extension Iterable where Self: ~Copyable & ~Escapable, Element: Copyable { + @inlinable + package func _copyContents( + intoPrefixOf buffer: UnsafeMutableBufferPointer + ) -> Int { + var target = buffer + var it = self.startBorrowIteration() + while target.count != 0 { + let span = it.nextSpan(maximumCount: target.count) + if span.isEmpty { + return buffer.count - target.count + } + target._initializeAndDropPrefix(copying: span) + } + let test = it.nextSpan() + precondition(test.isEmpty, "Contents do not fit in target buffer") + return buffer.count + } +} + +#if true +@available(SwiftStdlib 5.0, *) +extension Iterable where Self: ~Copyable & ~Escapable { + @inlinable + public func elementsEqual< + Other: Iterable & ~Copyable & ~Escapable + >( + _ other: borrowing Other, + ) -> Bool where Element: Equatable { + switch (self.estimatedCount, other.estimatedCount) { + case let (.exactly(a), .exactly(b)): + guard a == b else { return false } + default: + break + } + var it1 = self.startBorrowIteration() + var it2 = other.startBorrowIteration() + while true { + let a = it1.nextSpan() + var i = 0 + if a.isEmpty { + return it2.nextSpan().isEmpty + } + while i < a.count { + let b = it2.nextSpan(maximumCount: a.count) + if b.isEmpty { + return false + } + precondition(b.count <= a.count - i) + + var j = 0 + while j < b.count { + guard a[unchecked: i] == b[unchecked: j] else { return false } + i &+= 1 + j &+= 1 + } + } + } + } +} + +@available(SwiftStdlib 5.0, *) +extension Iterable where Self: ~Copyable & ~Escapable { + /// Returns a Boolean value indicating whether this Iterable and another + /// Iterable 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 Iterable to compare to this Iterable. + /// - areEquivalent: A predicate that returns `true` if its two arguments + /// are equivalent; otherwise, `false`. + /// - Returns: `true` if this Iterable and `other` contain equivalent items, + /// using `areEquivalent` as the equivalence test; otherwise, `false.` + /// + /// - Complexity: O(*m*), where *m* is the count of the longer of the input Iterables. + @inlinable + public func elementsEqual< + E: Error, + Other: Iterable & ~Copyable & ~Escapable + >( + _ other: borrowing Other, + by areEquivalent: (borrowing Element, borrowing Other.Element) throws(E) -> Bool + ) throws(E) -> Bool { + switch (self.estimatedCount, other.estimatedCount) { + case let (.exactly(a), .exactly(b)): + guard a == b else { return false } + default: + break + } + var it1 = self.startBorrowIteration() + var it2 = other.startBorrowIteration() + while true { + let a = it1.nextSpan() + var i = 0 + if a.isEmpty { + return it2.nextSpan().isEmpty + } + while i < a.count { + let b = it2.nextSpan(maximumCount: a.count) + if b.isEmpty { + return false + } + precondition(b.count <= a.count - i) + + var j = 0 + while j < b.count { + guard try areEquivalent(a[unchecked: i], b[unchecked: j]) else { + return false + } + i &+= 1 + j &+= 1 + } + } + } + } +} + +#else // rdar://150228920 +@available(SwiftStdlib 5.0, *) +extension Iterable where Self: ~Copyable & ~Escapable { + @inlinable + public func elementsEqual< + Other: Iterable & ~Copyable & ~Escapable + >( + _ other: borrowing Other, + ) -> Bool where Element: Equatable { + var it1 = self.startBorrowIteration() + var it2 = other.startBorrowIteration() + var a = Span() + var b = Span() + loop: + while true { + if a.isEmpty { + a = it1.nextSpan() + } + if b.isEmpty { + b = it2.nextSpan() + } + if a.isEmpty || b.isEmpty { + return a.isEmpty && b.isEmpty + } + + let c = Swift.min(a.count, b.count) + var i = 0 + while i < c { + guard a[unchecked: i] == b[unchecked: i] else { return false } + i &+= 1 + } + a = a.extracting(droppingFirst: c) + b = b.extracting(droppingFirst: c) + } + } +} + +@available(SwiftStdlib 5.0, *) +extension Iterable where Self: ~Copyable & ~Escapable { + @inlinable + @discardableResult + internal func _spanwiseZip< + Other: Iterable & ~Copyable & ~Escapable, + State: ~Copyable, E: Error + >( + state: inout State, + with other: borrowing Other, + by process: (inout State, Span, Span) throws(E) -> Bool + ) throws(E) -> Int { + var it1 = self.startBorrowIteration() + var it2 = other.startBorrowIteration() + var a = Span() + var b = Span() + var offset = 0 // Offset of the start of the current spans + loop: + while true { + if a.isEmpty { + a = it1.nextSpan() + } + if b.isEmpty { + b = it2.nextSpan() + } + if a.isEmpty || b.isEmpty { + return offset + } + + let c = Swift.min(a.count, b.count) + guard try process(&state, a.extracting(first: c), b.extracting(first: c)) else { + return offset + } + a = a.extracting(droppingFirst: c) + b = b.extracting(droppingFirst: c) + offset += c + } + } + + /// Returns a Boolean value indicating whether this Iterable and another + /// Iterable 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 Iterable to compare to this Iterable. + /// - areEquivalent: A predicate that returns `true` if its two arguments + /// are equivalent; otherwise, `false`. + /// - Returns: `true` if this Iterable and `other` contain equivalent items, + /// using `areEquivalent` as the equivalence test; otherwise, `false.` + /// + /// - Complexity: O(*m*), where *m* is the count of the longer of the input Iterables. + @inlinable + public func elementsEqual< + E: Error, + Other: Iterable & ~Copyable & ~Escapable + >( + _ other: borrowing Other, + by areEquivalent: (borrowing Element, borrowing Other.Element) throws(E) -> Bool + ) throws(E) -> Bool { + guard self.count == other.count else { return false } + var result = true + 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 + } +} + +@available(SwiftStdlib 5.0, *) +extension Iterable where Self: ~Copyable & ~Escapable, Element: Equatable { + @inlinable + public func elementsEqual< + Other: Iterable & ~Copyable & ~Escapable + >( + _ other: borrowing Other, + ) -> Bool { + self.elementsEqual(other, by: ==) + } +} +#endif + +#endif diff --git a/Sources/ContainersPreview/Iterable.swift b/Sources/ContainersPreview/Iterable.swift new file mode 100644 index 000000000..d60341394 --- /dev/null +++ b/Sources/ContainersPreview/Iterable.swift @@ -0,0 +1,139 @@ +//===----------------------------------------------------------------------===// +// +// 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 compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW + +public enum EstimatedCount { + case infinite + case exactly(Int) + case unknown +} + +@available(SwiftStdlib 5.0, *) +public protocol Iterable: ~Copyable, ~Escapable { + associatedtype Element: ~Copyable + associatedtype BorrowIterator: BorrowIteratorProtocol & ~Copyable & ~Escapable + + var isEmpty: Bool { get } + + var estimatedCount: EstimatedCount { get } + + @_lifetime(borrow self) + borrowing func startBorrowIteration() -> BorrowIterator + + func _customContainsEquatableElement( + _ element: borrowing Element + ) -> Bool? +} + +@available(SwiftStdlib 5.0, *) +extension Iterable where Self: ~Copyable & ~Escapable { + @inlinable + public var underestimatedCount: Int { + switch estimatedCount { + case .infinite: + Int.max + case .exactly(let c): + c + case .unknown: + 0 + } + } + + @inlinable + public func _customContainsEquatableElement(_ element: borrowing Element) -> Bool? { + nil + } +} + +@available(SwiftStdlib 5.0, *) +extension Iterable where Self: Sequence { + @inlinable + public func _customContainsEquatableElement(_ element: borrowing Element) -> Bool? { + nil + } +} + +@available(SwiftStdlib 5.0, *) +extension Iterable where Element: Copyable { + // FIXME: How do we expect this to work for ~Copyable elements? + public var first: Element? { + var it = startBorrowIteration() + let span = it.nextSpan(maximumCount: 1) + guard !span.isEmpty else { return nil } + return span[0] + } +} + + + +@available(SwiftStdlib 5.0, *) +extension Iterable where Self: ~Copyable & ~Escapable { + /// Implementation demo of what borrowing for-in loops would need to expand into. + @inlinable + public func _borrowingForEach( + _ body: (borrowing Element) throws(E) -> Void + ) throws(E) -> Void { + var it = startBorrowIteration() + while true { + let span = it.nextSpan() + if span.isEmpty { break } + var i = 0 + while i < span.count { + try body(span[unchecked: i]) + i &+= 1 + } + } + } +} + +@available(SwiftStdlib 5.0, *) +extension Iterable where Self: ~Copyable & ~Escapable { + @inlinable + public func borrowingReduce( + into initial: consuming Result, + _ update: (inout Result, borrowing Self.Element) throws(E) -> () + ) throws(E) -> Result { + var result = initial + try self._borrowingForEach { item throws(E) in + try update(&result, item) + } + return result + } + + @inlinable + public func borrowingReduce( + _ initial: consuming Result, + _ next: (consuming Result, borrowing Self.Element) throws(E) -> Result + ) throws(E) -> Result { + var result = initial +#if false // FIXME: missing reinitialization of closure capture 'result' after consume + try self._borrowingForEach { item throws(E) in + result = try next(result, item) + } +#else + var it = startBorrowIteration() + while true { + let span = it.nextSpan() + if span.isEmpty { break } + var i = 0 + while i < span.count { + result = try next(result, span[unchecked: i]) + i &+= 1 + } + } +#endif + return result + } + +} + +#endif diff --git a/Sources/ContainersPreview/Mut.swift b/Sources/ContainersPreview/Mut.swift index 983b63145..7451d8ab7 100644 --- a/Sources/ContainersPreview/Mut.swift +++ b/Sources/ContainersPreview/Mut.swift @@ -9,20 +9,18 @@ // //===----------------------------------------------------------------------===// -#if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if compiler(>=6.2) 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 a `Mut`, one must have exclusive access -/// to the instance of `T`. This is achieved through the 'inout' operator, '&'. +/// In order to get an instance of a `Mut`, one must have exclusive access +/// to the instance of `Target`. This is achieved through the 'inout' operator, '&'. @frozen @safe -public struct Mut: ~Copyable, ~Escapable { +public struct Mut: ~Copyable, ~Escapable { @usableFromInline - package let _pointer: UnsafeMutablePointer + package let _pointer: UnsafeMutablePointer /// Initializes an instance of 'Mut' extending the exclusive access of the /// passed instance. @@ -30,8 +28,8 @@ public struct Mut: ~Copyable, ~Escapable { /// - Parameter instance: The desired instance to get a mutable reference to. @_alwaysEmitIntoClient @_transparent - public init(_ instance: inout T) { - unsafe _pointer = UnsafeMutablePointer(Builtin.unprotectedAddressOf(&instance)) + public init(_ instance: inout Target) { + unsafe _pointer = UnsafeMutablePointer(Builtin.unprotectedAddressOf(&instance)) } /// Unsafely initializes an instance of 'Mut' using the given 'unsafeAddress' @@ -39,7 +37,7 @@ public struct Mut: ~Copyable, ~Escapable { /// argument. /// /// - Parameter unsafeAddress: The address to use to mutably reference an - /// instance of type 'T'. + /// instance of type 'Target'. /// - Parameter owner: The owning instance that this 'Mut' instance's /// lifetime is based on. @unsafe @@ -47,7 +45,7 @@ public struct Mut: ~Copyable, ~Escapable { @_transparent @_lifetime(&owner) public init( - unsafeAddress: UnsafeMutablePointer, + unsafeAddress: UnsafeMutablePointer, mutating owner: inout Owner ) { unsafe _pointer = unsafeAddress @@ -58,26 +56,26 @@ public struct Mut: ~Copyable, ~Escapable { /// lifetime is immortal. /// /// - Parameter unsafeImmortalAddress: The address to use to mutably reference - /// an immortal instance of type 'T'. + /// an immortal instance of type 'Target'. @_lifetime(immortal) @unsafe @_alwaysEmitIntoClient @_transparent public init( - unsafeImmortalAddress: UnsafeMutablePointer + unsafeImmortalAddress: UnsafeMutablePointer ) { unsafe _pointer = unsafeImmortalAddress } } -extension Mut where T: ~Copyable { +extension Mut where Target: ~Copyable { /// Dereferences the mutable reference allowing for in-place reads and writes /// to the underlying instance. @_alwaysEmitIntoClient - public subscript() -> T { + public subscript() -> Target { @_transparent unsafeAddress { - unsafe UnsafePointer(_pointer) + unsafe UnsafePointer(_pointer) } @_transparent @@ -87,4 +85,25 @@ extension Mut where T: ~Copyable { } } } + +extension Optional where Wrapped: ~Copyable /* FIXME: ~Escapable */ { + @_lifetime(&self) + public mutating func mutate() -> Mut? { + if self == nil { + return nil + } + let pointer = unsafe UnsafeMutablePointer( + Builtin.unprotectedAddressOf(&self)) + return unsafe Mut(unsafeAddress: pointer, mutating: &self) + } + + @_lifetime(&self) + @_alwaysEmitIntoClient + @_transparent + public mutating func insert(_ value: consuming Wrapped) -> Mut { + self = .some(value) + return mutate()._consumingUnsafelyUnwrap() + } + +} #endif diff --git a/Sources/ContainersPreview/Ref.swift b/Sources/ContainersPreview/Ref.swift index ab7edecce..66f6ec123 100644 --- a/Sources/ContainersPreview/Ref.swift +++ b/Sources/ContainersPreview/Ref.swift @@ -12,17 +12,25 @@ #if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW import Builtin +@available(SwiftStdlib 5.0, *) @frozen @safe -public struct Ref: Copyable, ~Escapable { +public struct Ref: Copyable, ~Escapable { @usableFromInline - package let _pointer: UnsafePointer + package let _pointer: UnsafePointer -#if compiler(>=6.2) && FIXME @_lifetime(borrow value) @_alwaysEmitIntoClient @_transparent - public init(_ value: borrowing @_addressable T) { + internal init(_borrowing value: borrowing @_addressable Target) { + unsafe _pointer = UnsafePointer(Builtin.unprotectedAddressOfBorrow(value)) + } + +#if compiler(>=6.3) // rdar://161844406 (https://github.com/swiftlang/swift/pull/84748) + @_lifetime(borrow value) + @_alwaysEmitIntoClient + @_transparent + public init(_ value: borrowing @_addressable Target) { unsafe _pointer = UnsafePointer(Builtin.unprotectedAddressOfBorrow(value)) } #endif @@ -31,7 +39,7 @@ public struct Ref: Copyable, ~Escapable { @_alwaysEmitIntoClient @_transparent public init( - unsafeAddress: UnsafePointer, + unsafeAddress: UnsafePointer, borrowing owner: borrowing Owner ) { unsafe _pointer = unsafeAddress @@ -41,18 +49,35 @@ public struct Ref: Copyable, ~Escapable { @_alwaysEmitIntoClient @_transparent public init( - unsafeAddress: UnsafePointer, + unsafeAddress: UnsafePointer, copying owner: borrowing Owner ) { unsafe _pointer = unsafeAddress } @_alwaysEmitIntoClient - public subscript() -> T { + public subscript() -> Target { @_transparent unsafeAddress { unsafe _pointer } } } + +extension Optional where Wrapped: ~Copyable /* FIXME: ~Escapable */ { + @available(SwiftStdlib 5.0, *) + @_lifetime(borrow self) + @_addressableSelf + public func borrow() -> Ref? { + if self == nil { + return nil + } + + let pointer = unsafe UnsafePointer( + Builtin.unprotectedAddressOfBorrow(self) + ) + + return unsafe Ref(unsafeAddress: pointer, borrowing: self) + } +} #endif diff --git a/Tests/BasicContainersTests/RigidArrayTests.swift b/Tests/BasicContainersTests/RigidArrayTests.swift index 622c2f143..3382fe70a 100644 --- a/Tests/BasicContainersTests/RigidArrayTests.swift +++ b/Tests/BasicContainersTests/RigidArrayTests.swift @@ -22,7 +22,7 @@ import BasicContainers #if !COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW /// Check if `left` and `right` contain equal elements in the same order. @available(SwiftStdlib 5.0, *) -public func expectContainerContents< +public func expectIteratorContents< Element: Equatable, C2: Collection, >( @@ -33,7 +33,7 @@ public func expectContainerContents< file: StaticString = #file, line: UInt = #line ) { - expectContainerContents( + expectIteratorContents( left.span, equalTo: right, message(), trapping: trapping, file: file, line: line) @@ -41,7 +41,7 @@ public func expectContainerContents< /// Check if `left` and `right` contain equal elements in the same order. @available(SwiftStdlib 5.0, *) -public func expectContainerContents< +public func expectIteratorContents< E1: ~Copyable, C2: Collection, >( @@ -53,7 +53,7 @@ public func expectContainerContents< file: StaticString = #file, line: UInt = #line ) { - expectContainerContents( + expectIteratorContents( left.span, equivalentTo: right, by: areEquivalent, @@ -69,9 +69,9 @@ class RigidArrayTests: CollectionTestCase { let items = tracker.rigidArray(layout: layout) let expected = (0 ..< layout.count).map { tracker.instance(for: $0) } expectEqual(tracker.instances, 2 * layout.count) - expectContainerContents(items, equalTo: expected) + expectIteratorContents(items, equalTo: expected) #if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW - checkContainer(items, expectedContents: expected) + checkIterable(items, expectedContents: expected) #endif } } @@ -115,7 +115,7 @@ class RigidArrayTests: CollectionTestCase { expectEqual(a.freeCapacity, layout.capacity - layout.count) expectEqual(a.isEmpty, layout.count == 0) expectEqual(a.isFull, layout.count == layout.capacity) - expectContainerContents( + expectIteratorContents( a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) @@ -160,6 +160,7 @@ class RigidArrayTests: CollectionTestCase { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: Update func test_init_copying_Container() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in withEvery("spanCounts", in: [ @@ -184,6 +185,7 @@ class RigidArrayTests: CollectionTestCase { } } } +#endif #endif func test_span() { @@ -286,7 +288,7 @@ class RigidArrayTests: CollectionTestCase { a.swapAt(i, layout.count - 1 - i) } let expected = (0 ..< layout.count).reversed() - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectIteratorContents(a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.count) } } @@ -390,7 +392,7 @@ class RigidArrayTests: CollectionTestCase { errorHandler: { error in expectTrue(error is TestError) } - expectContainerContents( + expectIteratorContents( a, equivalentTo: (0 ..< layout.count).map { -$0 }, by: { $0.payload == $1 }) @@ -415,7 +417,7 @@ class RigidArrayTests: CollectionTestCase { expectEqual(a.count, layout.count) expectEqual(a.capacity, newCapacity) expectEqual(tracker.instances, layout.count) - expectContainerContents( + expectIteratorContents( a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) } } @@ -439,7 +441,7 @@ class RigidArrayTests: CollectionTestCase { expectEqual(a.count, layout.count) expectEqual(a.capacity, Swift.max(layout.capacity, newCapacity)) expectEqual(tracker.instances, layout.count) - expectContainerContents( + expectIteratorContents( a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) } } @@ -454,8 +456,8 @@ class RigidArrayTests: CollectionTestCase { expectEqual(b.count, layout.count) expectEqual(b.capacity, layout.count) 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 }) + expectIteratorContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + expectIteratorContents(b, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) } } } @@ -472,8 +474,8 @@ class RigidArrayTests: CollectionTestCase { 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 }) + expectIteratorContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) + expectIteratorContents(b, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) } } } @@ -517,7 +519,7 @@ class RigidArrayTests: CollectionTestCase { expectEqual(tracker.instances, layout.count) a.removeLast(k) expectEqual(tracker.instances, layout.count - k) - expectContainerContents( + expectIteratorContents( a, equivalentTo: expected, by: { $0.payload == $1 }) } } @@ -534,7 +536,7 @@ class RigidArrayTests: CollectionTestCase { var a = tracker.rigidArray(layout: layout) let old = a.remove(at: i) expectEqual(old.payload, i) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectIteratorContents(a, equivalentTo: expected, by: { $0.payload == $1 }) } } } @@ -549,7 +551,7 @@ class RigidArrayTests: CollectionTestCase { var a = tracker.rigidArray(layout: layout) a.removeSubrange(range) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectIteratorContents(a, equivalentTo: expected, by: { $0.payload == $1 }) } } } @@ -564,7 +566,7 @@ class RigidArrayTests: CollectionTestCase { var a = tracker.rigidArray(layout: layout) a.removeAll(where: { $0.payload.isMultiple(of: 2) }) - expectContainerContents( + expectIteratorContents( a, equivalentTo: expected, by: { $0.payload == $1 }) } } @@ -581,7 +583,7 @@ class RigidArrayTests: CollectionTestCase { let item = a.popLast() expectEquivalent(item, expectedItem, by: { $0?.payload == $1 }) - expectContainerContents( + expectIteratorContents( a, equivalentTo: expected, by: { $0.payload == $1 }) } } @@ -594,7 +596,7 @@ class RigidArrayTests: CollectionTestCase { 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 }) + expectIteratorContents(a, equivalentTo: 0 ..< i + 1, by: { $0.payload == $1 }) } expectTrue(a.isFull) expectEqual(tracker.instances, layout.capacity) @@ -629,13 +631,14 @@ class RigidArrayTests: CollectionTestCase { using: { layout.count + $0 }) a.append(copying: b.span) expectTrue(a.isFull) - expectContainerContents(a, equivalentTo: 0 ..< layout.capacity, by: { $0.payload == $1 }) + expectIteratorContents(a, equivalentTo: 0 ..< layout.capacity, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.capacity) } } } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: Update 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 @@ -648,12 +651,13 @@ class RigidArrayTests: CollectionTestCase { spanCounts: [spanCount]) a.append(copying: b) expectTrue(a.isFull) - expectContainerContents(a, equivalentTo: 0 ..< layout.capacity, by: { $0.payload == $1 }) + expectIteratorContents(a, equivalentTo: 0 ..< layout.capacity, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.capacity) } } } } +#endif #endif func test_insert_at() { @@ -667,7 +671,7 @@ class RigidArrayTests: CollectionTestCase { var a = tracker.rigidArray(layout: layout) a.insert(tracker.instance(for: -1), at: i) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectIteratorContents(a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.count + 1) } } @@ -690,7 +694,7 @@ class RigidArrayTests: CollectionTestCase { var a = tracker.rigidArray(layout: layout) a.insert(copying: trackedAddition, at: i) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectIteratorContents(a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.capacity) } } @@ -715,7 +719,7 @@ class RigidArrayTests: CollectionTestCase { } a.insert(copying: rigidAddition.span, at: i) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectIteratorContents(a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.capacity) } } @@ -723,6 +727,7 @@ class RigidArrayTests: CollectionTestCase { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: Update func test_insert_copying_Container() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in withEvery("i", in: 0 ... layout.count) { i in @@ -743,13 +748,14 @@ class RigidArrayTests: CollectionTestCase { spanCounts: [Swift.max(spanCount, 1)]) a.insert(copying: rigidAddition, at: i) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectIteratorContents(a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.capacity) } } } } } +#endif #endif func test_replaceSubrange_Collection() { @@ -768,7 +774,7 @@ class RigidArrayTests: CollectionTestCase { var a = tracker.rigidArray(layout: layout) a.replaceSubrange(range, copying: trackedAddition) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectIteratorContents(a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.count - range.count + c) } } @@ -789,7 +795,7 @@ class RigidArrayTests: CollectionTestCase { let trackedAddition = RigidArray(copying: addition.map { tracker.instance(for: $0) }) a.replaceSubrange(range, copying: trackedAddition.span) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectIteratorContents(a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.count - range.count + c) } } @@ -798,6 +804,7 @@ class RigidArrayTests: CollectionTestCase { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: Update func test_replaceSubrange_Container() { withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in withEveryRange("range", in: 0 ..< layout.count) { range in @@ -814,7 +821,7 @@ class RigidArrayTests: CollectionTestCase { spanCounts: [spanCount]) a.replaceSubrange(range, copying: trackedAddition) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectIteratorContents(a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.count - range.count + c) } } @@ -823,5 +830,6 @@ class RigidArrayTests: CollectionTestCase { } } #endif +#endif } #endif diff --git a/Tests/BasicContainersTests/UniqueArrayTests.swift b/Tests/BasicContainersTests/UniqueArrayTests.swift index 3eaf74884..9836a7026 100644 --- a/Tests/BasicContainersTests/UniqueArrayTests.swift +++ b/Tests/BasicContainersTests/UniqueArrayTests.swift @@ -22,7 +22,7 @@ import BasicContainers #if !COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW /// Check if `left` and `right` contain equal elements in the same order. @available(SwiftStdlib 5.0, *) -public func expectContainerContents< +public func expectIteratorContents< Element: Equatable, C2: Collection, >( @@ -33,7 +33,7 @@ public func expectContainerContents< file: StaticString = #file, line: UInt = #line ) { - expectContainerContents( + expectIteratorContents( left.span, equalTo: right, message(), trapping: trapping, file: file, line: line) @@ -41,7 +41,7 @@ public func expectContainerContents< /// Check if `left` and `right` contain equal elements in the same order. @available(SwiftStdlib 5.0, *) -public func expectContainerContents< +public func expectIteratorContents< E1: ~Copyable, C2: Collection, >( @@ -53,7 +53,7 @@ public func expectContainerContents< file: StaticString = #file, line: UInt = #line ) { - expectContainerContents( + expectIteratorContents( left.span, equivalentTo: right, by: areEquivalent, @@ -70,9 +70,9 @@ class UniqueArrayTests: CollectionTestCase { let items = UniqueArray(consuming: tracker.rigidArray(layout: layout)) let expected = (0 ..< layout.count).map { tracker.instance(for: $0) } expectEqual(tracker.instances, 2 * layout.count) - expectContainerContents(items, equalTo: expected) + expectIteratorContents(items, equalTo: expected) #if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW - checkContainer(items, expectedContents: expected) + checkIterable(items, expectedContents: expected) #endif } } @@ -150,7 +150,7 @@ class UniqueArrayTests: CollectionTestCase { 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 }) + expectIteratorContents(a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) } } } @@ -189,6 +189,7 @@ class UniqueArrayTests: CollectionTestCase { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: Update func test_init_copying_Container() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in withEvery("spanCounts", in: [ @@ -214,6 +215,7 @@ class UniqueArrayTests: CollectionTestCase { } } } +#endif #endif func test_span() { @@ -316,7 +318,7 @@ class UniqueArrayTests: CollectionTestCase { a.swapAt(i, layout.count - 1 - i) } let expected = (0 ..< layout.count).reversed() - expectContainerContents( + expectIteratorContents( a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.count) } @@ -394,7 +396,7 @@ class UniqueArrayTests: CollectionTestCase { errorHandler: { error in expectTrue(error is TestError) } - expectContainerContents( + expectIteratorContents( a, equivalentTo: (0 ..< layout.count).map { -$0 }, by: { $0.payload == $1 }) @@ -419,7 +421,7 @@ class UniqueArrayTests: CollectionTestCase { expectEqual(a.count, layout.count) expectEqual(a.capacity, newCapacity) expectEqual(tracker.instances, layout.count) - expectContainerContents( + expectIteratorContents( a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) } } @@ -443,7 +445,7 @@ class UniqueArrayTests: CollectionTestCase { expectEqual(a.count, layout.count) expectEqual(a.capacity, Swift.max(layout.capacity, newCapacity)) expectEqual(tracker.instances, layout.count) - expectContainerContents( + expectIteratorContents( a, equivalentTo: 0 ..< layout.count, by: { $0.payload == $1 }) } } @@ -500,7 +502,7 @@ class UniqueArrayTests: CollectionTestCase { expectEqual(tracker.instances, layout.count) a.removeLast(k) expectEqual(tracker.instances, layout.count - k) - expectContainerContents( + expectIteratorContents( a, equivalentTo: expected, by: { $0.payload == $1 }) } } @@ -517,7 +519,7 @@ class UniqueArrayTests: CollectionTestCase { var a = tracker.uniqueArray(layout: layout) let old = a.remove(at: i) expectEqual(old.payload, i) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectIteratorContents(a, equivalentTo: expected, by: { $0.payload == $1 }) } } } @@ -532,7 +534,7 @@ class UniqueArrayTests: CollectionTestCase { var a = tracker.uniqueArray(layout: layout) a.removeSubrange(range) - expectContainerContents(a, equivalentTo: expected, by: { $0.payload == $1 }) + expectIteratorContents(a, equivalentTo: expected, by: { $0.payload == $1 }) } } } @@ -547,7 +549,7 @@ class UniqueArrayTests: CollectionTestCase { var a = tracker.uniqueArray(layout: layout) a.removeAll(where: { $0.payload.isMultiple(of: 2) }) - expectContainerContents( + expectIteratorContents( a, equivalentTo: expected, by: { $0.payload == $1 }) } } @@ -564,7 +566,7 @@ class UniqueArrayTests: CollectionTestCase { let item = a.popLast() expectEquivalent(item, expectedItem, by: { $0?.payload == $1 }) - expectContainerContents( + expectIteratorContents( a, equivalentTo: expected, by: { $0.payload == $1 }) } } @@ -634,7 +636,7 @@ class UniqueArrayTests: CollectionTestCase { 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 }) + expectIteratorContents(a, equivalentTo: 0 ..< i + 1, by: { $0.payload == $1 }) } expectEqual(tracker.instances, c) } @@ -651,7 +653,7 @@ class UniqueArrayTests: CollectionTestCase { elements: (layout.count ..< c).map { tracker.instance(for: $0) }, underestimatedCount: .half, isContiguous: isContiguous)) - expectContainerContents( + expectIteratorContents( a, equivalentTo: 0 ..< c, by: { $0.payload == $1}) expectEqual(tracker.instances, c) } @@ -671,7 +673,7 @@ class UniqueArrayTests: CollectionTestCase { } a.append(copying: b.span) let c = layout.count + additions - expectContainerContents( + expectIteratorContents( a, equivalentTo: 0 ..< c, by: { $0.payload == $1 }) expectEqual(tracker.instances, c) } @@ -680,6 +682,7 @@ class UniqueArrayTests: CollectionTestCase { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: Update func test_append_copying_Container() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in withEvery("additions", in: [0, 1, 10, 100]) { additions in @@ -695,7 +698,7 @@ class UniqueArrayTests: CollectionTestCase { contents: RigidArray(copying: addition), spanCounts: [spanCount]) a.append(copying: b) - expectContainerContents( + expectIteratorContents( a, equivalentTo: 0 ..< c, by: { $0.payload == $1 }) expectEqual(tracker.instances, c) } @@ -703,6 +706,7 @@ class UniqueArrayTests: CollectionTestCase { } } } +#endif #endif func test_insert_at() { @@ -715,7 +719,7 @@ class UniqueArrayTests: CollectionTestCase { var a = tracker.uniqueArray(layout: layout) a.insert(tracker.instance(for: -1), at: i) - expectContainerContents( + expectIteratorContents( a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.count + 1) } @@ -737,7 +741,7 @@ class UniqueArrayTests: CollectionTestCase { var a = tracker.uniqueArray(layout: layout) a.insert(copying: trackedAddition, at: i) - expectContainerContents( + expectIteratorContents( a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.count + c) } @@ -762,7 +766,7 @@ class UniqueArrayTests: CollectionTestCase { var a = tracker.uniqueArray(layout: layout) a.insert(copying: rigidAddition.span, at: i) - expectContainerContents( + expectIteratorContents( a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.count + c) } @@ -772,6 +776,7 @@ class UniqueArrayTests: CollectionTestCase { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: Update func test_insert_copying_Container() { withSomeArrayLayouts("layout", ofCapacities: [0, 10, 100]) { layout in withEvery("i", in: 0 ... layout.count) { i in @@ -793,7 +798,7 @@ class UniqueArrayTests: CollectionTestCase { spanCounts: [spanCount]) a.insert(copying: rigidAddition, at: i) - expectContainerContents( + expectIteratorContents( a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.count + c) } @@ -802,6 +807,7 @@ class UniqueArrayTests: CollectionTestCase { } } } +#endif #endif func test_replaceSubrange_copying_Collection() { @@ -817,7 +823,7 @@ class UniqueArrayTests: CollectionTestCase { let trackedAddition = addition.map { tracker.instance(for: $0) } a.replaceSubrange(range, copying: trackedAddition) - expectContainerContents( + expectIteratorContents( a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.count - range.count + c) } @@ -840,7 +846,7 @@ class UniqueArrayTests: CollectionTestCase { copying: addition.map { tracker.instance(for: $0) }) a.replaceSubrange(range, copying: trackedAddition.span) - expectContainerContents( + expectIteratorContents( a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.count - range.count + c) } @@ -850,6 +856,7 @@ class UniqueArrayTests: CollectionTestCase { } #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: Update func test_replaceSubrange_copying_Container() { withSomeArrayLayouts("layout", ofCapacities: [0, 5, 10]) { layout in withEveryRange("range", in: 0 ..< layout.count) { range in @@ -867,7 +874,7 @@ class UniqueArrayTests: CollectionTestCase { spanCounts: [spanCount]) a.replaceSubrange(range, copying: trackedAddition) - expectContainerContents( + expectIteratorContents( a, equivalentTo: expected, by: { $0.payload == $1 }) expectEqual(tracker.instances, layout.count - range.count + c) } @@ -877,5 +884,6 @@ class UniqueArrayTests: CollectionTestCase { } } #endif +#endif } #endif diff --git a/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift index e153de166..14f68c7d7 100644 --- a/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift +++ b/Tests/_CollectionsTestSupport/AssertionContexts/Assertions+Containers.swift @@ -19,7 +19,7 @@ import ContainersPreview #if compiler(>=6.2) /// Check if `left` and `right` contain equal elements in the same order. @available(SwiftStdlib 5.0, *) -public func expectContainerContents< +public func expectIteratorContents< Element: Equatable, C2: Collection, >( @@ -58,7 +58,7 @@ public func expectContainerContents< /// Check if `left` and `right` contain equal elements in the same order. @available(SwiftStdlib 5.0, *) -public func expectContainerContents< +public func expectIteratorContents< E1: ~Copyable, C2: Collection, >( @@ -97,19 +97,19 @@ public func expectContainerContents< #if COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW @available(SwiftStdlib 5.0, *) -public func expectContainersWithEquivalentElements< - C1: Container & ~Copyable & ~Escapable, - C2: Container & ~Copyable & ~Escapable +public func expectIterablesWithEquivalentElements< + I1: Iterable & ~Copyable & ~Escapable, + I2: Iterable & ~Copyable & ~Escapable >( - _ left: borrowing C1, - _ right: borrowing C2, - by areEquivalent: (borrowing C1.Element, borrowing C2.Element) -> Bool, + _ left: borrowing I1, + _ right: borrowing I2, + by areEquivalent: (borrowing I1.Element, borrowing I2.Element) -> Bool, _ message: @autoclosure () -> String = "", trapping: Bool = false, file: StaticString = #file, line: UInt = #line ) { - if left.borrowingElementsEqual(right, by: areEquivalent) { return } + if left.elementsEqual(right, by: areEquivalent) { return } _expectFailure( "Containers do not have equivalent elements", message, trapping: trapping, file: file, line: line) @@ -117,19 +117,19 @@ public func expectContainersWithEquivalentElements< /// Check if `left` and `right` contain equal elements in the same order. @available(SwiftStdlib 5.0, *) -public func expectContainersWithEqualElements< +public func expectIterablesWithEqualElements< Element: Equatable, - C1: Container & ~Copyable & ~Escapable, - C2: Container & ~Copyable & ~Escapable, + I1: Iterable & ~Copyable & ~Escapable, + I2: Iterable & ~Copyable & ~Escapable, >( - _ left: borrowing C1, - _ right: borrowing C2, + _ left: borrowing I1, + _ right: borrowing I2, _ message: @autoclosure () -> String = "", trapping: Bool = false, file: StaticString = #file, line: UInt = #line ) { - if left.borrowingElementsEqual(right) { return } + if left.elementsEqual(right) { return } _expectFailure( "Containers do not have equal elements", message, trapping: trapping, file: file, line: line) @@ -137,12 +137,12 @@ public func expectContainersWithEqualElements< /// Check if `left` and `right` contain equal elements in the same order. @available(SwiftStdlib 5.0, *) -public func expectContainerContents< +public func expectIteratorContents< Element: Equatable, - C1: Container & ~Copyable & ~Escapable, + I1: Iterable & ~Copyable & ~Escapable, C2: Collection, >( - _ left: borrowing C1, + _ left: borrowing I1, equalTo right: C2, _ message: @autoclosure () -> String = "", trapping: Bool = false, @@ -179,13 +179,13 @@ public func expectContainerContents< /// Check if `left` and `right` contain equal elements in the same order. @available(SwiftStdlib 5.0, *) -public func expectContainerContents< - C1: Container & ~Copyable & ~Escapable, +public func expectIteratorContents< + I1: Iterable & ~Copyable & ~Escapable, C2: Collection, >( - _ left: borrowing C1, + _ left: borrowing I1, equivalentTo right: C2, - by areEquivalent: (borrowing C1.Element, C2.Element) -> Bool, + by areEquivalent: (borrowing I1.Element, C2.Element) -> Bool, _ message: @autoclosure () -> String = "", trapping: Bool = false, file: StaticString = #file, diff --git a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift index 032ec0949..a18e6d66a 100644 --- a/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift +++ b/Tests/_CollectionsTestSupport/ConformanceCheckers/CheckContainer.swift @@ -15,17 +15,17 @@ import ContainersPreview @available(SwiftStdlib 5.0, *) @inlinable -public func checkContainer< - C: Container & ~Copyable & ~Escapable, - Expected: Sequence +public func checkIterable< + I: Iterable & ~Copyable & ~Escapable, + Expected: Sequence >( - _ container: borrowing C, + _ iterable: borrowing I, expectedContents: Expected, file: StaticString = #filePath, line: UInt = #line -) where C.Element: Equatable { - checkContainer( - container, +) where I.Element: Equatable { + checkIterable( + iterable, expectedContents: expectedContents, by: ==, file: file, line: line) @@ -33,32 +33,41 @@ public func checkContainer< @available(SwiftStdlib 5.0, *) @inlinable -public func checkContainer< - C: Container & ~Copyable & ~Escapable, - Expected: Sequence +public func checkIterable< + I: Iterable & ~Copyable & ~Escapable, + Expected: Sequence >( - _ container: borrowing C, + _ iterable: borrowing I, expectedContents: Expected, - by areEquivalent: (C.Element, C.Element) -> Bool, + by areEquivalent: (I.Element, I.Element) -> Bool, file: StaticString = #filePath, line: UInt = #line -) where C.Element: Equatable { - let entry = TestContext.current.push("checkContainer", file: file, line: line) +) where I.Element: Equatable { + let entry = TestContext.current.push("checkIterable", 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) + + expectEqual(iterable.isEmpty, expectedContents.isEmpty) + + let estimatedCount = iterable.estimatedCount + switch estimatedCount { + case .exactly(let c): + expectEqual(c, expectedContents.count) + case .infinite: + expectFailure() + case .unknown: + break + } // Check that the spans seem plausibly sized and that the indices are monotonic. let spanShapes: [Range] = { var r: [Range] = [] var pos = 0 - var it = container.startBorrowIteration() + var it = iterable.startBorrowIteration() while true { let origPos = pos - let span = it.nextSpan(maximumCount: nil) + let span = it.nextSpan() pos += span.count if span.isEmpty { break @@ -68,16 +77,16 @@ public func checkContainer< return r }() expectEqual( - spanShapes.reduce(into: 0, { $0 += $1.count }), actualCount, + spanShapes.reduce(into: 0, { $0 += $1.count }), expectedContents.count, "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 it = container.startBorrowIteration() + var it = iterable.startBorrowIteration() var spanIndex = 0 while true { - let span = it.nextSpan(maximumCount: nil) + let span = it.nextSpan() if span.isEmpty { break } expectEqual( span.count, spanShapes[spanIndex].count, @@ -95,7 +104,7 @@ public func checkContainer< // Check that we can iterate one by one. do { var pos = 0 - var it = container.startBorrowIteration() + var it = iterable.startBorrowIteration() while true { let span = it.nextSpan(maximumCount: 1) if span.isEmpty { break } @@ -111,7 +120,7 @@ public func checkContainer< // Check that we can iterate with huge maximum counts do { var pos = 0 - var it = container.startBorrowIteration() + var it = iterable.startBorrowIteration() var spanIndex = 0 while true { let span = it.nextSpan(maximumCount: Int.max) diff --git a/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift b/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift index b61709f8d..068b47f3b 100644 --- a/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift +++ b/Tests/_CollectionsTestSupport/MinimalTypes/StaccatoContainer.swift @@ -17,6 +17,7 @@ import ContainersPreview #endif #if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW +#if false // FIXME: Update /// A container type with user-defined contents and storage chunks. /// Useful for testing. @available(SwiftStdlib 5.0, *) @@ -86,3 +87,4 @@ extension StaccatoContainer: Container where Element: ~Copyable { } } #endif +#endif