diff --git a/README.md b/README.md index 7a701ca..928c505 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,10 @@ Please check the [releases](https://github.com/mochidev/AsyncSequenceReader/rele ```swift dependencies: [ - .package(url: "https://github.com/mochidev/AsyncSequenceReader.git", .upToNextMinor(from: "0.4.1")), + .package( + url: "https://github.com/mochidev/AsyncSequenceReader.git", + .upToNextMinor(from: "0.5.0") + ), ], ... targets: [ diff --git a/Sources/AsyncSequenceReader/AsyncIteratorMapSequence.swift b/Sources/AsyncSequenceReader/AsyncIteratorMapSequence.swift index 908ccdf..325cab3 100644 --- a/Sources/AsyncSequenceReader/AsyncIteratorMapSequence.swift +++ b/Sources/AsyncSequenceReader/AsyncIteratorMapSequence.swift @@ -155,7 +155,7 @@ public struct AsyncIteratorMapSequence) async throws(TransformFailure) -> Transformed + transform: sending @escaping (_ iterator: inout AsyncBufferedIterator) async throws(TransformFailure) -> Transformed ) { self.base = base self.transform = transform diff --git a/Sources/AsyncSequenceReader/AsyncReadSequence.swift b/Sources/AsyncSequenceReader/AsyncReadSequence.swift index d38375c..88e2605 100644 --- a/Sources/AsyncSequenceReader/AsyncReadSequence.swift +++ b/Sources/AsyncSequenceReader/AsyncReadSequence.swift @@ -9,7 +9,7 @@ /// An ``/AsyncSequenceReader/_Concurrency/AsyncSequence`` subtype suitable for reading an existing iterator in place. /// -/// Note that to conform to this protocol, your type must be a reference type. After iterating, you'll also likely want to copy the base iterator back into your starting iterator, as shown in ``/AsyncSequenceReader/_Concurrency/AsyncIteratorProtocol/transform(with:readSequenceFactory:)``. +/// Note that to conform to this protocol, your type must be a reference type. After iterating, you'll also likely want to copy the base iterator back into your starting iterator, as shown in ``AsyncBufferedIterator/transform(isolation:with:readSequenceFactory:)``. public protocol AsyncReadSequence: AsyncSequence, AnyObject { associatedtype BaseIterator: AsyncIteratorProtocol where BaseIterator.Element == Element @@ -22,6 +22,7 @@ extension AsyncIteratorProtocol { /// /// - Note: Iterating over the read sequence multiple times will result in undefined behavior. /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter sequenceTransform: A transformation that accepts a sequence that can be read from, or stopped prematurely by returning `nil`. The receiving iterator will have moved forward by the same amount of items consumed within `sequenceTransform`. /// - Parameter readSequenceFactory: A factory to create a suitable ``AsyncReadSequence`` that will determine the logical bounds of the transformation within the receiving iterator. /// - Returns: A transformed value read from the iterator, or `nil` if there were no values left to read. @@ -29,7 +30,8 @@ extension AsyncIteratorProtocol { Transformed, ReadSequence: AsyncReadSequence, TransformFailure: Error >( - with sequenceTransform: sending (sending ReadSequence) async throws(TransformFailure) -> Transformed, + isolation actor: isolated (any Actor)? = #isolation, + with sequenceTransform: sending (sending ReadSequence) async throws(TransformFailure) -> sending Transformed, readSequenceFactory: (inout AsyncBufferedIterator) -> ReadSequence ) async throws(TransformFailure) -> Transformed? where ReadSequence.BaseIterator == Self { var results: Transformed? = nil @@ -50,7 +52,8 @@ extension AsyncBufferedIterator { /// Transform the receiving iterator using the specified sequence transformer and configured read sequence. /// /// - Note: Iterating over the read sequence multiple times will result in undefined behavior. - /// + /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter sequenceTransform: A transformation that accepts a sequence that can be read from, or stopped prematurely by returning `nil`. The receiving iterator will have moved forward by the same amount of items consumed within `sequenceTransform`. /// - Parameter readSequenceFactory: A factory to create a suitable ``AsyncReadSequence`` that will determine the logical bounds of the transformation within the receiving iterator. /// - Returns: A transformed value read from the iterator, or `nil` if there were no values left to read. @@ -58,7 +61,8 @@ extension AsyncBufferedIterator { Transformed, ReadSequence: AsyncReadSequence, TransformFailure: Error >( - with sequenceTransform: sending (sending ReadSequence) async throws(TransformFailure) -> Transformed, + isolation actor: isolated (any Actor)? = #isolation, + with sequenceTransform: sending (sending ReadSequence) async throws(TransformFailure) -> sending Transformed, readSequenceFactory: (inout Self) -> ReadSequence ) async throws(TransformFailure) -> Transformed? where ReadSequence.BaseIterator == BaseIterator { var results: Transformed? = nil diff --git a/Sources/AsyncSequenceReader/AsyncReadUpToCountSequence.swift b/Sources/AsyncSequenceReader/AsyncReadUpToCountSequence.swift index 810bc85..d7272a7 100644 --- a/Sources/AsyncSequenceReader/AsyncReadUpToCountSequence.swift +++ b/Sources/AsyncSequenceReader/AsyncReadUpToCountSequence.swift @@ -11,11 +11,16 @@ extension AsyncIteratorProtocol { /// Asynchronously advances by the specified number of elements, or ends the sequence if there is no next element. /// /// If a complete array could not be collected, an error is thrown and the sequence should be considered finished. + /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter count: The number of elements to collect. /// - Returns: A collection with exactly `count` elements, or `nil` if the sequence is finished. /// - Throws: ``AsyncSequenceReaderError/insufficientElements(minimum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended. @inlinable - public mutating func collect(_ count: Int) async throws -> [Element]? { + public mutating func collect( + isolation actor: isolated (any Actor)? = #isolation, + _ count: Int + ) async throws -> [Element]? { assert(count >= 0, "count must be larger than or equal to 0") return try await collect(min: count, max: count) } @@ -23,12 +28,18 @@ extension AsyncIteratorProtocol { /// Asynchronously advances by the specified minimum number of elements, continuing until the specified maximum number of elements, or ends the sequence if there is no next element. /// /// If a complete array larger than `minCount` could not be constructed, an error is thrown and the sequence should be considered finished. + /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter minCount: The minimum number of elements to collect. /// - Parameter maxCount: The maximum number of elements to collect. /// - Returns: A collection with at least `minCount` and at most `maxCount` elements, or `nil` if the sequence is finished. /// - Throws: ``AsyncSequenceReaderError/insufficientElements(minimum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended. @inlinable - public mutating func collect(min minCount: Int = 0, max maxCount: Int) async throws -> [Element]? { + public mutating func collect( + isolation actor: isolated (any Actor)? = #isolation, + min minCount: Int = 0, + max maxCount: Int + ) async throws -> [Element]? { precondition(minCount <= maxCount, "maxCount must be larger than or equal to minCount") precondition(minCount >= 0, "minCount must be larger than or equal to 0") if maxCount == 0 { return [] } @@ -59,11 +70,16 @@ extension AsyncIteratorProtocol where Failure == Never { /// Asynchronously advances by the specified number of elements, or ends the sequence if there is no next element. /// /// If a complete array could not be collected, an error is thrown and the sequence should be considered finished. + /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter count: The number of elements to collect. /// - Returns: A collection with exactly `count` elements, or `nil` if the sequence is finished. /// - Throws: ``AsyncSequenceReaderError/insufficientElements(minimum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended. @inlinable - public mutating func collect(_ count: Int) async throws(AsyncSequenceReaderError) -> [Element]? { + public mutating func collect( + isolation actor: isolated (any Actor)? = #isolation, + _ count: Int + ) async throws(AsyncSequenceReaderError) -> [Element]? { assert(count >= 0, "count must be larger than or equal to 0") #if compiler(<6.1.3) || compiler(>=6.2) return try await collect(min: count, max: count) @@ -94,12 +110,18 @@ extension AsyncIteratorProtocol where Failure == Never { /// Asynchronously advances by the specified minimum number of elements, continuing until the specified maximum number of elements, or ends the sequence if there is no next element. /// /// If a complete array larger than `minCount` could not be constructed, an error is thrown and the sequence should be considered finished. + /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter minCount: The minimum number of elements to collect. /// - Parameter maxCount: The maximum number of elements to collect. /// - Returns: A collection with at least `minCount` and at most `maxCount` elements, or `nil` if the sequence is finished. /// - Throws: ``AsyncSequenceReaderError/insufficientElements(minimum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended. @inlinable - public mutating func collect(min minCount: Int = 0, max maxCount: Int) async throws(AsyncSequenceReaderError) -> [Element]? { + public mutating func collect( + isolation actor: isolated (any Actor)? = #isolation, + min minCount: Int = 0, + max maxCount: Int + ) async throws(AsyncSequenceReaderError) -> [Element]? { precondition(minCount <= maxCount, "maxCount must be larger than or equal to minCount") precondition(minCount >= 0, "minCount must be larger than or equal to 0") if maxCount == 0 { return [] } @@ -153,6 +175,7 @@ extension AsyncIteratorProtocol { /// // Prints: "Hello, World!", "My name is Dimitri.", "", "Bye!" /// ``` /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter count: The number of elements the `sequenceTransform` closure will have access to. /// - Parameter sequenceTransform: A transformation that accepts a sequence of the specified size that can be read from, or stopped prematurely by returning early. The receiving iterator will have moved forward by the same amount of items consumed within `sequenceTransform`. /// - Returns: A transformed value as returned by `sequenceTransform`, or `nil` if the sequence was already finished. @@ -162,8 +185,9 @@ extension AsyncIteratorProtocol { Transformed, TransformFailure >( + isolation actor: isolated (any Actor)? = #isolation, _ count: Int, - sequenceTransform: sending (sending AsyncReadUpToCountSequence) async throws(TransformFailure) -> Transformed + sequenceTransform: sending (sending AsyncReadUpToCountSequence) async throws(TransformFailure) -> sending Transformed ) async throws(TransformFailure) -> Transformed? { assert(count >= 0, "count must be larger than or equal to 0") return try await collect(min: count, max: count, sequenceTransform: sequenceTransform) @@ -198,6 +222,7 @@ extension AsyncIteratorProtocol { /// /// - Important: This variation reads ahead a single byte /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter minCount: The minimum number of elements the `sequenceTransform` closure will attempt have access to. If this number cannot be guaranteed, an error will be thrown. /// - Parameter maxCount: The maximum number of elements the `sequenceTransform` closure will have access to. /// - Parameter sequenceTransform: A transformation that accepts a sequence of the specified size that can be read from, or stopped prematurely by returning early. The receiving iterator will have moved forward by the same amount of items consumed within `sequenceTransform`. @@ -208,9 +233,10 @@ extension AsyncIteratorProtocol { Transformed, TransformFailure >( + isolation actor: isolated (any Actor)? = #isolation, min minCount: Int = 1, max maxCount: Int, - sequenceTransform: sending (sending AsyncReadUpToCountSequence) async throws(TransformFailure) -> Transformed + sequenceTransform: sending (sending AsyncReadUpToCountSequence) async throws(TransformFailure) -> sending Transformed ) async throws(TransformFailure) -> Transformed? { /// It is unsafe to read ahead in this case, so exit early if we know we won't need to read. if maxCount == 0 { return nil } @@ -247,6 +273,7 @@ extension AsyncBufferedIterator { /// // Prints: "Hello, World!", "My name is Dimitri.", "", "Bye!" /// ``` /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter count: The number of elements the `sequenceTransform` closure will have access to. /// - Parameter sequenceTransform: A transformation that accepts a sequence of the specified size that can be read from, or stopped prematurely by returning early. The receiving iterator will have moved forward by the same amount of items consumed within `sequenceTransform`. /// - Returns: A transformed value as returned by `sequenceTransform`, or `nil` if the sequence was already finished. @@ -256,8 +283,9 @@ extension AsyncBufferedIterator { Transformed, TransformFailure >( + isolation actor: isolated (any Actor)? = #isolation, _ count: Int, - sequenceTransform: sending (sending AsyncReadUpToCountSequence) async throws(TransformFailure) -> Transformed + sequenceTransform: sending (sending AsyncReadUpToCountSequence) async throws(TransformFailure) -> sending Transformed ) async throws(TransformFailure) -> Transformed? { assert(count >= 0, "count must be larger than 0") return try await collect(min: count, max: count, sequenceTransform: sequenceTransform) @@ -290,6 +318,7 @@ extension AsyncBufferedIterator { /// // Prints: "Hello, World!", "My name is Dimitri.", "", "Bye?" /// ``` /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter minCount: The minimum number of elements the `sequenceTransform` closure will attempt have access to. If this number cannot be guaranteed, an error will be thrown. /// - Parameter maxCount: The maximum number of elements the `sequenceTransform` closure will have access to. /// - Parameter sequenceTransform: A transformation that accepts a sequence of the specified size that can be read from, or stopped prematurely by returning early. The receiving iterator will have moved forward by the same amount of items consumed within `sequenceTransform`. @@ -300,9 +329,10 @@ extension AsyncBufferedIterator { Transformed, TransformFailure: Error >( + isolation actor: isolated (any Actor)? = #isolation, min minCount: Int = 0, max maxCount: Int, - sequenceTransform: sending (sending AsyncReadUpToCountSequence) async throws(TransformFailure) -> Transformed + sequenceTransform: sending (sending AsyncReadUpToCountSequence) async throws(TransformFailure) -> sending Transformed ) async throws(TransformFailure) -> Transformed? { try await transform(with: sequenceTransform) { .init($0, minCount: minCount, maxCount: maxCount) } } @@ -358,7 +388,16 @@ extension AsyncReadUpToCountSequence: AsyncSequence { /// /// This iterator checks if `numberOfElementsRead` has exceeded the max size for the sequence. If it has not, then it'll read until it does. If the next value read marks the end of the sequence, but the minimum size has not yet been reached, an error is thrown. @inlinable - public mutating func next() async throws -> Element? { + @_disfavoredOverload + public mutating func next() async rethrows -> Element? { + try await next() + } + + /// Produces the next element in the sequence. + /// + /// This iterator checks if `numberOfElementsRead` has exceeded the max size for the sequence. If it has not, then it'll read until it does. If the next value read marks the end of the sequence, but the minimum size has not yet been reached, an error is thrown. + @inlinable + public mutating func next(isolation actor: isolated (any Actor)? = #isolation) async throws -> Element? { guard numberOfElementsRead < readSequence.maxCount else { return nil } guard let next = try await readSequence.baseIterator.next() else { diff --git a/Sources/AsyncSequenceReader/AsyncReadUpToElementsSequence.swift b/Sources/AsyncSequenceReader/AsyncReadUpToElementsSequence.swift index 53331a1..39f4cf3 100644 --- a/Sources/AsyncSequenceReader/AsyncReadUpToElementsSequence.swift +++ b/Sources/AsyncSequenceReader/AsyncReadUpToElementsSequence.swift @@ -30,12 +30,14 @@ extension AsyncIteratorProtocol where Element: Equatable { /// // Prints: "apple", "orange", "banana", "kiwi", "kumquat", "pear", "pineapple", /// ``` /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter termination: The element marking the end of the sequence that will be collected. /// - Parameter maximumBufferSize: The maximum amount of elements that will be read before an error is thrown if a termination is not detected. /// - Returns: An array of the collected elements, or `nil` if the sequence was already finished. /// - Throws: ``AsyncSequenceReaderError/terminationNotFound(maximum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended. @inlinable public mutating func collect( + isolation actor: isolated (any Actor)? = #isolation, upToIncluding termination: Element, throwsIfOver maximumBufferSize: Int ) async throws -> [Element]? { @@ -64,12 +66,14 @@ extension AsyncIteratorProtocol where Element: Equatable { /// // Prints: "apple", "orange", "banana", "kiwi", "kumquat", "pear", "pineapple", /// ``` /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter termination: The sequence of elements marking the end of the sequence that will be collected. /// - Parameter maximumBufferSize: The maximum amount of elements that will be read before an error is thrown if a termination is not detected. /// - Returns: An array of the collected elements, or `nil` if the sequence was already finished. /// - Throws: ``AsyncSequenceReaderError/terminationNotFound(maximum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended. @inlinable public mutating func collect( + isolation actor: isolated (any Actor)? = #isolation, upToIncluding termination: some Collection, throwsIfOver maximumBufferSize: Int ) async throws -> [Element]? { @@ -115,12 +119,14 @@ extension AsyncIteratorProtocol where Element: Equatable { /// // Prints: "apple", "orange", "banana", "kiwi", "kumquat", "pear", "pineapple", /// ``` /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter termination: The element marking the end of the sequence that will be collected. /// - Parameter maximumBufferSize: The maximum amount of elements that will be read before an error is thrown if a termination is not detected. /// - Returns: An array of the collected elements, or `nil` if the sequence was already finished. /// - Throws: ``AsyncSequenceReaderError/terminationNotFound(maximum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended. @inlinable public mutating func collect( + isolation actor: isolated (any Actor)? = #isolation, upToExcluding termination: Element, throwsIfOver maximumBufferSize: Int ) async throws -> [Element]? { @@ -149,12 +155,14 @@ extension AsyncIteratorProtocol where Element: Equatable { /// // Prints: "apple", "orange", "banana", "kiwi", "kumquat", "pear", "pineapple", /// ``` /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter termination: The sequence of elements marking the end of the sequence that will be collected. /// - Parameter maximumBufferSize: The maximum amount of elements that will be read before an error is thrown if a termination is not detected. /// - Returns: An array of the collected elements, or `nil` if the sequence was already finished. /// - Throws: ``AsyncSequenceReaderError/terminationNotFound(maximum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended. @inlinable public mutating func collect( + isolation actor: isolated (any Actor)? = #isolation, upToExcluding termination: some Collection, throwsIfOver maximumBufferSize: Int ) async throws -> [Element]? { @@ -186,12 +194,14 @@ extension AsyncIteratorProtocol where Element: Equatable, Failure == Never { /// // Prints: "apple", "orange", "banana", "kiwi", "kumquat", "pear", "pineapple", /// ``` /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter termination: The element marking the end of the sequence that will be collected. /// - Parameter maximumBufferSize: The maximum amount of elements that will be read before an error is thrown if a termination is not detected. /// - Returns: An array of the collected elements, or `nil` if the sequence was already finished. /// - Throws: ``AsyncSequenceReaderError/terminationNotFound(maximum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended. @inlinable public mutating func collect( + isolation actor: isolated (any Actor)? = #isolation, upToIncluding termination: Element, throwsIfOver maximumBufferSize: Int ) async throws(AsyncSequenceReaderError) -> [Element]? { @@ -241,12 +251,14 @@ extension AsyncIteratorProtocol where Element: Equatable, Failure == Never { /// // Prints: "apple", "orange", "banana", "kiwi", "kumquat", "pear", "pineapple", /// ``` /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter termination: The sequence of elements marking the end of the sequence that will be collected. /// - Parameter maximumBufferSize: The maximum amount of elements that will be read before an error is thrown if a termination is not detected. /// - Returns: An array of the collected elements, or `nil` if the sequence was already finished. /// - Throws: ``AsyncSequenceReaderError/terminationNotFound(maximum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended. @inlinable public mutating func collect( + isolation actor: isolated (any Actor)? = #isolation, upToIncluding termination: some Collection, throwsIfOver maximumBufferSize: Int ) async throws(AsyncSequenceReaderError) -> [Element]? { @@ -292,12 +304,14 @@ extension AsyncIteratorProtocol where Element: Equatable, Failure == Never { /// // Prints: "apple", "orange", "banana", "kiwi", "kumquat", "pear", "pineapple", /// ``` /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter termination: The element marking the end of the sequence that will be collected. /// - Parameter maximumBufferSize: The maximum amount of elements that will be read before an error is thrown if a termination is not detected. /// - Returns: An array of the collected elements, or `nil` if the sequence was already finished. /// - Throws: ``AsyncSequenceReaderError/terminationNotFound(maximum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended. @inlinable public mutating func collect( + isolation actor: isolated (any Actor)? = #isolation, upToExcluding termination: Element, throwsIfOver maximumBufferSize: Int ) async throws(AsyncSequenceReaderError) -> [Element]? { @@ -347,12 +361,14 @@ extension AsyncIteratorProtocol where Element: Equatable, Failure == Never { /// // Prints: "apple", "orange", "banana", "kiwi", "kumquat", "pear", "pineapple", /// ``` /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter termination: The sequence of elements marking the end of the sequence that will be collected. /// - Parameter maximumBufferSize: The maximum amount of elements that will be read before an error is thrown if a termination is not detected. /// - Returns: An array of the collected elements, or `nil` if the sequence was already finished. /// - Throws: ``AsyncSequenceReaderError/terminationNotFound(maximum:actual:)`` if a complete byte sequence could not be returned by the time the sequence ended. @inlinable public mutating func collect( + isolation actor: isolated (any Actor)? = #isolation, upToExcluding termination: some Collection, throwsIfOver maximumBufferSize: Int ) async throws(AsyncSequenceReaderError) -> [Element]? { @@ -410,6 +426,7 @@ extension AsyncIteratorProtocol where Element: Equatable { /// // Prints: "apple", "orange", "banana", "kiwi", "kumquat", "pear", "pineapple", /// ``` /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter termination: The element marking the end of the sequence the `sequenceTransform` closure will have access to. /// - Parameter sequenceTransform: A transformation that accepts a sequence containing elements up to the termination that can be read from, or stopped prematurely by returning early. The receiving iterator will have moved forward by the same amount of items consumed within `sequenceTransform`. /// - Returns: A transformed value as returned by `sequenceTransform`, or `nil` if the sequence was already finished. @@ -418,8 +435,9 @@ extension AsyncIteratorProtocol where Element: Equatable { Transformed, TransformFailure: Error >( + isolation actor: isolated (any Actor)? = #isolation, upToIncluding termination: Element, - sequenceTransform: sending (AsyncReadUpToElementsSequence>) async throws(TransformFailure) -> Transformed + sequenceTransform: sending (AsyncReadUpToElementsSequence>) async throws(TransformFailure) -> sending Transformed ) async throws(TransformFailure) -> Transformed? { try await collect(upToIncluding: [termination], sequenceTransform: sequenceTransform) } @@ -453,6 +471,7 @@ extension AsyncIteratorProtocol where Element: Equatable { /// // Prints: "apple", "orange", "banana", "kiwi", "kumquat", "pear", "pineapple", /// ``` /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter termination: The sequence of elements marking the end of the sequence the `sequenceTransform` closure will have access to. /// - Parameter sequenceTransform: A transformation that accepts a sequence containing elements up to the termination that can be read from, or stopped prematurely by returning early. The receiving iterator will have moved forward by the same amount of items consumed within `sequenceTransform`. /// - Returns: A transformed value as returned by `sequenceTransform`, or `nil` if the sequence was already finished. @@ -462,8 +481,9 @@ extension AsyncIteratorProtocol where Element: Equatable { Transformed, TransformFailure: Error >( + isolation actor: isolated (any Actor)? = #isolation, upToIncluding termination: TerminationCollection, - sequenceTransform: sending (AsyncReadUpToElementsSequence) async throws(TransformFailure) -> Transformed + sequenceTransform: sending (AsyncReadUpToElementsSequence) async throws(TransformFailure) -> sending Transformed ) async throws(TransformFailure) -> Transformed? { try await transform(with: sequenceTransform) { .init($0, termination: termination) } } @@ -499,13 +519,15 @@ extension AsyncBufferedIterator where Element: Equatable { /// // Prints: "apple", "orange", "banana", "kiwi", "kumquat", "pear", "pineapple", /// ``` /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter termination: The element marking the end of the sequence the `sequenceTransform` closure will have access to. /// - Parameter sequenceTransform: A transformation that accepts a sequence containing elements up to the termination that can be read from, or stopped prematurely by returning early. The receiving iterator will have moved forward by the same amount of items consumed within `sequenceTransform`. /// - Returns: A transformed value as returned by `sequenceTransform`, or `nil` if the sequence was already finished. @inlinable public mutating func collect( + isolation actor: isolated (any Actor)? = #isolation, upToIncluding termination: Element, - sequenceTransform: sending (AsyncReadUpToElementsSequence>) async throws -> Transformed + sequenceTransform: sending (AsyncReadUpToElementsSequence>) async throws -> sending Transformed ) async rethrows -> Transformed? { try await collect(upToIncluding: [termination], sequenceTransform: sequenceTransform) } @@ -539,6 +561,7 @@ extension AsyncBufferedIterator where Element: Equatable { /// // Prints: "apple", "orange", "banana", "kiwi", "kumquat", "pear", "pineapple", /// ``` /// + /// - Parameter actor: The isolation context to run the reciever on. /// - Parameter termination: The sequence of elements marking the end of the sequence the `sequenceTransform` closure will have access to. /// - Parameter sequenceTransform: A transformation that accepts a sequence containing elements up to the termination that can be read from, or stopped prematurely by returning early. The receiving iterator will have moved forward by the same amount of items consumed within `sequenceTransform`. /// - Returns: A transformed value as returned by `sequenceTransform`, or `nil` if the sequence was already finished. @@ -547,8 +570,9 @@ extension AsyncBufferedIterator where Element: Equatable { TerminationCollection: Collection, Transformed >( + isolation actor: isolated (any Actor)? = #isolation, upToIncluding termination: TerminationCollection, - sequenceTransform: sending (AsyncReadUpToElementsSequence) async throws -> Transformed + sequenceTransform: sending (AsyncReadUpToElementsSequence) async throws -> sending Transformed ) async rethrows -> Transformed? { try await transform(with: sequenceTransform) { .init($0, termination: termination) } } diff --git a/Sources/AsyncSequenceReader/AsyncSequenceReader.swift b/Sources/AsyncSequenceReader/AsyncSequenceReader.swift index 9cb9b88..9a841b9 100644 --- a/Sources/AsyncSequenceReader/AsyncSequenceReader.swift +++ b/Sources/AsyncSequenceReader/AsyncSequenceReader.swift @@ -51,10 +51,26 @@ extension AsyncSequenceReader: AsyncSequence { /// Produces the next element in the sequence. /// /// This iterator calls `read()` with its base iterator, and lets that closure produce an appropriate result. + #if swift(<6.2) @inlinable public mutating func next() async throws -> Element? { return try await readSequence.read(&readSequence.baseIterator) } + #else + @inlinable + @_disfavoredOverload + public mutating func next() async rethrows -> Element? { + try await next() + } + + /// Produces the next element in the sequence. + /// + /// This iterator calls `read()` with its base iterator, and lets that closure produce an appropriate result. + @inlinable + public mutating func next(isolation actor: isolated (any Actor)? = #isolation) async throws -> Element? { + return try await readSequence.read(&readSequence.baseIterator) + } + #endif } @inlinable diff --git a/Tests/AsyncSequenceReaderTests/AsyncReadUpToCountSequenceTests.swift b/Tests/AsyncSequenceReaderTests/AsyncReadUpToCountSequenceTests.swift index 9712099..5ab0f10 100644 --- a/Tests/AsyncSequenceReaderTests/AsyncReadUpToCountSequenceTests.swift +++ b/Tests/AsyncSequenceReaderTests/AsyncReadUpToCountSequenceTests.swift @@ -221,7 +221,7 @@ import Testing }.reduce(into: [], { $0.append($1) }) } #if DEBUG - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:262: Assertion failed: count must be larger than 0") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:290: Assertion failed: count must be larger than 0") #endif } @@ -236,7 +236,7 @@ import Testing }.reduce(into: [], { $0.append($1) }) } #if DEBUG - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:331: Precondition failed: minCount must be larger than or equal to 0") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:361: Precondition failed: minCount must be larger than or equal to 0") #endif } @@ -251,7 +251,7 @@ import Testing }.reduce(into: [], { $0.append($1) }) } #if DEBUG - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:331: Precondition failed: minCount must be larger than or equal to 0") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:361: Precondition failed: minCount must be larger than or equal to 0") #endif } @@ -266,7 +266,7 @@ import Testing }.reduce(into: [], { $0.append($1) }) } #if DEBUG - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:330: Precondition failed: maxCount must be larger than or equal to minCount") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:360: Precondition failed: maxCount must be larger than or equal to minCount") #endif } @@ -280,7 +280,7 @@ import Testing } } #if DEBUG - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:168: Assertion failed: count must be larger than or equal to 0") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:192: Assertion failed: count must be larger than or equal to 0") #endif } @@ -293,9 +293,9 @@ import Testing } #if DEBUG #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS) - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:19: Assertion failed: count must be larger than or equal to 0") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:24: Assertion failed: count must be larger than or equal to 0") #else - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:67: Assertion failed: count must be larger than or equal to 0") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:83: Assertion failed: count must be larger than or equal to 0") #endif #endif } @@ -310,7 +310,7 @@ import Testing try await sequence.reduce(into: "") { $0 += ($0.isEmpty ? "" : " ") + String($1) } } } - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:217: Assertion failed: minCount must be larger than or equal to 1, or the first value risks getting dropped") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:243: Assertion failed: minCount must be larger than or equal to 1, or the first value risks getting dropped") #endif } @@ -324,7 +324,7 @@ import Testing } } #if DEBUG - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:217: Assertion failed: minCount must be larger than or equal to 1, or the first value risks getting dropped") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:243: Assertion failed: minCount must be larger than or equal to 1, or the first value risks getting dropped") #endif } @@ -337,9 +337,9 @@ import Testing } #if DEBUG #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS) - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:33: Precondition failed: minCount must be larger than or equal to 0") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:44: Precondition failed: minCount must be larger than or equal to 0") #else - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:104: Precondition failed: minCount must be larger than or equal to 0") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:126: Precondition failed: minCount must be larger than or equal to 0") #endif #endif } @@ -354,7 +354,7 @@ import Testing } } #if DEBUG - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:217: Assertion failed: minCount must be larger than or equal to 1, or the first value risks getting dropped") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:243: Assertion failed: minCount must be larger than or equal to 1, or the first value risks getting dropped") #endif } @@ -367,9 +367,9 @@ import Testing } #if DEBUG #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS) - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:33: Precondition failed: minCount must be larger than or equal to 0") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:44: Precondition failed: minCount must be larger than or equal to 0") #else - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:104: Precondition failed: minCount must be larger than or equal to 0") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:126: Precondition failed: minCount must be larger than or equal to 0") #endif #endif } @@ -384,7 +384,7 @@ import Testing } } #if DEBUG - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:217: Assertion failed: minCount must be larger than or equal to 1, or the first value risks getting dropped") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:243: Assertion failed: minCount must be larger than or equal to 1, or the first value risks getting dropped") #endif } @@ -397,9 +397,9 @@ import Testing } #if DEBUG #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS) - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:32: Precondition failed: maxCount must be larger than or equal to minCount") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:43: Precondition failed: maxCount must be larger than or equal to minCount") #else - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:103: Precondition failed: maxCount must be larger than or equal to minCount") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToCountSequence.swift:125: Precondition failed: maxCount must be larger than or equal to minCount") #endif #endif } diff --git a/Tests/AsyncSequenceReaderTests/AsyncReadUpToElementsSequenceTests.swift b/Tests/AsyncSequenceReaderTests/AsyncReadUpToElementsSequenceTests.swift index c8d359f..e2f259b 100644 --- a/Tests/AsyncSequenceReaderTests/AsyncReadUpToElementsSequenceTests.swift +++ b/Tests/AsyncSequenceReaderTests/AsyncReadUpToElementsSequenceTests.swift @@ -159,9 +159,9 @@ import Testing } #if DEBUG #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS) - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToElementsSequence.swift:76: Precondition failed: termination must not be empty") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToElementsSequence.swift:80: Precondition failed: termination must not be empty") #else - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToElementsSequence.swift:253: Precondition failed: termination must not be empty") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToElementsSequence.swift:265: Precondition failed: termination must not be empty") #endif #endif } @@ -314,9 +314,9 @@ import Testing } #if DEBUG #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS) - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToElementsSequence.swift:76: Precondition failed: termination must not be empty") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToElementsSequence.swift:80: Precondition failed: termination must not be empty") #else - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToElementsSequence.swift:253: Precondition failed: termination must not be empty") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToElementsSequence.swift:265: Precondition failed: termination must not be empty") #endif #endif } @@ -371,7 +371,7 @@ import Testing } } #if DEBUG - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToElementsSequence.swift:575: Precondition failed: termination must not be empty") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToElementsSequence.swift:599: Precondition failed: termination must not be empty") #endif } @@ -439,7 +439,7 @@ import Testing } } #if DEBUG - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToElementsSequence.swift:575: Precondition failed: termination must not be empty") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadUpToElementsSequence.swift:599: Precondition failed: termination must not be empty") #endif } } diff --git a/Tests/AsyncSequenceReaderTests/AsyncSequenceReaderTests.swift b/Tests/AsyncSequenceReaderTests/AsyncSequenceReaderTests.swift index cc64968..5630fc0 100644 --- a/Tests/AsyncSequenceReaderTests/AsyncSequenceReaderTests.swift +++ b/Tests/AsyncSequenceReaderTests/AsyncSequenceReaderTests.swift @@ -146,7 +146,7 @@ import Testing // MARK: - Test Transforms - func countCharacters(_ sequence: sending ReadSequence) async throws -> Int? where ReadSequence.Element == String { + func countCharacters(_ sequence: sending ReadSequence) async throws -> sending Int? where ReadSequence.Element == String { try await sequence.map { $0.count }.reduce(into: 0) { partialResult, next in partialResult += next } @@ -294,7 +294,7 @@ import Testing }) } #if DEBUG - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadSequence.swift:42: Precondition failed: A transform was requested, but the sequence was left in a state where the next value will never be read (fix: Use `sequence.makeBufferedIterator()` instead of calling `.transform` on `iterator` directly)") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadSequence.swift:44: Precondition failed: A transform was requested, but the sequence was left in a state where the next value will never be read (fix: Use `sequence.makeBufferedIterator()` instead of calling `.transform` on `iterator` directly)") #endif } @@ -313,7 +313,7 @@ import Testing }) } #if DEBUG - #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadSequence.swift:42: Precondition failed: A transform was requested, but the sequence was left in a state where the next value will never be read (fix: Use `sequence.makeBufferedIterator()` instead of calling `.transform` on `iterator` directly)") + #expect(result?.standardErrorUTF8Lines.first == "AsyncSequenceReader/AsyncReadSequence.swift:44: Precondition failed: A transform was requested, but the sequence was left in a state where the next value will never be read (fix: Use `sequence.makeBufferedIterator()` instead of calling `.transform` on `iterator` directly)") #endif } }