Skip to content

Commit 6ea3b1b

Browse files
authored
Add experimental support for "diffable" objects (#105)
* Experimental protocol to diff an object w/ history * wip * wip * test * wip * wip * wip * wip
1 parent cc39088 commit 6ea3b1b

File tree

3 files changed

+122
-0
lines changed

3 files changed

+122
-0
lines changed

Sources/CustomDump/Diff.swift

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,64 @@ public func diff<T>(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String
308308
case (is CustomDumpStringConvertible, _, is CustomDumpStringConvertible, _):
309309
diffEverything()
310310

311+
case let (lhs as _CustomDiffObject, _, rhs as _CustomDiffObject, _) where lhs === rhs:
312+
let lhsItem = ObjectIdentifier(lhs)
313+
let rhsItem = ObjectIdentifier(rhs)
314+
let subjectType = typeName(lhsMirror.subjectType)
315+
if visitedItems.contains(lhsItem) || visitedItems.contains(rhsItem) {
316+
print(
317+
"\(lhsName.map { "\($0): " } ?? "")\(subjectType)(↩︎)"
318+
.indenting(by: indent)
319+
.indenting(with: format.first + " "),
320+
to: &out
321+
)
322+
print(
323+
"\(rhsName.map { "\($0): " } ?? "")\(subjectType)(↩︎)"
324+
.indenting(by: indent)
325+
.indenting(with: format.second + " "),
326+
terminator: "",
327+
to: &out
328+
)
329+
} else if lhsItem == rhsItem {
330+
let (lhs, rhs) = lhs._customDiffValues
331+
print(
332+
diffHelp(
333+
lhs,
334+
rhs,
335+
lhsName: lhsName,
336+
rhsName: rhsName,
337+
separator: separator,
338+
indent: indent,
339+
isRoot: isRoot
340+
),
341+
terminator: "",
342+
to: &out
343+
)
344+
} else {
345+
let showObjectIdentifiers =
346+
lhsItem != rhsItem
347+
&& isMirrorEqual(Array(lhsMirror.children), Array(rhsMirror.children))
348+
let lhsMirror =
349+
showObjectIdentifiers
350+
? Mirror(lhs, children: [("_", lhsItem)] + lhsMirror.children, displayStyle: .class)
351+
: lhsMirror
352+
let rhsMirror =
353+
showObjectIdentifiers
354+
? Mirror(rhs, children: [("_", rhsItem)] + rhsMirror.children, displayStyle: .class)
355+
: rhsMirror
356+
visitedItems.insert(lhsItem)
357+
diffChildren(
358+
lhsMirror,
359+
rhsMirror,
360+
prefix: "\(subjectType)(",
361+
suffix: ")",
362+
elementIndent: 2,
363+
elementSeparator: ",",
364+
collapseUnchanged: false,
365+
filter: macroPropertyFilter(for: lhs)
366+
)
367+
}
368+
311369
case let (lhs as CustomDumpRepresentable, _, rhs as CustomDumpRepresentable, _):
312370
out.write(
313371
diffHelp(
@@ -339,6 +397,22 @@ public func diff<T>(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String
339397
terminator: "",
340398
to: &out
341399
)
400+
} else if lhsItem == rhsItem,
401+
let (lhs, rhs) = (lhs as? _CustomDiffObject)?._customDiffValues
402+
{
403+
print(
404+
diffHelp(
405+
lhs,
406+
rhs,
407+
lhsName: lhsName,
408+
rhsName: rhsName,
409+
separator: separator,
410+
indent: indent,
411+
isRoot: isRoot
412+
),
413+
terminator: "",
414+
to: &out
415+
)
342416
} else {
343417
let showObjectIdentifiers =
344418
lhsItem != rhsItem
@@ -636,3 +710,7 @@ private struct Line: CustomDumpStringConvertible, Identifiable {
636710
.init(self.rawValue)
637711
}
638712
}
713+
714+
public protocol _CustomDiffObject: AnyObject {
715+
var _customDiffValues: (Any, Any) { get }
716+
}

Sources/CustomDump/Internal/Mirror.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ extension Mirror {
99
let child = self.children.first
1010
else { return false }
1111
var value = child.value
12+
if value is _CustomDiffObject {
13+
return false
14+
}
1215
while let representable = value as? CustomDumpRepresentable {
1316
value = representable.customDumpValue
17+
if value is _CustomDiffObject {
18+
return false
19+
}
1420
}
1521
if let convertible = child.value as? CustomDumpStringConvertible {
1622
return !convertible.customDumpDescription.contains("\n")

Tests/CustomDumpTests/DiffTests.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,44 @@ final class DiffTests: XCTestCase {
11811181
"""
11821182
)
11831183
}
1184+
1185+
func testDiffableObject() {
1186+
let obj = DiffableObject()
1187+
XCTAssertNoDifference(
1188+
diff(obj, obj),
1189+
"""
1190+
- "before"
1191+
+ "after"
1192+
"""
1193+
)
1194+
1195+
let bar = DiffableObjects(obj1: obj, obj2: obj)
1196+
XCTAssertNoDifference(
1197+
diff(bar, bar),
1198+
"""
1199+
DiffableObjects(
1200+
- obj1: "before",
1201+
+ obj1: "after",
1202+
- obj2: "before"
1203+
+ obj2: "after"
1204+
)
1205+
"""
1206+
)
1207+
}
1208+
}
1209+
1210+
private class DiffableObject: _CustomDiffObject, Equatable {
1211+
var _customDiffValues: (Any, Any) {
1212+
("before", "after")
1213+
}
1214+
static func == (lhs: DiffableObject, rhs: DiffableObject) -> Bool {
1215+
false
1216+
}
1217+
}
1218+
1219+
private struct DiffableObjects: Equatable {
1220+
var obj1: DiffableObject
1221+
var obj2: DiffableObject
11841222
}
11851223

11861224
private struct Stack<State: Equatable>: CustomDumpReflectable, Equatable {

0 commit comments

Comments
 (0)