From 9612a02e9a6d2f9ca141ecabb854bd025a361d0c Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 1 Oct 2025 11:22:08 +0100 Subject: [PATCH 1/2] [NFC] Remove ConcreteAsyncSequence --- .../ConfigReader+internalHelpers.swift | 6 +- .../Configuration/ConfigSnapshotReader.swift | 2 +- Sources/Configuration/MultiProvider.swift | 4 +- .../Wrappers/KeyMappingProvider.swift | 2 +- .../Utilities/AsyncSequences.swift | 65 ------------------- .../AsyncSequencesTests.swift | 33 ---------- .../MultiProviderTests.swift | 4 +- 7 files changed, 9 insertions(+), 107 deletions(-) diff --git a/Sources/Configuration/ConfigReader+internalHelpers.swift b/Sources/Configuration/ConfigReader+internalHelpers.swift index 3255240..ac7cb5b 100644 --- a/Sources/Configuration/ConfigReader+internalHelpers.swift +++ b/Sources/Configuration/ConfigReader+internalHelpers.swift @@ -639,7 +639,7 @@ extension ConfigReader { ) async throws -> Return { let absoluteKey = keyPrefix.appending(key) return try await provider.watchValue(forKey: absoluteKey, type: type) { updates in - let mappedUpdates = ConcreteAsyncSequence(updates) + let mappedUpdates = updates .map { updateTuple in let (providerResults, value) = mergingIsSecret( isSecret, @@ -715,7 +715,7 @@ extension ConfigReader { ) async throws -> Return { let absoluteKey = keyPrefix.appending(key) return try await provider.watchValue(forKey: absoluteKey, type: type) { updates in - let mappedUpdates = ConcreteAsyncSequence(updates) + let mappedUpdates = updates .map { updateTuple in let (providerResults, value) = mergingIsSecret( isSecret, @@ -790,7 +790,7 @@ extension ConfigReader { ) async throws -> Return { let absoluteKey = keyPrefix.appending(key) return try await provider.watchValue(forKey: absoluteKey, type: type) { updates in - let mappedUpdates = ConcreteAsyncSequence(updates) + let mappedUpdates = updates .mapThrowing { updateTuple in let (providerResults, value) = mergingIsSecret( isSecret, diff --git a/Sources/Configuration/ConfigSnapshotReader.swift b/Sources/Configuration/ConfigSnapshotReader.swift index 53e2f21..3e749f5 100644 --- a/Sources/Configuration/ConfigSnapshotReader.swift +++ b/Sources/Configuration/ConfigSnapshotReader.swift @@ -363,7 +363,7 @@ extension ConfigReader { try await provider.watchSnapshot { updates in try await updatesHandler( ConfigUpdatesAsyncSequence( - ConcreteAsyncSequence(updates) + updates .map { multiSnapshot in ConfigSnapshotReader( keyPrefix: keyPrefix, diff --git a/Sources/Configuration/MultiProvider.swift b/Sources/Configuration/MultiProvider.swift index e33dc05..2379b44 100644 --- a/Sources/Configuration/MultiProvider.swift +++ b/Sources/Configuration/MultiProvider.swift @@ -205,7 +205,7 @@ extension MultiProvider { updatesHandler: { updateArrays in try await body( ConfigUpdatesAsyncSequence( - ConcreteAsyncSequence(updateArrays) + updateArrays .map { array in MultiSnapshot(snapshots: array) } @@ -302,7 +302,7 @@ extension MultiProvider { updatesHandler: { updateArrays in try await updatesHandler( ConfigUpdatesAsyncSequence( - ConcreteAsyncSequence(updateArrays) + updateArrays .map { array in var results: [AccessEvent.ProviderResult] = [] for (providerIndex, lookupResult) in array.enumerated() { diff --git a/Sources/Configuration/Providers/Wrappers/KeyMappingProvider.swift b/Sources/Configuration/Providers/Wrappers/KeyMappingProvider.swift index 97a6335..34d7874 100644 --- a/Sources/Configuration/Providers/Wrappers/KeyMappingProvider.swift +++ b/Sources/Configuration/Providers/Wrappers/KeyMappingProvider.swift @@ -109,7 +109,7 @@ extension KeyMappingProvider: ConfigProvider { try await upstream.watchSnapshot { sequence in try await updatesHandler( ConfigUpdatesAsyncSequence( - ConcreteAsyncSequence(sequence) + sequence .map { snapshot in MappedKeySnapshot(mapKey: self.mapKey, upstream: self.upstream.snapshot()) } diff --git a/Sources/Configuration/Utilities/AsyncSequences.swift b/Sources/Configuration/Utilities/AsyncSequences.swift index 252c879..23fb959 100644 --- a/Sources/Configuration/Utilities/AsyncSequences.swift +++ b/Sources/Configuration/Utilities/AsyncSequences.swift @@ -55,71 +55,6 @@ extension ConfigUpdatesAsyncSequence: AsyncSequence { } } -/// A concrete async sequence that wraps an existential async sequence. -/// -/// This type provides a concrete implementation of `AsyncSequence` that wraps -/// an existential `any AsyncSequence`. It serves as a workaround for limitations -/// in Swift's type system where certain operations like `map` and other sequence -/// transformations don't work directly with existential async sequences. -/// -/// ## Purpose -/// -/// The Swift standard library's async sequence operations require concrete types -/// to function properly. When working with `any AsyncSequence`, these operations -/// are not available. This wrapper provides a concrete type that enables the use -/// of standard async sequence operations. -/// -/// ## Usage -/// -/// ```swift -/// let existentialSequence: any AsyncSequence = someAsyncSequence -/// let concreteSequence = ConcreteAsyncSequence(existentialSequence) -/// -/// // Now you can use standard async sequence operations -/// let mappedSequence = concreteSequence.map { $0 * 2 } -/// ``` -package struct ConcreteAsyncSequence { - - /// The upstream async sequence that this concrete sequence wraps. - /// - /// This property holds the async sequence that provides the actual elements. - /// All operations on this concrete sequence are delegated to this upstream sequence. - var upstream: any AsyncSequence - - /// Creates a new concrete async sequence wrapping the provided existential sequence. - /// - /// - Parameter upstream: The async sequence to wrap. - package init(_ upstream: any AsyncSequence) { - self.upstream = upstream - } -} - -extension ConcreteAsyncSequence: AsyncSequence { - - /// An async iterator that wraps an existential async iterator. - /// - /// This iterator provides the concrete implementation for iterating over - /// the wrapped existential async sequence. It delegates all operations - /// to the upstream iterator while maintaining type safety. - package struct Iterator: AsyncIteratorProtocol { - - /// The upstream async iterator that provides the actual iteration logic. - var upstream: any AsyncIteratorProtocol - - // swift-format-ignore: AllPublicDeclarationsHaveDocumentation - package mutating func next( - isolation actor: isolated (any Actor)? - ) async throws(Failure) -> Element? { - try await upstream.next(isolation: actor) - } - } - - // swift-format-ignore: AllPublicDeclarationsHaveDocumentation - package func makeAsyncIterator() -> Iterator { - Iterator(upstream: upstream.makeAsyncIterator()) - } -} - // MARK: - AsyncSequence extensions extension AsyncSequence where Failure == Never { diff --git a/Tests/ConfigurationTests/AsyncSequencesTests.swift b/Tests/ConfigurationTests/AsyncSequencesTests.swift index 05d0d2e..613d7e2 100644 --- a/Tests/ConfigurationTests/AsyncSequencesTests.swift +++ b/Tests/ConfigurationTests/AsyncSequencesTests.swift @@ -57,39 +57,6 @@ struct AsyncSequencesTests { #expect(results == [1, 2]) } - // MARK: - ConcreteAsyncSequence tests - - @Test func concreteAsyncSequenceWrapsExistentialSequence() async throws { - let values = [1, 2, 3, 4, 5] - let concreteSequence = ConcreteAsyncSequence(values.async) - let results = await concreteSequence.collect() - #expect(results == values) - } - - @Test func concreteAsyncSequenceWithEmptySequence() async throws { - let concreteSequence = ConcreteAsyncSequence(([] as [Int]).async) - let results = await concreteSequence.collect() - #expect(results.isEmpty) - } - - @Test func concreteAsyncSequencePropagatesErrors() async throws { - let throwingSequence = AsyncThrowingStream { continuation in - continuation.yield(1) - continuation.yield(2) - continuation.finish(throwing: TestError.upstreamFailed) - } - let concreteSequence = ConcreteAsyncSequence(throwingSequence) - - var results: [Int] = [] - let error = await #expect(throws: TestError.self) { - for try await value in concreteSequence { - results.append(value) - } - } - #expect(error == .upstreamFailed) - #expect(results == [1, 2]) - } - // MARK: - mapThrowing Tests @Test func mapThrowingSuccessfulTransformation() async throws { diff --git a/Tests/ConfigurationTests/MultiProviderTests.swift b/Tests/ConfigurationTests/MultiProviderTests.swift index f6758c0..505711f 100644 --- a/Tests/ConfigurationTests/MultiProviderTests.swift +++ b/Tests/ConfigurationTests/MultiProviderTests.swift @@ -95,7 +95,7 @@ struct MultiProviderTests { try await multiProvider.watchValue(forKey: key, type: type) { updates in try await handler( ConfigUpdatesAsyncSequence( - ConcreteAsyncSequence(updates) + updates .map { update in update.1.map { .init(encodedKey: "", value: $0) } } @@ -116,7 +116,7 @@ struct MultiProviderTests { try await multiProvider.watchSnapshot { updates in try await updatesHandler( ConfigUpdatesAsyncSequence( - ConcreteAsyncSequence(updates).map { $0 } + updates.map { $0 } ) ) } From d7768673bf51b96e9740c74c43077180baec8470 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 6 Oct 2025 10:35:02 +0200 Subject: [PATCH 2/2] Format --- Sources/Configuration/ConfigReader+internalHelpers.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/Configuration/ConfigReader+internalHelpers.swift b/Sources/Configuration/ConfigReader+internalHelpers.swift index ac7cb5b..73eb282 100644 --- a/Sources/Configuration/ConfigReader+internalHelpers.swift +++ b/Sources/Configuration/ConfigReader+internalHelpers.swift @@ -639,7 +639,8 @@ extension ConfigReader { ) async throws -> Return { let absoluteKey = keyPrefix.appending(key) return try await provider.watchValue(forKey: absoluteKey, type: type) { updates in - let mappedUpdates = updates + let mappedUpdates = + updates .map { updateTuple in let (providerResults, value) = mergingIsSecret( isSecret, @@ -715,7 +716,8 @@ extension ConfigReader { ) async throws -> Return { let absoluteKey = keyPrefix.appending(key) return try await provider.watchValue(forKey: absoluteKey, type: type) { updates in - let mappedUpdates = updates + let mappedUpdates = + updates .map { updateTuple in let (providerResults, value) = mergingIsSecret( isSecret, @@ -790,7 +792,8 @@ extension ConfigReader { ) async throws -> Return { let absoluteKey = keyPrefix.appending(key) return try await provider.watchValue(forKey: absoluteKey, type: type) { updates in - let mappedUpdates = updates + let mappedUpdates = + updates .mapThrowing { updateTuple in let (providerResults, value) = mergingIsSecret( isSecret,