Skip to content

Commit 3e726eb

Browse files
committed
Add nextElement() and explicit Failure types for a number of asynchronous iterators
1 parent 909257e commit 3e726eb

9 files changed

+222
-2
lines changed

stdlib/public/Concurrency/AsyncStream.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,24 @@ extension AsyncStream: AsyncSequence {
377377
public mutating func next() async -> Element? {
378378
await context.produce()
379379
}
380+
381+
/// The next value from the asynchronous stream.
382+
///
383+
/// When `nextElement()` returns `nil`, this signifies the end of the
384+
/// `AsyncStream`.
385+
///
386+
/// It is a programmer error to invoke `nextElement()` from a concurrent
387+
/// context that contends with another such call, which results in a call to
388+
/// `fatalError()`.
389+
///
390+
/// If you cancel the task this iterator is running in while `nextElement()`
391+
/// is awaiting a value, the `AsyncStream` terminates. In this case,
392+
/// `nextElement()` might return `nil` immediately, or return `nil` on
393+
/// subsequent calls.
394+
@available(SwiftStdlib 5.11, *)
395+
public mutating func nextElement() async -> Element? {
396+
await context.produce()
397+
}
380398
}
381399

382400
/// Creates the asynchronous iterator that produces elements of this
@@ -530,6 +548,12 @@ extension AsyncStream {
530548
public mutating func next() async -> Element? {
531549
fatalError("Unavailable in task-to-thread concurrency model")
532550
}
551+
552+
@available(SwiftStdlib 5.11, *)
553+
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model")
554+
public mutating func nextElement() async -> Element? {
555+
fatalError("Unavailable in task-to-thread concurrency model")
556+
}
533557
}
534558
@available(SwiftStdlib 5.1, *)
535559
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model")

stdlib/public/Concurrency/AsyncThrowingCompactMapSequence.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ extension AsyncThrowingCompactMapSequence: AsyncSequence {
9393
/// The compact map sequence produces whatever type of element its
9494
/// transforming closure produces.
9595
public typealias Element = ElementOfResult
96+
/// The type of element produced by this asynchronous sequence.
97+
///
98+
/// The compact map sequence produces errors from either the base
99+
/// sequence or the transforming closure.
100+
public typealias Failure = any Error
96101
/// The type of iterator that produces elements of the sequence.
97102
public typealias AsyncIterator = Iterator
98103

@@ -145,6 +150,35 @@ extension AsyncThrowingCompactMapSequence: AsyncSequence {
145150
}
146151
return nil
147152
}
153+
154+
/// Produces the next element in the compact map sequence.
155+
///
156+
/// This iterator calls `nextElement()` on its base iterator; if this call
157+
/// returns `nil`, `nextElement()` returns `nil`. Otherwise, `nextElement()`
158+
/// calls the transforming closure on the received element, returning it if
159+
/// the transform returns a non-`nil` value. If the transform returns `nil`,
160+
/// this method continues to wait for further elements until it gets one
161+
/// that transforms to a non-`nil` value. If calling the closure throws an
162+
/// error, the sequence ends and `nextElement()` rethrows the error.
163+
@available(SwiftStdlib 5.11, *)
164+
@inlinable
165+
public mutating func nextElement() async throws(Failure) -> ElementOfResult? {
166+
while !finished {
167+
guard let element = try await baseIterator.nextElement() else {
168+
finished = true
169+
return nil
170+
}
171+
do {
172+
if let transformed = try await transform(element) {
173+
return transformed
174+
}
175+
} catch {
176+
finished = true
177+
throw error
178+
}
179+
}
180+
return nil
181+
}
148182
}
149183

150184
@inlinable

stdlib/public/Concurrency/AsyncThrowingDropWhileSequence.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ extension AsyncThrowingDropWhileSequence: AsyncSequence {
9191
/// The drop-while sequence produces whatever type of element its base
9292
/// sequence produces.
9393
public typealias Element = Base.Element
94+
/// The type of element produced by this asynchronous sequence.
95+
///
96+
/// The drop-while sequence produces errors from either the base
97+
/// sequence or the filtering closure.
98+
public typealias Failure = any Error
9499
/// The type of iterator that produces elements of the sequence.
95100
public typealias AsyncIterator = Iterator
96101

@@ -148,6 +153,39 @@ extension AsyncThrowingDropWhileSequence: AsyncSequence {
148153
}
149154
return try await baseIterator.next()
150155
}
156+
157+
/// Produces the next element in the drop-while sequence.
158+
///
159+
/// This iterator calls `nextElement()` on its base iterator and evaluates
160+
/// the result with the `predicate` closure. As long as the predicate
161+
/// returns `true`, this method returns `nil`. After the predicate returns
162+
/// `false`, for a value received from the base iterator, this method
163+
/// returns that value. After that, the iterator returns values received
164+
/// from its base iterator as-is, and never executes the predicate closure
165+
/// again. If calling the closure throws an error, the sequence ends and
166+
/// `nextElement()` rethrows the error.
167+
@available(SwiftStdlib 5.11, *)
168+
@inlinable
169+
public mutating func nextElement() async throws(Failure) -> Base.Element? {
170+
while !finished && !doneDropping {
171+
guard let element = try await baseIterator.nextElement() else {
172+
return nil
173+
}
174+
do {
175+
if try await predicate(element) == false {
176+
doneDropping = true
177+
return element
178+
}
179+
} catch {
180+
finished = true
181+
throw error
182+
}
183+
}
184+
guard !finished else {
185+
return nil
186+
}
187+
return try await baseIterator.nextElement()
188+
}
151189
}
152190

153191
@inlinable

stdlib/public/Concurrency/AsyncThrowingFilterSequence.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ extension AsyncThrowingFilterSequence: AsyncSequence {
8181
/// The filter sequence produces whatever type of element its base
8282
/// sequence produces.
8383
public typealias Element = Base.Element
84+
/// The type of element produced by this asynchronous sequence.
85+
///
86+
/// The filter sequence produces errors from either the base
87+
/// sequence or the filtering closure.
88+
public typealias Failure = any Error
8489
/// The type of iterator that produces elements of the sequence.
8590
public typealias AsyncIterator = Iterator
8691

@@ -130,6 +135,35 @@ extension AsyncThrowingFilterSequence: AsyncSequence {
130135

131136
return nil
132137
}
138+
139+
/// Produces the next element in the filter sequence.
140+
///
141+
/// This iterator calls `nextElement()` on its base iterator; if this call
142+
/// returns `nil`, `nextElement()` returns nil. Otherwise, `nextElement()`
143+
/// evaluates the result with the `predicate` closure. If the closure
144+
/// returns `true`, `nextElement()` returns the received element; otherwise
145+
/// it awaits the next element from the base iterator. If calling the
146+
/// closure throws an error, the sequence ends and `nextElement()` rethrows
147+
/// the error.
148+
@available(SwiftStdlib 5.11, *)
149+
@inlinable
150+
public mutating func nextElement() async throws(Failure) -> Base.Element? {
151+
while !finished {
152+
guard let element = try await baseIterator.nextElement() else {
153+
return nil
154+
}
155+
do {
156+
if try await isIncluded(element) {
157+
return element
158+
}
159+
} catch {
160+
finished = true
161+
throw error
162+
}
163+
}
164+
165+
return nil
166+
}
133167
}
134168

135169
@inlinable

stdlib/public/Concurrency/AsyncThrowingFlatMapSequence.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ extension AsyncThrowingFlatMapSequence: AsyncSequence {
8989
/// The flat map sequence produces the type of element in the asynchronous
9090
/// sequence produced by the `transform` closure.
9191
public typealias Element = SegmentOfResult.Element
92+
/// The type of error produced by this asynchronous sequence.
93+
///
94+
/// The flat map sequence produces errors from either the base
95+
/// sequence or the `transform` closure.
96+
public typealias Failure = any Error
9297
/// The type of iterator that produces elements of the sequence.
9398
public typealias AsyncIterator = Iterator
9499

stdlib/public/Concurrency/AsyncThrowingMapSequence.swift

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ extension AsyncThrowingMapSequence: AsyncSequence {
9292
/// The map sequence produces whatever type of element its the transforming
9393
/// closure produces.
9494
public typealias Element = Transformed
95+
/// The type of error produced by this asynchronous sequence.
96+
///
97+
/// The map sequence produces errors from either the base
98+
/// sequence or the `transform` closure.
99+
public typealias Failure = any Error
95100
/// The type of iterator that produces elements of the sequence.
96101
public typealias AsyncIterator = Iterator
97102

@@ -122,7 +127,7 @@ extension AsyncThrowingMapSequence: AsyncSequence {
122127
/// calling the transforming closure on the received element. If calling
123128
/// the closure throws an error, the sequence ends and `next()` rethrows
124129
/// the error.
125-
@inlinable
130+
@inlinable
126131
public mutating func next() async throws -> Transformed? {
127132
guard !finished, let element = try await baseIterator.next() else {
128133
return nil
@@ -134,6 +139,27 @@ extension AsyncThrowingMapSequence: AsyncSequence {
134139
throw error
135140
}
136141
}
142+
143+
/// Produces the next element in the map sequence.
144+
///
145+
/// This iterator calls `nextElement()` on its base iterator; if this call
146+
/// returns `nil`, `nextElement()` returns nil. Otherwise, `nextElement()`
147+
/// returns the result of calling the transforming closure on the received
148+
/// element. If calling the closure throws an error, the sequence ends and
149+
/// `nextElement()` rethrows the error.
150+
@available(SwiftStdlib 5.11, *)
151+
@inlinable
152+
public mutating func nextElement() async throws(Failure) -> Transformed? {
153+
guard !finished, let element = try await baseIterator.nextElement() else {
154+
return nil
155+
}
156+
do {
157+
return try await transform(element)
158+
} catch {
159+
finished = true
160+
throw error
161+
}
162+
}
137163
}
138164

139165
@inlinable

stdlib/public/Concurrency/AsyncThrowingPrefixWhileSequence.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ extension AsyncThrowingPrefixWhileSequence: AsyncSequence {
8787
/// The prefix-while sequence produces whatever type of element its base
8888
/// iterator produces.
8989
public typealias Element = Base.Element
90+
/// The type of error produced by this asynchronous sequence.
91+
///
92+
/// The prefix-while sequence produces errors from either the base
93+
/// sequence or the filtering closure.
94+
public typealias Failure = any Error
9095
/// The type of iterator that produces elements of the sequence.
9196
public typealias AsyncIterator = Iterator
9297

@@ -133,6 +138,31 @@ extension AsyncThrowingPrefixWhileSequence: AsyncSequence {
133138
}
134139
return nil
135140
}
141+
142+
/// Produces the next element in the prefix-while sequence.
143+
///
144+
/// If the predicate hasn't failed yet, this method gets the next element
145+
/// from the base sequence and calls the predicate with it. If this call
146+
/// succeeds, this method passes along the element. Otherwise, it returns
147+
/// `nil`, ending the sequence. If calling the predicate closure throws an
148+
/// error, the sequence ends and `nextElement()` rethrows the error.
149+
@available(SwiftStdlib 5.11, *)
150+
@inlinable
151+
public mutating func nextElement() async throws(Failure) -> Base.Element? {
152+
if !predicateHasFailed, let nextElement = try await baseIterator.nextElement() {
153+
do {
154+
if try await predicate(nextElement) {
155+
return nextElement
156+
} else {
157+
predicateHasFailed = true
158+
}
159+
} catch {
160+
predicateHasFailed = true
161+
throw error
162+
}
163+
}
164+
return nil
165+
}
136166
}
137167

138168
@inlinable

stdlib/public/Concurrency/TaskGroup.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,6 +1141,36 @@ extension ThrowingTaskGroup: AsyncSequence {
11411141
}
11421142
}
11431143

1144+
/// Advances to and returns the result of the next child task.
1145+
///
1146+
/// The elements returned from this method
1147+
/// appear in the order that the tasks *completed*,
1148+
/// not in the order that those tasks were added to the task group.
1149+
/// After this method returns `nil`,
1150+
/// this iterator is guaranteed to never produce more values.
1151+
///
1152+
/// For more information about the iteration order and semantics,
1153+
/// see `ThrowingTaskGroup.next()`
1154+
///
1155+
/// - Throws: The error thrown by the next child task that completes.
1156+
///
1157+
/// - Returns: The value returned by the next child task that completes,
1158+
/// or `nil` if there are no remaining child tasks,
1159+
@available(SwiftStdlib 5.11, *)
1160+
public mutating func nextElement() async throws(Failure) -> Element? {
1161+
guard !finished else { return nil }
1162+
do {
1163+
guard let element = try await group.next() else {
1164+
finished = true
1165+
return nil
1166+
}
1167+
return element
1168+
} catch {
1169+
finished = true
1170+
throw error as! Failure
1171+
}
1172+
}
1173+
11441174
public mutating func cancel() {
11451175
finished = true
11461176
group.cancelAll()

test/api-digester/stability-concurrency-abi.test

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ Protocol AsyncSequence has generic signature change from <Self.AsyncIterator : _
6565
Struct CheckedContinuation has removed conformance to UnsafeSendable
6666
Protocol AsyncIteratorProtocol is now without @rethrows
6767
Protocol AsyncSequence is now without @rethrows
68-
Func AsyncIteratorProtocol.nextElement() has been added as a protocol requirement
6968

7069
// SerialExecutor gained `enqueue(_: __owned Job)`, protocol requirements got default implementations
7170
Func SerialExecutor.enqueue(_:) has been added as a protocol requirement

0 commit comments

Comments
 (0)