Last active
April 13, 2023 06:32
-
-
Save bscothern/e0f8d2ae5afa61ad7a44e6d3df9e9cd6 to your computer and use it in GitHub Desktop.
DerivingIdentifiable Experiments
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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