Skip to content

Commit c02d4ed

Browse files
- enable 1.2 and change 1.1 with backwards compatibility to iOS 16
- attempt to throw errors properly
1 parent 6b1b6f5 commit c02d4ed

File tree

4 files changed

+98
-36
lines changed

4 files changed

+98
-36
lines changed

Package.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ let AsyncAlgorithms_v1_0 = "AvailabilityMacro=AsyncAlgorithms 1.0:macOS 10.15, i
77
#if compiler(>=6.0) && swift(>=6.0) // 5.10 doesnt support visionOS availability
88
let AsyncAlgorithms_v1_1 =
99
"AvailabilityMacro=AsyncAlgorithms 1.1:macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, visionOS 1.0"
10+
let AsyncAlgorithms_v1_2 =
11+
"AvailabilityMacro=AsyncAlgorithms 1.2:macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, visionOS 2.0"
1012
#else
1113
let AsyncAlgorithms_v1_1 = "AvailabilityMacro=AsyncAlgorithms 1.1:macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0"
14+
let AsyncAlgorithms_v1_2 =
15+
"AvailabilityMacro=AsyncAlgorithms 1.2:macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0"
1216
#endif
1317

1418
let availabilityMacros: [SwiftSetting] = [
@@ -18,6 +22,9 @@ let availabilityMacros: [SwiftSetting] = [
1822
.enableExperimentalFeature(
1923
AsyncAlgorithms_v1_1
2024
),
25+
.enableExperimentalFeature(
26+
AsyncAlgorithms_v1_2
27+
)
2128
]
2229

2330
let package = Package(

Sources/AsyncAlgorithms/FailableAsyncSequence.swift renamed to Sources/AsyncAlgorithms/AsyncFailureBackportable.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919
///
2020
/// Example:
2121
/// ```swift
22-
/// class MySequence: AsyncSequence, FailableAsyncSequence { ... }
22+
/// class MySequence: AsyncSequence, AsyncFailureBackportable { ... }
2323
///
2424
/// ```
2525
@available(AsyncAlgorithms 1.1, *)
26-
public protocol FailableAsyncSequence {
27-
typealias _Failure = Failure
26+
public protocol AsyncFailureBackportable {
27+
typealias BackportableFailure = Failure
2828
associatedtype Failure: Error
2929
}

Sources/AsyncAlgorithms/AsyncShareSequence.swift

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
// See https://swift.org/LICENSE.txt for license information
99
//
1010
//===----------------------------------------------------------------------===//
11+
#if compiler(>=6.2)
1112

1213
import Synchronization
1314
import DequeModule
@@ -133,7 +134,7 @@ where Base.Element: Sendable, Base: _SendableMetatype, Base.AsyncIterator: _Send
133134
// - `continuation`: The continuation waiting for the next element (nil if not waiting)
134135
// - `position`: The consumer's current position in the shared buffer
135136
struct State {
136-
var continuation: UnsafeContinuation<Result<Base.Element?, _Failure>, Never>?
137+
var continuation: UnsafeContinuation<Result<Base.Element?, Failure>, Never>?
137138
var position = 0
138139

139140
// Creates a new state with the position adjusted by the given offset.
@@ -585,11 +586,11 @@ where Base.Element: Sendable, Base: _SendableMetatype, Base.AsyncIterator: _Send
585586
}
586587
}
587588
} catch {
588-
emit(.failure(error))
589+
emit(.failure(error as! Failure))
589590
}
590591
}
591592

592-
func next(isolation actor: isolated (any Actor)?, id: Int) async rethrows -> Base.Element? {
593+
func next(isolation actor: isolated (any Actor)?, id: Int) async throws(Failure) -> Base.Element? {
593594
let iteratingTask = state.withLock { state -> IteratingTask in
594595
defer {
595596
if case .pending = state.iteratingTask {
@@ -694,27 +695,33 @@ where Base.Element: Sendable, Base: _SendableMetatype, Base.AsyncIterator: _Send
694695
}
695696

696697
@available(AsyncAlgorithms 1.1, *)
697-
extension AsyncShareSequence: AsyncSequence, FailableAsyncSequence {
698+
extension AsyncShareSequence: AsyncSequence, AsyncFailureBackportable {
698699
public typealias Element = Base.Element
699-
700-
public struct Iterator: AsyncIteratorProtocol {
700+
public struct Iterator: AsyncIteratorProtocol, _SendableMetatype {
701701
let side: Side
702702

703703
init(_ iteration: Iteration) {
704704
side = Side(iteration)
705705
}
706-
706+
707707
mutating public func next() async rethrows -> Element? {
708708
try await side.next(isolation: nil)
709709
}
710-
711-
mutating public func next(isolation actor: isolated (any Actor)?) async rethrows -> Element? {
712-
try await side.next(isolation: actor)
713-
}
710+
711+
// mutating public func next(isolation actor: isolated (any Actor)?) async throws(Self.BackportableFailure) -> Element? {
712+
// try await side.next(isolation: actor)
713+
// }
714714
}
715715

716716
public func makeAsyncIterator() -> Iterator {
717717
Iterator(extent.iteration)
718718
}
719719
}
720720

721+
@available(AsyncAlgorithms 1.2, *)
722+
extension AsyncShareSequence.Iterator {
723+
mutating public func next(isolation actor: isolated (any Actor)?) async throws(Base.Failure) -> Base.Element? {
724+
try await side.next(isolation: actor)
725+
}
726+
}
727+
#endif

Tests/AsyncAlgorithmsTests/TestShare.swift

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ final class TestShare: XCTestCase {
3131
var iterator = shared.makeAsyncIterator()
3232
gate1.open()
3333
await gate2.enter()
34-
while let value = await iterator.next(isolation: nil) {
34+
while let value = await iterator.next() {
3535
results.append(value)
3636
}
3737
return results
@@ -42,7 +42,7 @@ final class TestShare: XCTestCase {
4242
var iterator = shared.makeAsyncIterator()
4343
gate2.open()
4444
await gate1.enter()
45-
while let value = await iterator.next(isolation: nil) {
45+
while let value = await iterator.next() {
4646
results.append(value)
4747
}
4848
return results
@@ -94,13 +94,13 @@ final class TestShare: XCTestCase {
9494
gate1.open()
9595
await gate2.enter()
9696
// Consumer 1 reads first element
97-
if let value = await iterator.next(isolation: nil) {
97+
if let value = await iterator.next() {
9898
results1.withLock { $0.append(value) }
9999
}
100100
// Delay to allow consumer 2 to get ahead
101101
try? await Task.sleep(nanoseconds: 10_000_000)
102102
// Continue reading
103-
while let value = await iterator.next(isolation: nil) {
103+
while let value = await iterator.next() {
104104
results1.withLock { $0.append(value) }
105105
}
106106
}
@@ -110,7 +110,7 @@ final class TestShare: XCTestCase {
110110
gate2.open()
111111
await gate1.enter()
112112
// Consumer 2 reads all elements quickly
113-
while let value = await iterator.next(isolation: nil) {
113+
while let value = await iterator.next() {
114114
results2.withLock { $0.append(value) }
115115
}
116116
}
@@ -143,7 +143,7 @@ final class TestShare: XCTestCase {
143143
var iterator = shared.makeAsyncIterator()
144144
gate2.open()
145145
await gate1.enter()
146-
while let value = await iterator.next(isolation: nil) {
146+
while let value = await iterator.next() {
147147
results1.withLock { $0.append(value) }
148148
// Add some delay to consumer 1
149149
try? await Task.sleep(nanoseconds: 1_000_000)
@@ -154,7 +154,7 @@ final class TestShare: XCTestCase {
154154
var iterator = shared.makeAsyncIterator()
155155
gate1.open()
156156
await gate2.enter()
157-
while let value = await iterator.next(isolation: nil) {
157+
while let value = await iterator.next() {
158158
results2.withLock { $0.append(value) }
159159
}
160160
}
@@ -179,7 +179,7 @@ final class TestShare: XCTestCase {
179179
var iterator = shared.makeAsyncIterator()
180180
gate2.open()
181181
await gate1.enter()
182-
while let value = await iterator.next(isolation: nil) {
182+
while let value = await iterator.next() {
183183
fastResults.withLock { $0.append(value) }
184184
}
185185
}
@@ -189,13 +189,13 @@ final class TestShare: XCTestCase {
189189
gate1.open()
190190
await gate2.enter()
191191
// Read first element immediately
192-
if let value = await iterator.next(isolation: nil) {
192+
if let value = await iterator.next() {
193193
slowResults.withLock { $0.append(value) }
194194
}
195195
// Add significant delay to let buffer fill up and potentially overflow
196196
try? await Task.sleep(nanoseconds: 50_000_000)
197197
// Continue reading remaining elements
198-
while let value = await iterator.next(isolation: nil) {
198+
while let value = await iterator.next() {
199199
slowResults.withLock { $0.append(value) }
200200
}
201201
}
@@ -251,7 +251,7 @@ final class TestShare: XCTestCase {
251251
var iterator = shared.makeAsyncIterator()
252252
gate2.open()
253253
await gate1.enter()
254-
while let value = await iterator.next(isolation: nil) {
254+
while let value = await iterator.next() {
255255
fastResults.withLock { $0.append(value) }
256256
}
257257
}
@@ -261,13 +261,13 @@ final class TestShare: XCTestCase {
261261
gate1.open()
262262
await gate2.enter()
263263
// Read first element immediately
264-
if let value = await iterator.next(isolation: nil) {
264+
if let value = await iterator.next() {
265265
slowResults.withLock { $0.append(value) }
266266
}
267267
// Add significant delay to let buffer fill up and potentially overflow
268268
try? await Task.sleep(nanoseconds: 50_000_000)
269269
// Continue reading remaining elements
270-
while let value = await iterator.next(isolation: nil) {
270+
while let value = await iterator.next() {
271271
slowResults.withLock { $0.append(value) }
272272
}
273273
}
@@ -397,11 +397,11 @@ final class TestShare: XCTestCase {
397397

398398
let task = Task {
399399
var iterator = shared.makeAsyncIterator()
400-
if await iterator.next(isolation: nil) != nil {
400+
if await iterator.next() != nil {
401401
iterated.fulfill()
402402
}
403403
// Task will be cancelled here, so iteration should stop
404-
while await iterator.next(isolation: nil) != nil {
404+
while await iterator.next() != nil {
405405
// Continue iterating until cancelled
406406
}
407407
finished.fulfill()
@@ -480,7 +480,7 @@ final class TestShare: XCTestCase {
480480
// Start early consumer
481481
let earlyConsumer = Task {
482482
var iterator = shared.makeAsyncIterator()
483-
while let value = await iterator.next(isolation: nil) {
483+
while let value = await iterator.next() {
484484
earlyResults.withLock { $0.append(value) }
485485
}
486486
}
@@ -495,7 +495,7 @@ final class TestShare: XCTestCase {
495495
// Start late consumer
496496
let lateConsumer = Task {
497497
var iterator = shared.makeAsyncIterator()
498-
while let value = await iterator.next(isolation: nil) {
498+
while let value = await iterator.next() {
499499
lateResults.withLock { $0.append(value) }
500500
}
501501
}
@@ -522,11 +522,11 @@ final class TestShare: XCTestCase {
522522
var iterator2 = shared.makeAsyncIterator()
523523

524524
// Both iterators should independently get the same elements
525-
let value1a = await iterator1.next(isolation: nil)
526-
let value2a = await iterator2.next(isolation: nil)
525+
let value1a = await iterator1.next()
526+
let value2a = await iterator2.next()
527527

528-
let value1b = await iterator1.next(isolation: nil)
529-
let value2b = await iterator2.next(isolation: nil)
528+
let value1b = await iterator1.next()
529+
let value2b = await iterator2.next()
530530

531531
XCTAssertEqual(value1a, 1)
532532
XCTAssertEqual(value2a, 1)
@@ -549,7 +549,7 @@ final class TestShare: XCTestCase {
549549

550550
// Create a new iterator after the sequence finished
551551
var newIterator = shared.makeAsyncIterator()
552-
let value = await newIterator.next(isolation: nil)
552+
let value = await newIterator.next()
553553
XCTAssertNil(value) // Should return nil since source is exhausted
554554
}
555555

@@ -572,6 +572,37 @@ final class TestShare: XCTestCase {
572572
XCTAssertEqual(results1, [1, 2, 3, 4, 5])
573573
XCTAssertEqual(results2, []) // Should be empty since source is exhausted
574574
}
575+
576+
@available(AsyncAlgorithms 1.1, *)
577+
func test_share_rethrows_failure_type_on_backported() async {
578+
let shared = AsyncThrowingStream<Void, Error> {
579+
$0.finish(throwing: TestError.failure)
580+
}.share()
581+
do {
582+
for try await _ in shared {
583+
XCTFail("Expected to not get here")
584+
}
585+
} catch {
586+
XCTAssertEqual(error as? TestError, .failure)
587+
}
588+
}
589+
590+
func test_share_rethrows_failure_type_without_falling_back_to_any_error() async {
591+
if #available(AsyncAlgorithms 1.2, *) {
592+
// Ensure - at compile time - that error is effectively a TestError
593+
let shared: some AsyncSequence<Void, TestError> = AlwaysFailingSequence().share()
594+
do {
595+
for try await _ in shared {
596+
XCTFail("Expected to not get here")
597+
}
598+
} catch {
599+
600+
XCTAssertEqual(error, TestError.failure)
601+
}
602+
} else {
603+
// not available
604+
}
605+
}
575606
}
576607

577608
// MARK: - Helper Types
@@ -580,4 +611,21 @@ private enum TestError: Error, Equatable {
580611
case failure
581612
}
582613

614+
@available(AsyncAlgorithms 1.2, *)
615+
/// A sequence used to properly test concrete error on 1.2
616+
private struct AlwaysFailingSequence: AsyncSequence, Sendable {
617+
init() {}
618+
619+
func makeAsyncIterator() -> AsyncIterator { AsyncIterator() }
620+
621+
struct AsyncIterator: AsyncIteratorProtocol, Sendable {
622+
623+
func next() async throws(TestError) -> Void? {
624+
throw TestError.failure
625+
}
626+
mutating func next(completion: @escaping (Result<Element?, TestError>) -> Void) async throws(TestError) -> Element? {
627+
throw TestError.failure
628+
}
629+
}
630+
}
583631
#endif

0 commit comments

Comments
 (0)