diff --git a/Sources/CustomDump/Diff.swift b/Sources/CustomDump/Diff.swift index 95e01a22..bfba274b 100644 --- a/Sources/CustomDump/Diff.swift +++ b/Sources/CustomDump/Diff.swift @@ -308,6 +308,64 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String case (is CustomDumpStringConvertible, _, is CustomDumpStringConvertible, _): diffEverything() + case let (lhs as _CustomDiffObject, _, rhs as _CustomDiffObject, _) where lhs === rhs: + let lhsItem = ObjectIdentifier(lhs) + let rhsItem = ObjectIdentifier(rhs) + let subjectType = typeName(lhsMirror.subjectType) + if visitedItems.contains(lhsItem) || visitedItems.contains(rhsItem) { + print( + "\(lhsName.map { "\($0): " } ?? "")\(subjectType)(↩︎)" + .indenting(by: indent) + .indenting(with: format.first + " "), + to: &out + ) + print( + "\(rhsName.map { "\($0): " } ?? "")\(subjectType)(↩︎)" + .indenting(by: indent) + .indenting(with: format.second + " "), + terminator: "", + to: &out + ) + } else if lhsItem == rhsItem { + let (lhs, rhs) = lhs._customDiffValues + print( + diffHelp( + lhs, + rhs, + lhsName: lhsName, + rhsName: rhsName, + separator: separator, + indent: indent, + isRoot: isRoot + ), + terminator: "", + to: &out + ) + } else { + let showObjectIdentifiers = + lhsItem != rhsItem + && isMirrorEqual(Array(lhsMirror.children), Array(rhsMirror.children)) + let lhsMirror = + showObjectIdentifiers + ? Mirror(lhs, children: [("_", lhsItem)] + lhsMirror.children, displayStyle: .class) + : lhsMirror + let rhsMirror = + showObjectIdentifiers + ? Mirror(rhs, children: [("_", rhsItem)] + rhsMirror.children, displayStyle: .class) + : rhsMirror + visitedItems.insert(lhsItem) + diffChildren( + lhsMirror, + rhsMirror, + prefix: "\(subjectType)(", + suffix: ")", + elementIndent: 2, + elementSeparator: ",", + collapseUnchanged: false, + filter: macroPropertyFilter(for: lhs) + ) + } + case let (lhs as CustomDumpRepresentable, _, rhs as CustomDumpRepresentable, _): out.write( diffHelp( @@ -339,6 +397,22 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String terminator: "", to: &out ) + } else if lhsItem == rhsItem, + let (lhs, rhs) = (lhs as? _CustomDiffObject)?._customDiffValues + { + print( + diffHelp( + lhs, + rhs, + lhsName: lhsName, + rhsName: rhsName, + separator: separator, + indent: indent, + isRoot: isRoot + ), + terminator: "", + to: &out + ) } else { let showObjectIdentifiers = lhsItem != rhsItem @@ -636,3 +710,7 @@ private struct Line: CustomDumpStringConvertible, Identifiable { .init(self.rawValue) } } + +public protocol _CustomDiffObject: AnyObject { + var _customDiffValues: (Any, Any) { get } +} diff --git a/Sources/CustomDump/Internal/Mirror.swift b/Sources/CustomDump/Internal/Mirror.swift index a37fe370..f9a592d6 100644 --- a/Sources/CustomDump/Internal/Mirror.swift +++ b/Sources/CustomDump/Internal/Mirror.swift @@ -9,8 +9,14 @@ extension Mirror { let child = self.children.first else { return false } var value = child.value + if value is _CustomDiffObject { + return false + } while let representable = value as? CustomDumpRepresentable { value = representable.customDumpValue + if value is _CustomDiffObject { + return false + } } if let convertible = child.value as? CustomDumpStringConvertible { return !convertible.customDumpDescription.contains("\n") diff --git a/Tests/CustomDumpTests/DiffTests.swift b/Tests/CustomDumpTests/DiffTests.swift index 6bbc152d..e48e5203 100644 --- a/Tests/CustomDumpTests/DiffTests.swift +++ b/Tests/CustomDumpTests/DiffTests.swift @@ -1181,6 +1181,44 @@ final class DiffTests: XCTestCase { """ ) } + + func testDiffableObject() { + let obj = DiffableObject() + XCTAssertNoDifference( + diff(obj, obj), + """ + - "before" + + "after" + """ + ) + + let bar = DiffableObjects(obj1: obj, obj2: obj) + XCTAssertNoDifference( + diff(bar, bar), + """ + DiffableObjects( + - obj1: "before", + + obj1: "after", + - obj2: "before" + + obj2: "after" + ) + """ + ) + } +} + +private class DiffableObject: _CustomDiffObject, Equatable { + var _customDiffValues: (Any, Any) { + ("before", "after") + } + static func == (lhs: DiffableObject, rhs: DiffableObject) -> Bool { + false + } +} + +private struct DiffableObjects: Equatable { + var obj1: DiffableObject + var obj2: DiffableObject } private struct Stack: CustomDumpReflectable, Equatable {