From 512d5efd552a98e2ce19055750db777d3fb4697c Mon Sep 17 00:00:00 2001 From: Ben Cohen Date: Thu, 6 Nov 2025 10:28:30 -0800 Subject: [PATCH] Allow Hashable: ~Escapable --- stdlib/public/core/Hashable.swift | 6 +-- stdlib/public/core/Hasher.swift | 2 +- test/Frontend/dump-parse.swift | 2 +- .../SILGen/synthesized_conformance_enum.swift | 6 +-- .../synthesized_conformance_struct.swift | 6 +-- ...tability-stdlib-source-base.swift.expected | 11 ++-- .../stability-stdlib-abi-without-asserts.test | 21 ++++---- test/stdlib/NoncopyableHashable.swift | 52 ++++++++++++++----- 8 files changed, 66 insertions(+), 40 deletions(-) diff --git a/stdlib/public/core/Hashable.swift b/stdlib/public/core/Hashable.swift index 386a20e866ff4..1e2b580d5e57d 100644 --- a/stdlib/public/core/Hashable.swift +++ b/stdlib/public/core/Hashable.swift @@ -101,7 +101,7 @@ /// print("New tap detected at (\(nextTap.x), \(nextTap.y)).") /// } /// // Prints "New tap detected at (0, 1).") -public protocol Hashable: Equatable & ~Copyable { +public protocol Hashable: Equatable & ~Copyable & ~Escapable { /// The hash value. /// /// Hash values are not guaranteed to be equal across different executions of @@ -135,7 +135,7 @@ public protocol Hashable: Equatable & ~Copyable { func _rawHashValue(seed: Int) -> Int } -extension Hashable where Self: ~Copyable { +extension Hashable where Self: ~Copyable & ~Escapable { @inlinable @inline(__always) @_preInverseGenerics @@ -150,7 +150,7 @@ extension Hashable where Self: ~Copyable { @inlinable @inline(__always) @_preInverseGenerics -public func _hashValue(for value: borrowing H) -> Int { +public func _hashValue(for value: borrowing H) -> Int { return value._rawHashValue(seed: 0) } diff --git a/stdlib/public/core/Hasher.swift b/stdlib/public/core/Hasher.swift index 5972fd25c1df9..c55a275fd5105 100644 --- a/stdlib/public/core/Hasher.swift +++ b/stdlib/public/core/Hasher.swift @@ -351,7 +351,7 @@ public struct Hasher { @inlinable @inline(__always) @_preInverseGenerics - public mutating func combine(_ value: borrowing H) { + public mutating func combine(_ value: borrowing H) { value.hash(into: &self) } diff --git a/test/Frontend/dump-parse.swift b/test/Frontend/dump-parse.swift index 9aec3af7e8017..0ec5d159def26 100644 --- a/test/Frontend/dump-parse.swift +++ b/test/Frontend/dump-parse.swift @@ -58,7 +58,7 @@ enum TrailingSemi { // CHECK-AST-LABEL: (func_decl{{.*}}"generic(_:)" "" interface_type=" (T) -> ()" access=internal captures=( {{.*}}) func generic(_: T) {} // CHECK-AST: (pattern_binding_decl -// CHECK-AST: (processed_init=declref_expr type="(Int) -> ()" location={{.*}} range={{.*}} decl="main.(file).generic@{{.*}} [with (substitution_map generic_signature= T -> Int)]" function_ref=unapplied)) +// CHECK-AST: (processed_init=declref_expr type="(Int) -> ()" location={{.*}} range={{.*}} decl="main.(file).generic@{{.*}} [with (substitution_map generic_signature= T -> Int)]" function_ref=unapplied)) let _: (Int) -> () = generic // Closures should be marked as escaping or not. diff --git a/test/SILGen/synthesized_conformance_enum.swift b/test/SILGen/synthesized_conformance_enum.swift index ea4ac8c9e0ef9..05ef7e54142c7 100644 --- a/test/SILGen/synthesized_conformance_enum.swift +++ b/test/SILGen/synthesized_conformance_enum.swift @@ -83,9 +83,9 @@ extension NoValues: Codable {} // CHECK-LABEL: sil_witness_table hidden Enum: Hashable module synthesized_conformance_enum { // CHECK-DAG: base_protocol Equatable: Enum: Equatable module synthesized_conformance_enum -// CHECK-DAG: method #Hashable.hashValue!getter: (Self) -> () -> Int : @$s28synthesized_conformance_enum4EnumOyxGSHAASHRzlSH9hashValueSivgTW // protocol witness for Hashable.hashValue.getter in conformance Enum -// CHECK-DAG: method #Hashable.hash: (Self) -> (inout Hasher) -> () : @$s28synthesized_conformance_enum4EnumOyxGSHAASHRzlSH4hash4intoys6HasherVz_tFTW // protocol witness for Hashable.hash(into:) in conformance Enum -// CHECK-DAG: method #Hashable._rawHashValue: (Self) -> (Int) -> Int : @$s28synthesized_conformance_enum4EnumOyxGSHAASHRzlSH13_rawHashValue4seedS2i_tFTW // protocol witness for Hashable._rawHashValue(seed:) in conformance Enum +// CHECK-DAG: method #Hashable.hashValue!getter: (Self) -> () -> Int : @$s28synthesized_conformance_enum4EnumOyxGSHAASHRzlSH9hashValueSivgTW // protocol witness for Hashable.hashValue.getter in conformance Enum +// CHECK-DAG: method #Hashable.hash: (Self) -> (inout Hasher) -> () : @$s28synthesized_conformance_enum4EnumOyxGSHAASHRzlSH4hash4intoys6HasherVz_tFTW // protocol witness for Hashable.hash(into:) in conformance Enum +// CHECK-DAG: method #Hashable._rawHashValue: (Self) -> (Int) -> Int : @$s28synthesized_conformance_enum4EnumOyxGSHAASHRzlSH13_rawHashValue4seedS2i_tFTW // protocol witness for Hashable._rawHashValue(seed:) in conformance Enum // CHECK-DAG: conditional_conformance (T: Hashable): dependent // CHECK: } diff --git a/test/SILGen/synthesized_conformance_struct.swift b/test/SILGen/synthesized_conformance_struct.swift index bd29424257c38..245c98f8d11b3 100644 --- a/test/SILGen/synthesized_conformance_struct.swift +++ b/test/SILGen/synthesized_conformance_struct.swift @@ -69,9 +69,9 @@ extension Struct: Codable where T: Codable {} // CHECK-LABEL: sil_witness_table hidden Struct: Hashable module synthesized_conformance_struct { // CHECK-DAG: base_protocol Equatable: Struct: Equatable module synthesized_conformance_struct -// CHECK-DAG: method #Hashable.hashValue!getter: (Self) -> () -> Int : @$s30synthesized_conformance_struct6StructVyxGSHAASHRzlSH9hashValueSivgTW // protocol witness for Hashable.hashValue.getter in conformance Struct -// CHECK-DAG: method #Hashable.hash: (Self) -> (inout Hasher) -> () : @$s30synthesized_conformance_struct6StructVyxGSHAASHRzlSH4hash4intoys6HasherVz_tFTW // protocol witness for Hashable.hash(into:) in conformance Struct -// CHECK-DAG: method #Hashable._rawHashValue: (Self) -> (Int) -> Int : @$s30synthesized_conformance_struct6StructVyxGSHAASHRzlSH13_rawHashValue4seedS2i_tFTW // protocol witness for Hashable._rawHashValue(seed:) in conformance Struct +// CHECK-DAG: method #Hashable.hashValue!getter: (Self) -> () -> Int : @$s30synthesized_conformance_struct6StructVyxGSHAASHRzlSH9hashValueSivgTW // protocol witness for Hashable.hashValue.getter in conformance Struct +// CHECK-DAG: method #Hashable.hash: (Self) -> (inout Hasher) -> () : @$s30synthesized_conformance_struct6StructVyxGSHAASHRzlSH4hash4intoys6HasherVz_tFTW // protocol witness for Hashable.hash(into:) in conformance Struct +// CHECK-DAG: method #Hashable._rawHashValue: (Self) -> (Int) -> Int : @$s30synthesized_conformance_struct6StructVyxGSHAASHRzlSH13_rawHashValue4seedS2i_tFTW // protocol witness for Hashable._rawHashValue(seed:) in conformance Struct // CHECK-DAG: conditional_conformance (T: Hashable): dependent // CHECK: } diff --git a/test/api-digester/Outputs/stability-stdlib-source-base.swift.expected b/test/api-digester/Outputs/stability-stdlib-source-base.swift.expected index 60bce9a98bf46..72cddc175b198 100644 --- a/test/api-digester/Outputs/stability-stdlib-source-base.swift.expected +++ b/test/api-digester/Outputs/stability-stdlib-source-base.swift.expected @@ -114,7 +114,6 @@ Protocol FixedWidthInteger has added inherited protocol Copyable Protocol FixedWidthInteger has added inherited protocol Escapable Protocol FloatingPoint has added inherited protocol Copyable Protocol FloatingPoint has added inherited protocol Escapable -Protocol Hashable has added inherited protocol Escapable Protocol Identifiable has added inherited protocol Copyable Protocol Identifiable has added inherited protocol Escapable Protocol IteratorProtocol has added inherited protocol Copyable @@ -399,9 +398,9 @@ Func Comparable.>=(_:_:) has generic signature change from =(_:_:) has parameter 0 changing from Default to Shared Func Comparable.>=(_:_:) has parameter 1 changing from Default to Shared -// Hashable: ~Copyable -Protocol Hashable has generic signature change from to -Accessor Hashable.hashValue.Get() has generic signature change from to -Func Hashable.hash(into:) has generic signature change from to -Func Hasher.combine(_:) has generic signature change from to +// Hashable: ~Copyable & Escapable +Protocol Hashable has generic signature change from to +Accessor Hashable.hashValue.Get() has generic signature change from to +Func Hashable.hash(into:) has generic signature change from to +Func Hasher.combine(_:) has generic signature change from to Func Hasher.combine(_:) has parameter 0 changing from Default to Shared diff --git a/test/api-digester/stability-stdlib-abi-without-asserts.test b/test/api-digester/stability-stdlib-abi-without-asserts.test index 52124e2147282..7e53c964c4df3 100644 --- a/test/api-digester/stability-stdlib-abi-without-asserts.test +++ b/test/api-digester/stability-stdlib-abi-without-asserts.test @@ -232,7 +232,6 @@ Protocol FixedWidthInteger has added inherited protocol Copyable Protocol FixedWidthInteger has added inherited protocol Escapable Protocol FloatingPoint has added inherited protocol Copyable Protocol FloatingPoint has added inherited protocol Escapable -Protocol Hashable has added inherited protocol Escapable Protocol Identifiable has added inherited protocol Copyable Protocol Identifiable has added inherited protocol Escapable Protocol IteratorProtocol has added inherited protocol Copyable @@ -910,19 +909,19 @@ Func Comparable.>=(_:_:) has parameter 0 changing from Default to Shared Func Comparable.>=(_:_:) has parameter 1 changing from Default to Shared Func Comparable.>=(_:_:) is now with @_preInverseGenerics -// Hashable: ~Copyable -Protocol Hashable has generic signature change from to -Accessor Hashable.hashValue.Get() has generic signature change from to -Func Hashable._rawHashValue(seed:) has generic signature change from to -Func Hashable._rawHashValue(seed:) has mangled name changing from '(extension in Swift):Swift.Hashable._rawHashValue(seed: Swift.Int) -> Swift.Int' to '(extension in Swift):Swift.Hashable< where A: ~Swift.Copyable>._rawHashValue(seed: Swift.Int) -> Swift.Int' +// Hashable: ~Copyable & ~Escapable +Protocol Hashable has generic signature change from to +Accessor Hashable.hashValue.Get() has generic signature change from to +Func Hashable._rawHashValue(seed:) has generic signature change from to +Func Hashable._rawHashValue(seed:) has mangled name changing from '(extension in Swift):Swift.Hashable._rawHashValue(seed: Swift.Int) -> Swift.Int' to '(extension in Swift):Swift.Hashable< where A: ~Swift.Copyable, A: ~Swift.Escapable>._rawHashValue(seed: Swift.Int) -> Swift.Int' Func Hashable._rawHashValue(seed:) is now with @_preInverseGenerics -Func Hashable.hash(into:) has generic signature change from to -Func Hasher.combine(_:) has generic signature change from to -Func Hasher.combine(_:) has mangled name changing from 'Swift.Hasher.combine(A) -> ()' to 'Swift.Hasher.combine(A) -> ()' +Func Hashable.hash(into:) has generic signature change from to +Func Hasher.combine(_:) has generic signature change from to +Func Hasher.combine(_:) has mangled name changing from 'Swift.Hasher.combine(A) -> ()' to 'Swift.Hasher.combine(A) -> ()' Func Hasher.combine(_:) has parameter 0 changing from Default to Shared Func Hasher.combine(_:) is now with @_preInverseGenerics -Func _hashValue(for:) has generic signature change from to -Func _hashValue(for:) has mangled name changing from 'Swift._hashValue(for: A) -> Swift.Int' to 'Swift._hashValue(for: A) -> Swift.Int' +Func _hashValue(for:) has generic signature change from to +Func _hashValue(for:) has mangled name changing from 'Swift._hashValue(for: A) -> Swift.Int' to 'Swift._hashValue(for: A) -> Swift.Int' Func _hashValue(for:) has parameter 0 changing from Default to Shared Func _hashValue(for:) is now with @_preInverseGenerics diff --git a/test/stdlib/NoncopyableHashable.swift b/test/stdlib/NoncopyableHashable.swift index 295377f458100..dcd6d4dd51f4f 100644 --- a/test/stdlib/NoncopyableHashable.swift +++ b/test/stdlib/NoncopyableHashable.swift @@ -9,29 +9,44 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// -// RUN: %target-run-simple-swift +// RUN: %target-run-simple-swift(-enable-experimental-feature Lifetimes) // REQUIRES: executable_test +// REQUIRES: swift_feature_Lifetimes import StdlibUnittest let NoncopyableHashableTests = TestSuite("NoncopyableHashable") -struct Noncopyable: ~Copyable { +struct Noncopyable: ~Copyable { var wrapped: Wrapped + + @_lifetime(copy wrapped) + init(wrapping wrapped: consuming Wrapped) { + self.wrapped = wrapped + } } -extension Noncopyable: Equatable where Wrapped: Equatable & ~Copyable { } +extension Noncopyable: Equatable where Wrapped: Equatable & ~Copyable & ~Escapable { } + +extension Noncopyable: Hashable where Wrapped: Hashable & ~Copyable & ~Escapable { } -extension Noncopyable: Hashable where Wrapped: Hashable & ~Copyable { } +extension Noncopyable: Escapable where Wrapped: Escapable & ~Copyable { } + +struct Nonescapable: ~Escapable { + let wrapped: Int +} +extension Nonescapable: Equatable { } -extension Hashable where Self: ~Copyable { +extension Nonescapable: Hashable { } + +extension Hashable where Self: ~Copyable & ~Escapable { func sameHash(as other: borrowing Self) -> Bool { self.hashValue == other.hashValue } } -func differentHash(_ lhs: borrowing T, _ rhs: borrowing T) -> Bool { +func differentHash(_ lhs: borrowing T, _ rhs: borrowing T) -> Bool { lhs.hashValue != rhs.hashValue } @@ -47,9 +62,9 @@ extension InlineArray where Element: Hashable & ~Copyable { } NoncopyableHashableTests.test("hashing noncopyables") { - let a = Noncopyable(wrapped: 1) - let b = Noncopyable(wrapped: 2) - let c = Noncopyable(wrapped: 1) + let a = Noncopyable(wrapping: 1) + let b = Noncopyable(wrapping: 2) + let c = Noncopyable(wrapping: 1) expectTrue(a.sameHash(as: a)) expectFalse(a.sameHash(as: b)) @@ -59,17 +74,30 @@ NoncopyableHashableTests.test("hashing noncopyables") { expectFalse(differentHash(a,a)) expectFalse(differentHash(a,c)) - let nc2 = Noncopyable(wrapped: Noncopyable(wrapped: "1")) + let nc2 = Noncopyable(wrapping: Noncopyable(wrapping: "1")) expectTrue(nc2.sameHash(as: nc2)) - expectTrue(differentHash(nc2, .init(wrapped: .init(wrapped: "2")))) + expectTrue(differentHash(nc2, .init(wrapping: .init(wrapping: "2")))) guard #available(SwiftStdlib 6.2, *) else { return } let a1: [_ of _] = [a,b] - let d = Noncopyable(wrapped: 2) + let d = Noncopyable(wrapping: 2) let a2: [_ of _] = [c,d] expectEqual(a1.combinedHashes(), a2.combinedHashes()) } +NoncopyableHashableTests.test("hashing nonescapables") { + let nc1 = Noncopyable(wrapping: .init(wrapped: 1)) + let nc2 = Noncopyable(wrapping: .init(wrapped: 1)) + let nc3 = Noncopyable(wrapping: .init(wrapped: 2)) + + expectTrue(nc1.hashValue == nc2.hashValue) + expectFalse(nc1.hashValue == nc3.hashValue) + expectTrue(nc1.sameHash(as: nc2)) + expectFalse(nc1.sameHash(as: nc3)) + expectTrue(differentHash(nc1, nc3)) + expectFalse(differentHash(nc1, nc2)) +} + runAllTests()