From 12755c3586e193377dead0a6bfe1088cedb9c40d Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Sat, 14 Feb 2026 23:36:07 +0100 Subject: [PATCH 1/5] When with Parameter Packs --- Sources/when.swift | 8 ++++++++ Tests/CorePromise/WhenTests.swift | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/Sources/when.swift b/Sources/when.swift index d457aa7f9..d6f61abab 100644 --- a/Sources/when.swift +++ b/Sources/when.swift @@ -110,6 +110,14 @@ public func when(fulfilled thenables: [any Thenable]) -> Promise<[Any]> { } #endif +#if swift(>=5.9) +public func when(fulfilled: repeat each U) -> Promise<(repeat (each U).T)> { + var voidPromises: [Promise] = [] + repeat voidPromises.append((each fulfilled).asVoid()) + return _when(voidPromises).map(on: nil) { (repeat (each fulfilled).value!) } +} +#endif + /// Wait for all promises in a set to fulfill. public func when(fulfilled promises: U...) -> Promise where U.T == Void { return _when(promises) diff --git a/Tests/CorePromise/WhenTests.swift b/Tests/CorePromise/WhenTests.swift index d1efd74a9..fc6be9c58 100644 --- a/Tests/CorePromise/WhenTests.swift +++ b/Tests/CorePromise/WhenTests.swift @@ -57,6 +57,25 @@ class WhenTests: XCTestCase { #endif } + func testParameterPacks() { + #if swift(>=5.9) + let e1 = expectation(description: "") + let p1 = Promise.value(1) + let g2 = Guarantee.value(2) + let p3 = Promise.value(3) + let g4 = Guarantee.value(4) + + when(fulfilled: p1, g2, p3, g4).done { x1, x2, x3, x4 in + XCTAssertEqual(x1, 1) + XCTAssertEqual(x2, 2) + XCTAssertEqual(x3, 3) + XCTAssertEqual(x4, 4) + e1.fulfill() + }.silenceWarning() + waitForExpectations(timeout: 1, handler: nil) + #endif + } + func testDoubleTuple() { let e1 = expectation(description: "") let p1 = Promise.value(1) From b0c1dd4407b7ed5a3cbe32101c86306169b0abb7 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Sat, 14 Feb 2026 23:42:30 +0100 Subject: [PATCH 2/5] More complex tests --- Tests/CorePromise/WhenTests.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/CorePromise/WhenTests.swift b/Tests/CorePromise/WhenTests.swift index fc6be9c58..cbc795fa5 100644 --- a/Tests/CorePromise/WhenTests.swift +++ b/Tests/CorePromise/WhenTests.swift @@ -61,15 +61,15 @@ class WhenTests: XCTestCase { #if swift(>=5.9) let e1 = expectation(description: "") let p1 = Promise.value(1) - let g2 = Guarantee.value(2) - let p3 = Promise.value(3) - let g4 = Guarantee.value(4) + let g2 = Guarantee.value("Hello") + let p3 = Promise.value("world") + let g4 = Guarantee.value(2) when(fulfilled: p1, g2, p3, g4).done { x1, x2, x3, x4 in XCTAssertEqual(x1, 1) - XCTAssertEqual(x2, 2) - XCTAssertEqual(x3, 3) - XCTAssertEqual(x4, 4) + XCTAssertEqual(x2, "Hello") + XCTAssertEqual(x3, "world") + XCTAssertEqual(x4, 2) e1.fulfill() }.silenceWarning() waitForExpectations(timeout: 1, handler: nil) From fd26a9256532e5fe7fdaa303476319ef1d68ebd1 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Sun, 15 Feb 2026 09:18:55 +0100 Subject: [PATCH 3/5] Update CI to run on Ubuntu 22.04 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7be614892..13fb7b013 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ concurrency: jobs: linux-build: name: Linux (Swift ${{ matrix.swift }}) - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: swift: From 675ddf9bc94b1d5c71e5303c611ebc8c8ced9f72 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Sun, 15 Feb 2026 09:38:33 +0100 Subject: [PATCH 4/5] Update CI workflow to use newer OS versions --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13fb7b013..17066d2b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,7 +78,7 @@ jobs: if: ${{ matrix.swift >= 6 }} test: - runs-on: macos-latest + runs-on: macos-26 name: ${{ matrix.platform }} continue-on-error: true strategy: @@ -101,7 +101,7 @@ jobs: test-android: name: Android - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - uses: skiptools/swift-android-action@v2 From 999ba15fd2ba7ca811f9b9215a7f29ca0c54e175 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Sun, 15 Feb 2026 10:05:33 +0100 Subject: [PATCH 5/5] Fix GitHub Actions --- .github/workflows/ci.yml | 4 ++-- Tests/CorePromise/CancellableErrorTests.swift | 7 ------- Tests/CorePromise/RaceTests.swift | 13 ++++++++----- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17066d2b2..13fb7b013 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,7 +78,7 @@ jobs: if: ${{ matrix.swift >= 6 }} test: - runs-on: macos-26 + runs-on: macos-latest name: ${{ matrix.platform }} continue-on-error: true strategy: @@ -101,7 +101,7 @@ jobs: test-android: name: Android - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: skiptools/swift-android-action@v2 diff --git a/Tests/CorePromise/CancellableErrorTests.swift b/Tests/CorePromise/CancellableErrorTests.swift index 871efa248..5721832cf 100644 --- a/Tests/CorePromise/CancellableErrorTests.swift +++ b/Tests/CorePromise/CancellableErrorTests.swift @@ -100,13 +100,6 @@ class CancellationTests: XCTestCase { } func testDoesntCrashSwift() { - #if os(macOS) - // Previously exposed a bridging crash in Swift - // NOTE nobody was brave enough or diligent enough to report this to Apple :{ - // NOTE no Linux test since this constructor doesn’t exist there - XCTAssertFalse(NSError().isCancelled) - #endif - #if canImport(StoreKit) if #available(watchOS 6.2, *) { do { diff --git a/Tests/CorePromise/RaceTests.swift b/Tests/CorePromise/RaceTests.swift index 45d7ef0b7..9d079bf81 100644 --- a/Tests/CorePromise/RaceTests.swift +++ b/Tests/CorePromise/RaceTests.swift @@ -76,14 +76,17 @@ class RaceTests: XCTestCase { func testFulfilledWithNoWinner() { enum Error: Swift.Error { case test1, test2 } let ex = expectation(description: "") - let promises: [Promise] = [after(seconds: 1).map { _ in throw Error.test1 }, after(seconds: 2).map { _ in throw Error.test2 }] + let promises: [Promise] = [ + after(seconds: 1).map { _ in throw Error.test1 }, + after(seconds: 2).map { _ in throw Error.test2 } + ] race(fulfilled: promises).done { _ in - XCTFail() + XCTFail("Done with error") ex.fulfill() }.catch { - guard let pmkError = $0 as? PMKError else { return XCTFail() } - guard case .noWinner = pmkError else { return XCTFail() } - guard pmkError.debugDescription == "All thenables passed to race(fulfilled:) were rejected" else { return XCTFail() } + guard let pmkError = $0 as? PMKError else { return XCTFail("error is not PMKError") } + guard case .noWinner = pmkError else { return XCTFail("error is not .noWinner") } + guard pmkError.debugDescription == "All thenables passed to race(fulfilled:) were rejected" else { return XCTFail("Not all thenables passed to race(fulfilled:) were rejected") } ex.fulfill() } wait(for: [ex], timeout: 10)