Repository: swiftlang/swift
Pull request #81064: [6.2][Concurrency] Add Hashable conformance to Async(Throwing)Stream.Continuation
Author: login: ktoso
Created: 2025-04-24T04:21:08Z | Merged: 2025-04-25T16:33:26Z

**Description**: Adds hashable conformance and was approved in SE over here https://forums.swift.org/t/accepted-se-0468-hashable-conformance-for-async-throwing-stream-continuation/79116

**Scope/Impact**: Adds a Hashable conformance to AsyncStream.Continuation
**Risk:** Low, adding a conformance
**Testing**: CI testing
**Reviewed by**: @ktoso // PR on behalf of @nickolas-pohilets 

**Original PR:** https://github.com/swiftlang/swift/pull/79457/files
**Radar:** rdar://149914179


Diff:
diff --git a/stdlib/public/Concurrency/AsyncStream.swift b/stdlib/public/Concurrency/AsyncStream.swift
index d678246efc7e7..ca6eb979695c1 100644
--- a/stdlib/public/Concurrency/AsyncStream.swift
+++ b/stdlib/public/Concurrency/AsyncStream.swift
@@ -475,6 +475,22 @@ extension AsyncStream: @unchecked Sendable where Element: Sendable { }
 @available(SwiftStdlib 5.1, *)
 extension AsyncStream.Continuation.YieldResult: Sendable where Element: Sendable { }
 
+@available(SwiftStdlib 6.2, *)
+extension AsyncStream.Continuation: Hashable {
+  @available(SwiftStdlib 6.2, *)
+  public func hash(into hasher: inout Hasher) {
+    return hasher.combine(ObjectIdentifier(storage))
+  }
+  @available(SwiftStdlib 6.2, *)
+  public var hashValue: Int {
+    return _hashValue(for: self)
+  }
+  @available(SwiftStdlib 6.2, *)
+  public static func == (lhs: Self, rhs: Self) -> Bool {
+    return lhs.storage === rhs.storage
+  }
+}
+
 #else
 @available(SwiftStdlib 5.1, *)
 @available(*, unavailable, message: "Unavailable in task-to-thread concurrency model")
diff --git a/stdlib/public/Concurrency/AsyncThrowingStream.swift b/stdlib/public/Concurrency/AsyncThrowingStream.swift
index 488ff9760f38d..3cdc5d0077bf0 100644
--- a/stdlib/public/Concurrency/AsyncThrowingStream.swift
+++ b/stdlib/public/Concurrency/AsyncThrowingStream.swift
@@ -521,6 +521,22 @@ extension AsyncThrowingStream: @unchecked Sendable where Element: Sendable { }
 @available(SwiftStdlib 5.1, *)
 extension AsyncThrowingStream.Continuation.YieldResult: Sendable where Element: Sendable { }
 
+@available(SwiftStdlib 6.2, *)
+extension AsyncThrowingStream.Continuation: Hashable {
+  @available(SwiftStdlib 6.2, *)
+  public func hash(into hasher: inout Hasher) {
+    return hasher.combine(ObjectIdentifier(storage))
+  }
+  @available(SwiftStdlib 6.2, *)
+  public var hashValue: Int {
+    return _hashValue(for: self)
+  }
+  @available(SwiftStdlib 6.2, *)
+  public static func == (lhs: Self, rhs: Self) -> Bool {
+    return lhs.storage === rhs.storage
+  }
+}
+
 #else
 @available(SwiftStdlib 5.1, *)
 @available(*, unavailable, message: "Unavailable in task-to-thread concurrency model")
diff --git a/test/Concurrency/Runtime/async_stream.swift b/test/Concurrency/Runtime/async_stream.swift
index 7f54de2165632..553d4d736bbd8 100644
--- a/test/Concurrency/Runtime/async_stream.swift
+++ b/test/Concurrency/Runtime/async_stream.swift
@@ -435,6 +435,24 @@ class NotSendable {}
         expectTrue(expectation.fulfilled)
       }
 
+      tests.test("continuation equality") {
+        let (_, continuation1) = AsyncStream<Int>.makeStream()
+        let (_, continuation2) = AsyncStream<Int>.makeStream()
+        expectTrue(continuation1 == continuation1)
+        expectTrue(continuation1 != continuation2)
+        expectTrue(continuation1.hashValue == continuation1.hashValue)
+        expectTrue(continuation1.hashValue != continuation2.hashValue)
+      }
+
+      tests.test("throwing continuation equality") {
+        let (_, continuation1) = AsyncThrowingStream<Int, Error>.makeStream()
+        let (_, continuation2) = AsyncThrowingStream<Int, Error>.makeStream()
+        expectTrue(continuation1 == continuation1)
+        expectTrue(continuation1 != continuation2)
+        expectTrue(continuation1.hashValue == continuation1.hashValue)
+        expectTrue(continuation1.hashValue != continuation2.hashValue)
+      }
+
       // MARK: - Multiple consumers
 
       tests.test("finish behavior with multiple consumers") {
diff --git a/test/abi/Inputs/macOS/arm64/concurrency/baseline b/test/abi/Inputs/macOS/arm64/concurrency/baseline
index b6ed4a9a6bc9a..8aa55b3098c58 100644
--- a/test/abi/Inputs/macOS/arm64/concurrency/baseline
+++ b/test/abi/Inputs/macOS/arm64/concurrency/baseline
@@ -164,12 +164,18 @@ _$sScS12ContinuationV15BufferingPolicyO15bufferingOldestyADyx__GSicAFmlFWC
 _$sScS12ContinuationV15BufferingPolicyO9unboundedyADyx__GAFmlFWC
 _$sScS12ContinuationV15BufferingPolicyOMa
 _$sScS12ContinuationV15BufferingPolicyOMn
+_$sScS12ContinuationV2eeoiySbAByx_G_ADtFZ
+_$sScS12ContinuationV4hash4intoys6HasherVz_tF
 _$sScS12ContinuationV5yield4withAB11YieldResultOyx__Gs0E0Oyxs5NeverOG_tF
 _$sScS12ContinuationV5yieldAB11YieldResultOyyt__GyytRszlF
 _$sScS12ContinuationV5yieldyAB11YieldResultOyx__GxnF
 _$sScS12ContinuationV6finishyyF
+_$sScS12ContinuationV9hashValueSivg
+_$sScS12ContinuationV9hashValueSivpMV
 _$sScS12ContinuationVMa
 _$sScS12ContinuationVMn
+_$sScS12ContinuationVyx_GSHsMc
+_$sScS12ContinuationVyx_GSQsMc
 _$sScS17makeAsyncIteratorScS0C0Vyx_GyF
 _$sScS8IteratorV4nextxSgyYaF
 _$sScS8IteratorV4nextxSgyYaFTu
@@ -359,12 +365,18 @@ _$sScs12ContinuationV15BufferingPolicyO15bufferingOldestyADyxq___GSicAFms5ErrorR
 _$sScs12ContinuationV15BufferingPolicyO9unboundedyADyxq___GAFms5ErrorR_r0_lFWC
 _$sScs12ContinuationV15BufferingPolicyOMa
 _$sScs12ContinuationV15BufferingPolicyOMn
+_$sScs12ContinuationV2eeoiySbAByxq__G_ADtFZ
+_$sScs12ContinuationV4hash4intoys6HasherVz_tF
 _$sScs12ContinuationV5yield4withAB11YieldResultOyxs5Error_p__Gs0E0OyxsAG_pG_tsAG_pRs_rlF
 _$sScs12ContinuationV5yieldAB11YieldResultOyytq___GyytRszrlF
 _$sScs12ContinuationV5yieldyAB11YieldResultOyxq___GxnF
 _$sScs12ContinuationV6finish8throwingyq_Sgn_tF
+_$sScs12ContinuationV9hashValueSivg
+_$sScs12ContinuationV9hashValueSivpMV
 _$sScs12ContinuationVMa
 _$sScs12ContinuationVMn
+_$sScs12ContinuationVyxq__GSHsMc
+_$sScs12ContinuationVyxq__GSQsMc
 _$sScs17makeAsyncIteratorScs0C0Vyxq__GyF
 _$sScs8IteratorV4nextxSgyYaKF
 _$sScs8IteratorV4nextxSgyYaKFTu
diff --git a/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts b/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts
index b6ed4a9a6bc9a..8aa55b3098c58 100644
--- a/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts
+++ b/test/abi/Inputs/macOS/arm64/concurrency/baseline-asserts
@@ -164,12 +164,18 @@ _$sScS12ContinuationV15BufferingPolicyO15bufferingOldestyADyx__GSicAFmlFWC
 _$sScS12ContinuationV15BufferingPolicyO9unboundedyADyx__GAFmlFWC
 _$sScS12ContinuationV15BufferingPolicyOMa
 _$sScS12ContinuationV15BufferingPolicyOMn
+_$sScS12ContinuationV2eeoiySbAByx_G_ADtFZ
+_$sScS12ContinuationV4hash4intoys6HasherVz_tF
 _$sScS12ContinuationV5yield4withAB11YieldResultOyx__Gs0E0Oyxs5NeverOG_tF
 _$sScS12ContinuationV5yieldAB11YieldResultOyyt__GyytRszlF
 _$sScS12ContinuationV5yieldyAB11YieldResultOyx__GxnF
 _$sScS12ContinuationV6finishyyF
+_$sScS12ContinuationV9hashValueSivg
+_$sScS12ContinuationV9hashValueSivpMV
 _$sScS12ContinuationVMa
 _$sScS12ContinuationVMn
+_$sScS12ContinuationVyx_GSHsMc
+_$sScS12ContinuationVyx_GSQsMc
 _$sScS17makeAsyncIteratorScS0C0Vyx_GyF
 _$sScS8IteratorV4nextxSgyYaF
 _$sScS8IteratorV4nextxSgyYaFTu
@@ -359,12 +365,18 @@ _$sScs12ContinuationV15BufferingPolicyO15bufferingOldestyADyxq___GSicAFms5ErrorR
 _$sScs12ContinuationV15BufferingPolicyO9unboundedyADyxq___GAFms5ErrorR_r0_lFWC
 _$sScs12ContinuationV15BufferingPolicyOMa
 _$sScs12ContinuationV15BufferingPolicyOMn
+_$sScs12ContinuationV2eeoiySbAByxq__G_ADtFZ
+_$sScs12ContinuationV4hash4intoys6HasherVz_tF
 _$sScs12ContinuationV5yield4withAB11YieldResultOyxs5Error_p__Gs0E0OyxsAG_pG_tsAG_pRs_rlF
 _$sScs12ContinuationV5yieldAB11YieldResultOyytq___GyytRszrlF
 _$sScs12ContinuationV5yieldyAB11YieldResultOyxq___GxnF
 _$sScs12ContinuationV6finish8throwingyq_Sgn_tF
+_$sScs12ContinuationV9hashValueSivg
+_$sScs12ContinuationV9hashValueSivpMV
 _$sScs12ContinuationVMa
 _$sScs12ContinuationVMn
+_$sScs12ContinuationVyxq__GSHsMc
+_$sScs12ContinuationVyxq__GSQsMc
 _$sScs17makeAsyncIteratorScs0C0Vyxq__GyF
 _$sScs8IteratorV4nextxSgyYaKF
 _$sScs8IteratorV4nextxSgyYaKFTu
diff --git a/test/abi/Inputs/macOS/x86_64/concurrency/baseline b/test/abi/Inputs/macOS/x86_64/concurrency/baseline
index b6ed4a9a6bc9a..8aa55b3098c58 100644
--- a/test/abi/Inputs/macOS/x86_64/concurrency/baseline
+++ b/test/abi/Inputs/macOS/x86_64/concurrency/baseline
@@ -164,12 +164,18 @@ _$sScS12ContinuationV15BufferingPolicyO15bufferingOldestyADyx__GSicAFmlFWC
 _$sScS12ContinuationV15BufferingPolicyO9unboundedyADyx__GAFmlFWC
 _$sScS12ContinuationV15BufferingPolicyOMa
 _$sScS12ContinuationV15BufferingPolicyOMn
+_$sScS12ContinuationV2eeoiySbAByx_G_ADtFZ
+_$sScS12ContinuationV4hash4intoys6HasherVz_tF
 _$sScS12ContinuationV5yield4withAB11YieldResultOyx__Gs0E0Oyxs5NeverOG_tF
 _$sScS12ContinuationV5yieldAB11YieldResultOyyt__GyytRszlF
 _$sScS12ContinuationV5yieldyAB11YieldResultOyx__GxnF
 _$sScS12ContinuationV6finishyyF
+_$sScS12ContinuationV9hashValueSivg
+_$sScS12ContinuationV9hashValueSivpMV
 _$sScS12ContinuationVMa
 _$sScS12ContinuationVMn
+_$sScS12ContinuationVyx_GSHsMc
+_$sScS12ContinuationVyx_GSQsMc
 _$sScS17makeAsyncIteratorScS0C0Vyx_GyF
 _$sScS8IteratorV4nextxSgyYaF
 _$sScS8IteratorV4nextxSgyYaFTu
@@ -359,12 +365,18 @@ _$sScs12ContinuationV15BufferingPolicyO15bufferingOldestyADyxq___GSicAFms5ErrorR
 _$sScs12ContinuationV15BufferingPolicyO9unboundedyADyxq___GAFms5ErrorR_r0_lFWC
 _$sScs12ContinuationV15BufferingPolicyOMa
 _$sScs12ContinuationV15BufferingPolicyOMn
+_$sScs12ContinuationV2eeoiySbAByxq__G_ADtFZ
+_$sScs12ContinuationV4hash4intoys6HasherVz_tF
 _$sScs12ContinuationV5yield4withAB11YieldResultOyxs5Error_p__Gs0E0OyxsAG_pG_tsAG_pRs_rlF
 _$sScs12ContinuationV5yieldAB11YieldResultOyytq___GyytRszrlF
 _$sScs12ContinuationV5yieldyAB11YieldResultOyxq___GxnF
 _$sScs12ContinuationV6finish8throwingyq_Sgn_tF
+_$sScs12ContinuationV9hashValueSivg
+_$sScs12ContinuationV9hashValueSivpMV
 _$sScs12ContinuationVMa
 _$sScs12ContinuationVMn
+_$sScs12ContinuationVyxq__GSHsMc
+_$sScs12ContinuationVyxq__GSQsMc
 _$sScs17makeAsyncIteratorScs0C0Vyxq__GyF
 _$sScs8IteratorV4nextxSgyYaKF
 _$sScs8IteratorV4nextxSgyYaKFTu
diff --git a/test/abi/Inputs/macOS/x86_64/concurrency/baseline-asserts b/test/abi/Inputs/macOS/x86_64/concurrency/baseline-asserts
index b6ed4a9a6bc9a..8aa55b3098c58 100644
--- a/test/abi/Inputs/macOS/x86_64/concurrency/baseline-asserts
+++ b/test/abi/Inputs/macOS/x86_64/concurrency/baseline-asserts
@@ -164,12 +164,18 @@ _$sScS12ContinuationV15BufferingPolicyO15bufferingOldestyADyx__GSicAFmlFWC
 _$sScS12ContinuationV15BufferingPolicyO9unboundedyADyx__GAFmlFWC
 _$sScS12ContinuationV15BufferingPolicyOMa
 _$sScS12ContinuationV15BufferingPolicyOMn
+_$sScS12ContinuationV2eeoiySbAByx_G_ADtFZ
+_$sScS12ContinuationV4hash4intoys6HasherVz_tF
 _$sScS12ContinuationV5yield4withAB11YieldResultOyx__Gs0E0Oyxs5NeverOG_tF
 _$sScS12ContinuationV5yieldAB11YieldResultOyyt__GyytRszlF
 _$sScS12ContinuationV5yieldyAB11YieldResultOyx__GxnF
 _$sScS12ContinuationV6finishyyF
+_$sScS12ContinuationV9hashValueSivg
+_$sScS12ContinuationV9hashValueSivpMV
 _$sScS12ContinuationVMa
 _$sScS12ContinuationVMn
+_$sScS12ContinuationVyx_GSHsMc
+_$sScS12ContinuationVyx_GSQsMc
 _$sScS17makeAsyncIteratorScS0C0Vyx_GyF
 _$sScS8IteratorV4nextxSgyYaF
 _$sScS8IteratorV4nextxSgyYaFTu
@@ -359,12 +365,18 @@ _$sScs12ContinuationV15BufferingPolicyO15bufferingOldestyADyxq___GSicAFms5ErrorR
 _$sScs12ContinuationV15BufferingPolicyO9unboundedyADyxq___GAFms5ErrorR_r0_lFWC
 _$sScs12ContinuationV15BufferingPolicyOMa
 _$sScs12ContinuationV15BufferingPolicyOMn
+_$sScs12ContinuationV2eeoiySbAByxq__G_ADtFZ
+_$sScs12ContinuationV4hash4intoys6HasherVz_tF
 _$sScs12ContinuationV5yield4withAB11YieldResultOyxs5Error_p__Gs0E0OyxsAG_pG_tsAG_pRs_rlF
 _$sScs12ContinuationV5yieldAB11YieldResultOyytq___GyytRszrlF
 _$sScs12ContinuationV5yieldyAB11YieldResultOyxq___GxnF
 _$sScs12ContinuationV6finish8throwingyq_Sgn_tF
+_$sScs12ContinuationV9hashValueSivg
+_$sScs12ContinuationV9hashValueSivpMV
 _$sScs12ContinuationVMa
 _$sScs12ContinuationVMn
+_$sScs12ContinuationVyxq__GSHsMc
+_$sScs12ContinuationVyxq__GSQsMc
 _$sScs17makeAsyncIteratorScs0C0Vyxq__GyF
 _$sScs8IteratorV4nextxSgyYaKF
 _$sScs8IteratorV4nextxSgyYaKFTu


Reviews:
user: login: DougGregor
body: LGTM, we can do the @inlinable bit in a follow-up
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-25T16:33:19Z
user: login: ktoso
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T02:05:26Z

Review comments:
diff_hunk: @@ -475,6 +475,22 @@ extension AsyncStream: @unchecked Sendable where Element: Sendable { }
 @available(SwiftStdlib 5.1, *)
 extension AsyncStream.Continuation.YieldResult: Sendable where Element: Sendable { }
 
+@available(SwiftStdlib 6.2, *)
+extension AsyncStream.Continuation: Hashable {
+  @available(SwiftStdlib 6.2, *)
path: stdlib/public/Concurrency/AsyncStream.swift
user: login: DougGregor
body: Should these all be `@inlinable`?
created_at: 2025-04-25T16:32:54Z
author_association: MEMBER
diff_hunk: @@ -521,6 +521,22 @@ extension AsyncThrowingStream: @unchecked Sendable where Element: Sendable { }
 @available(SwiftStdlib 5.1, *)
 extension AsyncThrowingStream.Continuation.YieldResult: Sendable where Element: Sendable { }
 
+@available(SwiftStdlib 6.2, *)
+extension AsyncThrowingStream.Continuation: Hashable {
+  @available(SwiftStdlib 6.2, *)
+  public func hash(into hasher: inout Hasher) {
path: stdlib/public/Concurrency/AsyncThrowingStream.swift
user: login: DougGregor
body: Same question here
created_at: 2025-04-25T16:33:04Z
author_association: MEMBER
diff_hunk: @@ -475,6 +475,22 @@ extension AsyncStream: @unchecked Sendable where Element: Sendable { }
 @available(SwiftStdlib 5.1, *)
 extension AsyncStream.Continuation.YieldResult: Sendable where Element: Sendable { }
 
+@available(SwiftStdlib 6.2, *)
+extension AsyncStream.Continuation: Hashable {
+  @available(SwiftStdlib 6.2, *)
path: stdlib/public/Concurrency/AsyncStream.swift
user: login: ktoso
body: Here's the followup: 

- main: https://github.com/swiftlang/swift/pull/81126
- 6.2: https://github.com/swiftlang/swift/pull/81127
created_at: 2025-04-28T02:05:21Z
author_association: CONTRIBUTOR

PR comments:
user: login: ktoso
created_at: 2025-04-24T04:21:16Z
body: @swift-ci please test
author_association: CONTRIBUTOR
user: login: ktoso
created_at: 2025-04-24T04:35:25Z
body: @swift-ci please test macOS
author_association: CONTRIBUTOR
user: login: ktoso
created_at: 2025-04-24T04:35:31Z
body: @swift-ci please test source compatibility
author_association: CONTRIBUTOR
user: login: ktoso
created_at: 2025-04-24T07:36:11Z
body: @swift-ci please test macOS
author_association: CONTRIBUTOR
user: login: ktoso
created_at: 2025-04-24T10:00:51Z
body: @swift-ci please test macOS
author_association: CONTRIBUTOR
user: login: ktoso
created_at: 2025-04-24T22:05:28Z
body: @swift-ci please test macOS
author_association: CONTRIBUTOR