Skip to content

Commit 6b0576e

Browse files
authored
AnyHashableSendable ergonomics (#47)
* `AnyHashableSendable` ergonomics While working with this type I noticed a couple deficiencies: - First, the initializer takes an opaque type rather than an existential, and while existentials are typically automatically opened, there are cases in which this initializer will fail, _e.g._: ```swift let x: (any Hashable & Sendable)? x.map(AnyHashableSendable.init) // 🛑 ``` We can fix this by updating the initializer to be an `any` already. - Second, comparing an `AnyHashableSendable` with another `AnyHashable` fails because of the underlying type, but there is an underscored public protocol in the standard library we can take advantage of that is called when a hashable type is cast to `AnyHashable`, and if we return the base value then things like this start to work: ```swift AnyHashableSendable(1) == 1 as AnyHashable // true ``` * Add basic literals * fix * fix
1 parent 524fd4b commit 6b0576e

File tree

2 files changed

+57
-0
lines changed

2 files changed

+57
-0
lines changed

Sources/ConcurrencyExtras/AnyHashableSendable.swift

+36
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
public struct AnyHashableSendable: Hashable, Sendable {
66
public let base: any Hashable & Sendable
77

8+
/// Creates a type-erased hashable, sendable value that wraps the given instance.
9+
@_disfavoredOverload
10+
public init(_ base: any Hashable & Sendable) {
11+
self.init(base)
12+
}
13+
814
/// Creates a type-erased hashable, sendable value that wraps the given instance.
915
public init(_ base: some Hashable & Sendable) {
1016
if let base = base as? AnyHashableSendable {
@@ -40,3 +46,33 @@ extension AnyHashableSendable: CustomStringConvertible {
4046
String(describing: base)
4147
}
4248
}
49+
50+
extension AnyHashableSendable: _HasCustomAnyHashableRepresentation {
51+
public func _toCustomAnyHashable() -> AnyHashable? {
52+
base as? AnyHashable
53+
}
54+
}
55+
56+
extension AnyHashableSendable: ExpressibleByBooleanLiteral {
57+
public init(booleanLiteral value: Bool) {
58+
self.init(value)
59+
}
60+
}
61+
62+
extension AnyHashableSendable: ExpressibleByFloatLiteral {
63+
public init(floatLiteral value: Double) {
64+
self.init(value)
65+
}
66+
}
67+
68+
extension AnyHashableSendable: ExpressibleByIntegerLiteral {
69+
public init(integerLiteral value: Int) {
70+
self.init(value)
71+
}
72+
}
73+
74+
extension AnyHashableSendable: ExpressibleByStringLiteral {
75+
public init(stringLiteral value: String) {
76+
self.init(value)
77+
}
78+
}

Tests/ConcurrencyExtrasTests/AnyHashableSendableTests.swift

+21
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,25 @@ final class AnyHashableSendableTests: XCTestCase {
1515

1616
XCTAssertEqual(flat, nested)
1717
}
18+
19+
func testExistential() {
20+
let base: (any Hashable & Sendable)? = 1
21+
let wrapped = base.map(AnyHashableSendable.init)
22+
XCTAssertEqual(wrapped, AnyHashableSendable(1))
23+
}
24+
25+
func testAnyHashable() {
26+
XCTAssertEqual(AnyHashableSendable(1), 1 as AnyHashable)
27+
}
28+
29+
func testLiterals() {
30+
XCTAssertEqual(AnyHashableSendable(true), true)
31+
XCTAssertEqual(AnyHashableSendable(1), 1)
32+
XCTAssertEqual(AnyHashableSendable(4.2), 4.2)
33+
XCTAssertEqual(AnyHashableSendable("Blob"), "Blob")
34+
35+
let bool: AnyHashableSendable = true
36+
XCTAssertEqual(bool.base as? Bool, true)
37+
XCTAssertEqual(bool as AnyHashable as? Bool, true)
38+
}
1839
}

0 commit comments

Comments
 (0)