diff --git a/Sources/OpenSwiftUICore/Data/Other/AnyHashable2.swift b/Sources/OpenSwiftUICore/Data/Other/AnyHashable2.swift new file mode 100644 index 000000000..8e7be1915 --- /dev/null +++ b/Sources/OpenSwiftUICore/Data/Other/AnyHashable2.swift @@ -0,0 +1,88 @@ +// +// AnyHashable2.swift +// OpenSwiftUICore +// +// Audited for iOS 18.0 +// Status: Complete +// ID: 12EE2013F9611424839A13CDF1FE08D2 (SwiftUICore) + +fileprivate class AnyHashableBox { + func `as`(type: T.Type) -> T? where T: Hashable { nil } + var description: String { "" } + var anyValue: any Hashable { preconditionFailure("") } + func isEqual(to other: AnyHashableBox) -> Bool { false } + func hash(into hasher: inout Hasher) {} +} + +fileprivate class _AnyHashableBox: AnyHashableBox where Value: Hashable { + let value: Value + + init(_ value: Value) { + self.value = value + } + + override func `as`(type: T.Type) -> T? where T: Hashable { + value as? T + } + + override var description: String { + String(describing: value) + } + + override var anyValue: any Hashable { + value + } + + override func isEqual(to other: AnyHashableBox) -> Bool { + guard let otherBox = other as? _AnyHashableBox else { + return false + } + return value == otherBox.value + } + + override func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(Value.self)) + hasher.combine(value) + } +} + +package struct AnyHashable2: Hashable, CustomStringConvertible { + private var box: AnyHashableBox + + package init(_ value: T) where T: Hashable { + box = _AnyHashableBox(value) + } + + package func `as`(type: T.Type) -> T? where T: Hashable { + box.as(type: type) + } + + package var anyValue: any Hashable { + box.anyValue + } + + package var anyHashable: AnyHashable { + AnyHashable(box.anyValue) + } + + package var description: String { + box.description + } + + package static func == (lhs: AnyHashable2, rhs: AnyHashable2) -> Bool { + guard lhs.box !== rhs.box else { + return true + } + return lhs.box.isEqual(to: rhs.box) + } + + package func hash(into hasher: inout Hasher) { + box.hash(into: &hasher) + } +} + +extension AnyHashable2: _HasCustomAnyHashableRepresentation { + package func _toCustomAnyHashable() -> AnyHashable? { + anyHashable + } +} diff --git a/Sources/OpenSwiftUICore/Data/Other/BitVector.swift b/Sources/OpenSwiftUICore/Data/Other/BitVector.swift index a66155c03..b2a4d094e 100644 --- a/Sources/OpenSwiftUICore/Data/Other/BitVector.swift +++ b/Sources/OpenSwiftUICore/Data/Other/BitVector.swift @@ -4,7 +4,7 @@ // // Audited for iOS 18.0 // Status: Complete -// ID: 8433FC349A42D7F59B64CD7FA08D81A9 +// ID: 8433FC349A42D7F59B64CD7FA08D81A9 (SwiftUICore) import Foundation diff --git a/Tests/OpenSwiftUICoreTests/Data/Other/AnyHashable2Tests.swift b/Tests/OpenSwiftUICoreTests/Data/Other/AnyHashable2Tests.swift new file mode 100644 index 000000000..553164dae --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/Data/Other/AnyHashable2Tests.swift @@ -0,0 +1,34 @@ +// +// AnyHashable2Tests.swift +// OpenSwiftUICoreTests + +import OpenSwiftUICore +import Testing + +struct AnyHashable2Tests { + @Test + func value() { + struct ValueA: Hashable { + var value: Int + } + + struct ValueB: Hashable { + var value: Int + } + + let valueA = ValueA(value: 1) + let valueB = ValueB(value: 1) + + let anyHashableA = AnyHashable(valueA) + let anyHashableB = AnyHashable(valueB) + + let anyHashable2A = AnyHashable2(valueA) + let anyHashable2B = AnyHashable2(valueB) + + #expect(anyHashableA.hashValue == anyHashableB.hashValue) + #expect(anyHashableA != anyHashableB) + + #expect(anyHashable2A.hashValue != anyHashable2B.hashValue) + #expect(anyHashable2A != anyHashable2B) + } +}