Last active
October 11, 2019 15:57
-
-
Save Sajjon/3f7dafdd341a42e675f3d830572c50b2 to your computer and use it in GitHub Desktop.
Auto Synthesizer for Equatable
This file contains hidden or 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
| // MARK: - Compare + Mirror | |
| private func compareSome<T>(lhs: T, rhs: T, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: Bool) -> Bool { | |
| compareAny(lhs: lhs, rhs: rhs, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer) | |
| } | |
| private func compareAny(lhs: Any, rhs: Any, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: Bool = true) -> Bool { | |
| let lMirror = Mirror(reflecting: lhs) | |
| let rMirror = Mirror(reflecting: rhs) | |
| guard | |
| lMirror.displayStyle == rMirror.displayStyle, | |
| lMirror.children.count == rMirror.children.count | |
| else | |
| { return false } | |
| for indexInt in 0..<lMirror.children.count { | |
| let index = AnyCollection<(label: Optional<String>, value: Any)>.Index(indexInt) | |
| let lChild = lMirror.children[index] | |
| let rChild = rMirror.children[index] | |
| guard lChild.label == rChild.label else { | |
| return false | |
| } | |
| guard "\(lChild.value)" == "\(rChild.value)" else { | |
| let lChildType = type(of: lChild.value) | |
| let rChildType = type(of: rChild.value) | |
| if beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer && "\(lChildType)" == "\(rChildType)" { | |
| continue | |
| } else { | |
| return false | |
| } | |
| } | |
| } | |
| return true | |
| } | |
| // MARK: - AutoEquatable | |
| public protocol AutoEquatable: Equatable { | |
| static func compare(lhs: Self, rhs: Self, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: Bool) -> Bool | |
| } | |
| public extension AutoEquatable { | |
| static func compare(lhs: Self, rhs: Self, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: Bool) -> Bool { | |
| compareSome(lhs: lhs, rhs: rhs, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer) | |
| } | |
| } | |
| // MARK: - Equatable | |
| public extension AutoEquatable { | |
| static func == (lhs: Self, rhs: Self) -> Bool { | |
| Self.compare(lhs: lhs, rhs: rhs, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: true) | |
| } | |
| } | |
| // MARK: - ErrorAutoEquatable | |
| public typealias ErrorAutoEquatable = Swift.Error & AutoEquatable | |
| public func == <LHS>(lhs: LHS, rhsAnyError: Swift.Error) -> Bool where LHS: ErrorAutoEquatable { | |
| guard let rhs = rhsAnyError as? LHS else { return false } | |
| return compareSome(lhs: lhs, rhs: rhs, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: true) | |
| } | |
| public func == <RHS>(lhsAnyError: Swift.Error, rhs: RHS) -> Bool where RHS: ErrorAutoEquatable { | |
| guard let lhs = lhsAnyError as? RHS else { return false } | |
| return compareSome(lhs: lhs, rhs: rhs, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: true) | |
| } | |
| // MARK: - Example | |
| // MARK: Example Error types | |
| enum AnError: ErrorAutoEquatable { | |
| case anInt(Int) | |
| case aString(String?) | |
| case anotherInt(Int) | |
| case anotherString(String?) | |
| case aBool(Bool?) | |
| case stringAndInt(aString: String, anInt: Int) | |
| case complex(aString: String, anInt: Int, aBool: Bool, aTuple: (String, Int, Bool)) | |
| } | |
| enum AnotherError: ErrorAutoEquatable { | |
| case anInt(Int) | |
| } | |
| // MARK: - Usage | |
| func example() { | |
| @discardableResult | |
| func isSame(_ lhs: AnError, _ rhs: AnError) -> Bool { | |
| return lhs == rhs | |
| } | |
| // ALL `true` | |
| isSame(.anInt(1), .anInt(2)) // true | |
| isSame(.aBool(true), .aBool(false)) // true | |
| isSame(.aString("Hello"), .aString("World")) // true | |
| isSame(.aString("Hello"), .aString(nil)) // true | |
| isSame( | |
| .complex(aString: "A", anInt: 1, aBool: true, aTuple: ("B", 2, false)), | |
| .complex(aString: "X", anInt: 8, aBool: false, aTuple: ("Y", 9, true)) | |
| ) // true | |
| (AnError.anInt(1) as Swift.Error) == AnError.anInt(1) // true | |
| AnError.anInt(1) == (AnError.anInt(1) as Swift.Error) // true | |
| // ALL `false` | |
| isSame(.anInt(1), .anotherInt(1)) // false | |
| isSame(.aString("Hello"), .anotherString("World")) // false | |
| isSame(.aString(nil), .anotherString(nil)) // false | |
| (AnError.anInt(1) as Swift.Error) == AnotherError.anInt(1) // false | |
| AnError.anInt(1) == (AnotherError.anInt(1) as Swift.Error) // false | |
| } | |
| // MARK: Advanced - 'Static' (using Functions) | |
| protocol EmptyInitializable { | |
| init() | |
| } | |
| protocol ValueIgnored { | |
| static var irrelevant: Self { get } | |
| } | |
| typealias Ignored = EmptyInitializable & ValueIgnored | |
| extension ValueIgnored where Self: EmptyInitializable { | |
| static var irrelevant: Self { .init() } | |
| } | |
| func == <E, A>(instance: E, make: (A) -> E) -> Bool where E: ErrorAutoEquatable, A: Ignored { | |
| instance == make(A.irrelevant) | |
| } | |
| func != <E, A>(instance: E, make: (A) -> E) -> Bool where E: ErrorAutoEquatable, A: Ignored { | |
| !(instance == make) | |
| } | |
| func == <E, A, B>(instance: E, make: (A, B) -> E) -> Bool where E: ErrorAutoEquatable, A: Ignored, B: Ignored { | |
| instance == make(A.irrelevant, B.irrelevant) | |
| } | |
| extension String: Ignored {} | |
| extension Bool: Ignored {} | |
| extension UInt: Ignored {} | |
| extension UInt64: Ignored {} | |
| extension UInt32: Ignored {} | |
| extension UInt16: Ignored {} | |
| extension UInt8: Ignored {} | |
| extension Int: Ignored {} | |
| extension Int64: Ignored {} | |
| extension Int32: Ignored {} | |
| extension Int16: Ignored {} | |
| extension Int8: Ignored {} | |
| // MARK: Advanced example | |
| func advanced() { | |
| AnError.anInt(1) == AnError.self.anInt // true | |
| AnError.stringAndInt(aString: "Some string", anInt: 123) != AnError.self.anInt // true, not equal | |
| AnError.stringAndInt(aString: "Some string", anInt: 123) == AnError.stringAndInt // true | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment