Skip to content

Commit aa7a6c5

Browse files
fix: crash on pre 1.2
1 parent c02d4ed commit aa7a6c5

File tree

4 files changed

+57
-67
lines changed

4 files changed

+57
-67
lines changed

Sources/AsyncAlgorithms/AsyncFailureBackportable.swift

Lines changed: 0 additions & 29 deletions
This file was deleted.

Sources/AsyncAlgorithms/AsyncShareSequence.swift

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ where Base.Element: Sendable, Base: _SendableMetatype, Base.AsyncIterator: _Send
129129
// **Usage**: Tracks buffer position and manages async continuations
130130
// **Cleanup**: Automatically unregisters and cancels pending operations on deinit
131131
final class Side {
132+
// Due to a runtime crash in 1.0 compatible versions, it's not possible to handle
133+
// a generic failure constrained to Base.Failure. We handle inner failure with a `any Error`
134+
// and force unwrap it to the generic 1.2 generic type on the outside Iterator.
135+
typealias Failure = any Error
132136
// Tracks the state of a single consumer's iteration.
133137
//
134138
// - `continuation`: The continuation waiting for the next element (nil if not waiting)
@@ -180,6 +184,7 @@ where Base.Element: Sendable, Base: _SendableMetatype, Base.AsyncIterator: _Send
180184
// All operations are synchronized using a `Mutex` to ensure thread-safe access
181185
// to the shared state across multiple concurrent consumers.
182186
final class Iteration: Sendable {
187+
typealias Failure = Side.Failure
183188
// Represents the state of the background task that consumes the source sequence.
184189
//
185190
// The iteration task goes through several states during its lifecycle:
@@ -586,7 +591,7 @@ where Base.Element: Sendable, Base: _SendableMetatype, Base.AsyncIterator: _Send
586591
}
587592
}
588593
} catch {
589-
emit(.failure(error as! Failure))
594+
emit(.failure(error))
590595
}
591596
}
592597

@@ -695,8 +700,10 @@ where Base.Element: Sendable, Base: _SendableMetatype, Base.AsyncIterator: _Send
695700
}
696701

697702
@available(AsyncAlgorithms 1.1, *)
698-
extension AsyncShareSequence: AsyncSequence, AsyncFailureBackportable {
703+
extension AsyncShareSequence: AsyncSequence {
699704
public typealias Element = Base.Element
705+
@available(AsyncAlgorithms 1.2, *)
706+
public typealias Failure = Base.Failure
700707
public struct Iterator: AsyncIteratorProtocol, _SendableMetatype {
701708
let side: Side
702709

@@ -707,21 +714,23 @@ extension AsyncShareSequence: AsyncSequence, AsyncFailureBackportable {
707714
mutating public func next() async rethrows -> Element? {
708715
try await side.next(isolation: nil)
709716
}
710-
711-
// mutating public func next(isolation actor: isolated (any Actor)?) async throws(Self.BackportableFailure) -> Element? {
712-
// try await side.next(isolation: actor)
713-
// }
717+
718+
@available(AsyncAlgorithms 1.2, *)
719+
mutating public func next(isolation actor: isolated (any Actor)?) async throws(Failure) -> Element? {
720+
do {
721+
return try await side.next(isolation: actor)
722+
} catch {
723+
// It's guaranteed to match `Failure` but we are keeping the internal `Side` and `Iteration`
724+
// constrained to `any Error` to prevent a compiler bug visible at runtime
725+
// on pre 1.2 operating systems
726+
throw error as! Failure
727+
}
728+
}
714729
}
715730

716731
public func makeAsyncIterator() -> Iterator {
717732
Iterator(extent.iteration)
718733
}
719734
}
720735

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-
}
727736
#endif
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// FailingSequence.swift
3+
// swift-async-algorithms
4+
//
5+
// Created by Stefano Mondino on 15/10/25.
6+
//
7+
8+
@available(AsyncAlgorithms 1.2, *)
9+
struct FailingSequence<Failure: Error>: AsyncSequence, Sendable {
10+
typealias Element = Void
11+
let error: Failure
12+
init(_ error: Failure) {
13+
self.error = error
14+
}
15+
func makeAsyncIterator() -> AsyncIterator { AsyncIterator(error: error) }
16+
17+
struct AsyncIterator: AsyncIteratorProtocol, Sendable {
18+
let error: Failure
19+
func next() async throws(Failure) -> Void? {
20+
throw error
21+
}
22+
mutating func next(completion: @escaping (Result<Element?, Failure>) -> Void) async throws(Failure) -> Element? {
23+
throw error
24+
}
25+
}
26+
}

Tests/AsyncAlgorithmsTests/TestShare.swift

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -589,18 +589,19 @@ final class TestShare: XCTestCase {
589589

590590
func test_share_rethrows_failure_type_without_falling_back_to_any_error() async {
591591
if #available(AsyncAlgorithms 1.2, *) {
592+
593+
592594
// 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)
595+
let shared: some AsyncSequence<Void, TestError> = FailingSequence(TestError.failure).share()
596+
do {
597+
for try await _ in shared {
598+
XCTFail("Expected to not get here")
599+
}
600+
} catch {
601+
XCTAssertEqual(error, TestError.failure)
601602
}
602603
} else {
603-
// not available
604+
_ = XCTSkip("This test is not available before 1.2")
604605
}
605606
}
606607
}
@@ -611,21 +612,4 @@ private enum TestError: Error, Equatable {
611612
case failure
612613
}
613614

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-
}
631615
#endif

0 commit comments

Comments
 (0)