From a725b752a7e1508e9c2a7e2c5f4e5ec37ddcecef Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Fri, 7 Jul 2023 15:31:41 +0200 Subject: [PATCH 1/3] Update Infallible's flatMap operators to maintain its contract --- .../Infallible/Infallible+Operators.swift | 58 ++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/RxSwift/Traits/Infallible/Infallible+Operators.swift b/RxSwift/Traits/Infallible/Infallible+Operators.swift index 792dafade..038067e37 100644 --- a/RxSwift/Traits/Infallible/Infallible+Operators.swift +++ b/RxSwift/Traits/Infallible/Infallible+Operators.swift @@ -263,7 +263,7 @@ extension InfallibleType { // MARK: - FlatMap extension InfallibleType { /** - Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. + Projects each element of an infallible sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. - seealso: [flatMap operator on reactivex.io](http://reactivex.io/documentation/operators/flatmap.html) @@ -271,13 +271,26 @@ extension InfallibleType { - returns: An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. */ public func flatMap(_ selector: @escaping (Element) -> Source) - -> Infallible { + -> Observable { + asObservable().flatMap(selector) + } + + /** + Projects each element of an infallible sequence to an infallible sequence and merges the resulting infallible sequences into one infallible sequence. + + - seealso: [flatMap operator on reactivex.io](http://reactivex.io/documentation/operators/flatmap.html) + + - parameter selector: A transform function to apply to each element. + - returns: An infallible sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence. + */ + public func flatMap(_ selector: @escaping (Element) -> Infallible) + -> Infallible { Infallible(asObservable().flatMap(selector)) } /** - Projects each element of an observable sequence into a new sequence of observable sequences and then - transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence. + Projects each element of an infallible sequence into a new sequence of observable sequences and then + transforms the observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence. It is a combination of `map` + `switchLatest` operator @@ -288,12 +301,29 @@ extension InfallibleType { Observable of Observable sequences and that at any point in time produces the elements of the most recent inner observable sequence that has been received. */ public func flatMapLatest(_ selector: @escaping (Element) -> Source) - -> Infallible { + -> Observable { + asObservable().flatMapLatest(selector) + } + + /** + Projects each element of an infallible sequence into a new sequence of infallible sequences and then + transforms the infallible sequence of infallible sequences into an infallible sequence producing values only from the most recent infallible sequence. + + It is a combination of `map` + `switchLatest` operator + + - seealso: [flatMapLatest operator on reactivex.io](http://reactivex.io/documentation/operators/flatmap.html) + + - parameter selector: A transform function to apply to each element. + - returns: An infallible sequence whose elements are the result of invoking the transform function on each element of source producing an + Infallible of Infallible sequences and that at any point in time produces the elements of the most recent inner infallible sequence that has been received. + */ + public func flatMapLatest(_ selector: @escaping (Element) -> Infallible) + -> Infallible { Infallible(asObservable().flatMapLatest(selector)) } /** - Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. + Projects each element of an infallible sequence to an observable sequence and merges the resulting observable sequences into one observable sequence. If element is received while there is some projected observable sequence being merged it will simply be ignored. - seealso: [flatMapFirst operator on reactivex.io](http://reactivex.io/documentation/operators/flatmap.html) @@ -302,7 +332,21 @@ extension InfallibleType { - returns: An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence that was received while no other sequence was being calculated. */ public func flatMapFirst(_ selector: @escaping (Element) -> Source) - -> Infallible { + -> Observable { + asObservable().flatMapFirst(selector) + } + + /** + Projects each element of an infallible sequence to an infallible sequence and merges the resulting infallible sequences into one infallible sequence. + If element is received while there is some projected infallible sequence being merged it will simply be ignored. + + - seealso: [flatMapFirst operator on reactivex.io](http://reactivex.io/documentation/operators/flatmap.html) + + - parameter selector: A transform function to apply to element that was observed while no observable is executing in parallel. + - returns: An infallible sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence that was received while no other sequence was being calculated. + */ + public func flatMapFirst(_ selector: @escaping (Element) -> Infallible) + -> Infallible { Infallible(asObservable().flatMapFirst(selector)) } } From ddc468e52222723b9d16291742994e7612d3dde2 Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Fri, 7 Jul 2023 15:51:36 +0200 Subject: [PATCH 2/3] Add some test code to see how overload resolution would work --- Tests/RxSwiftTests/Infallible+Tests.swift | 101 ++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/Tests/RxSwiftTests/Infallible+Tests.swift b/Tests/RxSwiftTests/Infallible+Tests.swift index 69dcb86b4..5b032d04e 100644 --- a/Tests/RxSwiftTests/Infallible+Tests.swift +++ b/Tests/RxSwiftTests/Infallible+Tests.swift @@ -331,6 +331,107 @@ extension InfallibleTest { } } +// MARK: - flatMap +extension InfallibleTest { + func testReturnsObservableWhenClosureReturnsObservable() { + let testObject = TestObject() + let scheduler = TestScheduler(initialClock: 0) + var values = [String]() + var disposed: UUID? + var failed: UUID? + + let infallible = scheduler.createColdObservable([ + .next(10, 0), + .next(20, 1), + .next(30, 2), + ]).asInfallible(onErrorFallbackTo: .empty()) + + // Specifying the type explicitly will help the compiler to infer types/find matching overload + // so we won't do that here. + let merged/*: Observable<_> */ = infallible.flatMap { number -> Observable in + if number == 1 { + return .error(testError) + } + return .from(Array(repeating: number, count: 2)) + } + + // Instead, use a witness method to confirm the type. + merged.iAmObservable() + + _ = merged.subscribe( + with: testObject, + onNext: { object, value in values.append(object.id.uuidString + "\(value)") }, + onError: { object, _ in failed = object.id }, + onCompleted: { _ in XCTFail("Unexpected completion") }, onDisposed: { disposed = $0.id } + ) + + scheduler.start() + + let uuid = testObject.id + XCTAssertEqual(values, [ + uuid.uuidString + "0", + uuid.uuidString + "0", + ]) + + XCTAssertEqual(failed, uuid) + XCTAssertEqual(disposed, uuid) + } + + func testReturnsInfallibleWhenClosureReturnsInfallible() { + let testObject = TestObject() + let scheduler = TestScheduler(initialClock: 0) + var values = [String]() + var disposed: UUID? + var completed: UUID? + + let infallible = scheduler.createColdObservable([ + .next(10, 0), + .next(20, 1), + .next(30, 2), + .completed(40), + ]).asInfallible(onErrorFallbackTo: .empty()) + + // Specifying the type explicitly will help the compiler to infer types/find matching overload + // so we won't do that here. + let merged/*: Infallible<_> */ = infallible.flatMap { number -> Infallible in + return .from(Array(repeating: number, count: 2)) + } + + // Instead, use a witness method to confirm the type. + merged.iAmInfallible() + + _ = merged.subscribe( + with: testObject, + onNext: { object, value in values.append(object.id.uuidString + "\(value)") }, + onCompleted: { completed = $0.id }, + onDisposed: { disposed = $0.id } + ) + + scheduler.start() + + let uuid = testObject.id + XCTAssertEqual(values, [ + uuid.uuidString + "0", + uuid.uuidString + "0", + uuid.uuidString + "1", + uuid.uuidString + "1", + uuid.uuidString + "2", + uuid.uuidString + "2", + ]) + + XCTAssertEqual(completed, uuid) + XCTAssertEqual(disposed, uuid) + } +} + private class TestObject: NSObject { var id = UUID() } + +private extension Infallible { + func iAmInfallible() {} +} + +private extension Observable { + func iAmObservable() {} +} From c9d1604b1053300d8e06e82dac4f8e7ddc19d47e Mon Sep 17 00:00:00 2001 From: Nikolay Kasyanov Date: Tue, 18 Jul 2023 12:01:13 +0200 Subject: [PATCH 3/3] Given even fewer hints to the compiler --- Tests/RxSwiftTests/Infallible+Tests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/RxSwiftTests/Infallible+Tests.swift b/Tests/RxSwiftTests/Infallible+Tests.swift index 5b032d04e..d6063bdda 100644 --- a/Tests/RxSwiftTests/Infallible+Tests.swift +++ b/Tests/RxSwiftTests/Infallible+Tests.swift @@ -348,9 +348,9 @@ extension InfallibleTest { // Specifying the type explicitly will help the compiler to infer types/find matching overload // so we won't do that here. - let merged/*: Observable<_> */ = infallible.flatMap { number -> Observable in + let merged/*: Observable<_> */ = infallible.flatMap { number in if number == 1 { - return .error(testError) + return Observable.error(testError) } return .from(Array(repeating: number, count: 2)) } @@ -393,7 +393,7 @@ extension InfallibleTest { // Specifying the type explicitly will help the compiler to infer types/find matching overload // so we won't do that here. - let merged/*: Infallible<_> */ = infallible.flatMap { number -> Infallible in + let merged/*: Infallible<_> */ = infallible.flatMap { number in return .from(Array(repeating: number, count: 2)) }