Skip to content

Instantly share code, notes, and snippets.

@bscothern
Last active April 13, 2023 06:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bscothern/e0f8d2ae5afa61ad7a44e6d3df9e9cd6 to your computer and use it in GitHub Desktop.
Save bscothern/e0f8d2ae5afa61ad7a44e6d3df9e9cd6 to your computer and use it in GitHub Desktop.
DerivingIdentifiable Experiments
import ComposableArchitecture
@_spi(Reflection) import CasePaths
public protocol DerivingIdentifiable: Hashable, Identifiable where ID == AnyHashable {}
@usableFromInline
struct MergeHashes: Hashable {
@usableFromInline
var first: any Hashable
@usableFromInline
var second: any Hashable
@usableFromInline
init(_ first: any Hashable, _ second: any Hashable) {
self.first = first
self.second = second
}
@usableFromInline
func hash(into hasher: inout Hasher) {
hasher.combine(first)
hasher.combine(second)
}
@usableFromInline
static func areEqual<T, U>(_ lhs: T, _ rhs: U) -> Bool where T: Equatable, U: Equatable {
if T.self is U.Type {
return rhs == (lhs as! U)
} else if U.self is T.Type {
return lhs == (rhs as! T)
}
return false
}
@usableFromInline
static func == (lhs: Self, rhs: Self) -> Bool {
return areEqual(lhs.first, rhs.first) && areEqual(lhs.second, rhs.second)
}
}
extension Identifiable where Self: DerivingIdentifiable {
@inlinable
public var id: AnyHashable {
return AnyHashable(MergeHashes(getID(self), self))
}
}
@usableFromInline
func getID<Value>(_ value: Value) -> AnyHashable {
func extractID(_ identifiable: any Identifiable) -> AnyHashable {
func extractID(_ identifiable: some Identifiable) -> AnyHashable {
AnyHashable(identifiable.id)
}
return extractID(identifiable)
}
if let projection = EnumMetadata.project(value) {
if let projection = projection as? (any Identifiable) {
return extractID(projection)
} else if let value = projection as? any Hashable {
XCTFail("DerivingIdentifiable had to use raw Metatype because type \(Value.self) doesn't conform to Identifiable at the bottom of the tree")
return AnyHashable(MergeHashes(value, ObjectIdentifier(Value.self)))
}
XCTFail("DerivingIdentifiable had to use raw Metatype because type \(Value.self) doesn't conform to Identifiable or Hashable at the bottom of the tree")
} else {
XCTFail("Unable to get projection of value: \(value) (\(Value.self))")
}
return AnyHashable(ObjectIdentifier(Value.self))
}
// Tests
import XCTest
final class DerivingIdentifiableTests: XCTestCase {
struct ID<T: Hashable>: Equatable, Hashable, Identifiable {
let id: T
init(_ id: T) {
self.id = id
}
}
enum A: Equatable, DerivingIdentifiable {
case b(B)
case c(C)
}
enum B: Equatable, DerivingIdentifiable {
case d(ID<Int>)
case e(ID<String>)
}
enum C: Equatable, DerivingIdentifiable {
case f(ID<Double>)
case g(D)
}
enum D: Equatable, DerivingIdentifiable {
case h(ID<Character>)
case i(Int)
case j(Foo)
func hash(into hasher: inout Hasher) {
switch self {
case let .h(value):
hasher.combine(value)
case let .i(value):
hasher.combine(value)
case .j:
hasher.combine(ObjectIdentifier(Foo.self))
}
}
}
struct Foo: Equatable {}
func testExample() throws {
XCTAssertNotEqual(A.b(.d(ID(3))).id, AnyHashable(3))
XCTAssertNotEqual(A.b(.e(ID("apple"))).id, AnyHashable("apple"))
XCTAssertNotEqual(A.c(.f(ID(1.23))).id, AnyHashable(1.23))
XCTAssertNotEqual(A.c(.g(.h(ID("@")))).id, AnyHashable("@" as Character))
XCTExpectFailure {
XCTAssertNotEqual(A.c(.g(.i(42))).id, AnyHashable(42))
}
XCTExpectFailure {
XCTAssertNotEqual(A.c(.g(.j(Foo()))).id, AnyHashable(ObjectIdentifier(Foo.self)))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment