From ac7d15a3e37c3c7355fa1fc0f991ddb59d7487da Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 7 Mar 2024 09:25:16 -0800 Subject: [PATCH 01/19] wip --- Sources/CustomDump/Diff.swift | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/Sources/CustomDump/Diff.swift b/Sources/CustomDump/Diff.swift index bfba274..f1de845 100644 --- a/Sources/CustomDump/Diff.swift +++ b/Sources/CustomDump/Diff.swift @@ -308,9 +308,10 @@ 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) + case let (lhs as _CustomDiffObject, _, rhs as _CustomDiffObject, _) + where lhs._objectIdentifier == rhs._objectIdentifier: + let lhsItem = lhs._objectIdentifier + let rhsItem = rhs._objectIdentifier let subjectType = typeName(lhsMirror.subjectType) if visitedItems.contains(lhsItem) || visitedItems.contains(rhsItem) { print( @@ -397,22 +398,6 @@ 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 @@ -711,6 +696,13 @@ private struct Line: CustomDumpStringConvertible, Identifiable { } } -public protocol _CustomDiffObject: AnyObject { +public protocol _CustomDiffObject { var _customDiffValues: (Any, Any) { get } + var _objectIdentifier: ObjectIdentifier { get } +} + +extension _CustomDiffObject where Self: AnyObject { + public var _objectIdentifier: ObjectIdentifier { + ObjectIdentifier(self) + } } From df8c1188592819f054bfbf472d6eaeef994c1af0 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 7 Mar 2024 12:41:20 -0800 Subject: [PATCH 02/19] wip --- Sources/CustomDump/Conformances/SwiftUI.swift | 4 +- Sources/CustomDump/Diff.swift | 262 ++++++++++++------ Sources/CustomDump/Dump.swift | 174 +++++++++--- Tests/CustomDumpTests/DiffTests.swift | 119 +++++--- Tests/CustomDumpTests/DumpTests.swift | 137 ++++++++- 5 files changed, 521 insertions(+), 175 deletions(-) diff --git a/Sources/CustomDump/Conformances/SwiftUI.swift b/Sources/CustomDump/Conformances/SwiftUI.swift index a922c48..a6579e3 100644 --- a/Sources/CustomDump/Conformances/SwiftUI.swift +++ b/Sources/CustomDump/Conformances/SwiftUI.swift @@ -18,12 +18,14 @@ case .spring(): return "Animation.spring()" default: + var tracker = ObjectTracker() let base = _customDump( Mirror(reflecting: self).children.first?.value as Any, name: nil, indent: 2, isRoot: false, - maxDepth: .max + maxDepth: .max, + tracker: &tracker ) return """ Animation( diff --git a/Sources/CustomDump/Diff.swift b/Sources/CustomDump/Diff.swift index f1de845..10648a8 100644 --- a/Sources/CustomDump/Diff.swift +++ b/Sources/CustomDump/Diff.swift @@ -26,7 +26,7 @@ /// - Returns: A string describing any difference detected between values, or `nil` if no difference /// is detected. public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String? { - var visitedItems: Set = [] + var tracker = ObjectTracker() func diffHelp( _ lhs: Any, @@ -39,9 +39,16 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String ) -> String { let rhsName = rhsName ?? lhsName guard lhsName != rhsName || !isMirrorEqual(lhs, rhs) else { - return _customDump(lhs, name: rhsName, indent: indent, isRoot: isRoot, maxDepth: 0) - .appending(separator) - .indenting(with: format.both + " ") + return _customDump( + lhs, + name: rhsName, + indent: indent, + isRoot: isRoot, + maxDepth: 0, + tracker: &tracker + ) + .appending(separator) + .indenting(with: format.both + " ") } let lhsMirror = Mirror(customDumpReflecting: lhs) @@ -49,8 +56,22 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String var out = "" func diffEverything() { - var lhs = _customDump(lhs, name: lhsName, indent: indent, isRoot: isRoot, maxDepth: .max) - var rhs = _customDump(rhs, name: rhsName, indent: indent, isRoot: isRoot, maxDepth: .max) + var lhs = _customDump( + lhs, + name: lhsName, + indent: indent, + isRoot: isRoot, + maxDepth: .max, + tracker: &tracker + ) + var rhs = _customDump( + rhs, + name: rhsName, + indent: indent, + isRoot: isRoot, + maxDepth: .max, + tracker: &tracker + ) if lhs == rhs { if lhsMirror.subjectType != rhsMirror.subjectType { lhs.append(" as \(typeName(lhsMirror.subjectType))") @@ -100,14 +121,16 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String name: lhsName, indent: indent, isRoot: false, - maxDepth: 0 + maxDepth: 0, + tracker: &tracker ) let rhsDump = _customDump( rhs, name: rhsName, indent: indent, isRoot: false, - maxDepth: 0 + maxDepth: 0, + tracker: &tracker ) if lhsDump == rhsDump { print( @@ -137,7 +160,8 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String name: lhsName, indent: indent, isRoot: isRoot, - maxDepth: .max + maxDepth: .max, + tracker: &tracker ) .indenting(with: format.first + " "), to: &out @@ -148,7 +172,8 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String name: rhsName, indent: indent, isRoot: isRoot, - maxDepth: .max + maxDepth: .max, + tracker: &tracker ) .indenting(with: format.second + " "), terminator: "", @@ -190,7 +215,8 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String name: child.label, indent: indent + elementIndent, isRoot: false, - maxDepth: 0 + maxDepth: 0, + tracker: &tracker ) .indenting(with: format.both + " "), terminator: rhsOffset - 1 == rhsChildren.count - 1 ? "\n" : "\(elementSeparator)\n", @@ -264,7 +290,8 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String name: lhsChild.label, indent: indent + elementIndent, isRoot: false, - maxDepth: .max + maxDepth: .max, + tracker: &tracker ) .indenting(with: format.first + " "), terminator: lhsOffset == lhsChildren.count - 1 ? "\n" : "\(elementSeparator)\n", @@ -281,7 +308,8 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String name: rhsChild.label, indent: indent + elementIndent, isRoot: false, - maxDepth: .max + maxDepth: .max, + tracker: &tracker ) .indenting(with: format.second + " "), terminator: rhsOffset == rhsChildren.count - 1 && unchangedBuffer.isEmpty @@ -310,10 +338,19 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String case let (lhs as _CustomDiffObject, _, rhs as _CustomDiffObject, _) where lhs._objectIdentifier == rhs._objectIdentifier: - let lhsItem = lhs._objectIdentifier - let rhsItem = rhs._objectIdentifier - let subjectType = typeName(lhsMirror.subjectType) - if visitedItems.contains(lhsItem) || visitedItems.contains(rhsItem) { + let item = lhs._objectIdentifier + let name = typeName(lhsMirror.subjectType) + var occurrence = tracker.occurrencePerType[name, default: 1] { + didSet { tracker.occurrencePerType[name] = occurrence } + } + var id: String { + let id = tracker.idPerItem[item, default: occurrence] + tracker.idPerItem[item] = id + + return id > 0 ? "#\(id)" : "" + } + let subjectType = "\(id) \(name)" + if tracker.visitedItems.contains(item) { print( "\(lhsName.map { "\($0): " } ?? "")\(subjectType)(↩︎)" .indenting(by: indent) @@ -327,7 +364,9 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String terminator: "", to: &out ) - } else if lhsItem == rhsItem { + } else { + tracker.visitedItems.insert(item) + occurrence += 1 let (lhs, rhs) = lhs._customDiffValues print( diffHelp( @@ -342,29 +381,6 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String 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, _): @@ -384,43 +400,78 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String 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) + if !tracker.visitedItems.contains(lhsItem) && !tracker.visitedItems.contains(rhsItem) { + if lhsItem == rhsItem { + diffChildren( + lhsMirror, + rhsMirror, + prefix: "\(subjectType)(", + suffix: ")", + elementIndent: 2, + elementSeparator: ",", + collapseUnchanged: false, + filter: macroPropertyFilter(for: lhs) + ) + } else { + diffEverything() + } + } else { + var occurrence: UInt { tracker.occurrencePerType[subjectType, default: 0] } + if tracker.visitedItems.contains(lhsItem) { + var lhsID: String { + let id = tracker.idPerItem[lhsItem, default: occurrence] + tracker.idPerItem[lhsItem] = id + return id > 0 ? "#\(id) " : "" + } + print( + "\(lhsName.map { "\($0): " } ?? "")\(lhsID)\(subjectType)(↩︎)" + .indenting(by: indent) + .indenting(with: format.first + " "), + to: &out + ) + } else { + print( + _customDump( + lhs, + name: lhsName, + indent: indent, + isRoot: isRoot, + maxDepth: .max, + tracker: &tracker + ) .indenting(with: format.first + " "), - to: &out - ) - print( - "\(rhsName.map { "\($0): " } ?? "")\(subjectType)(↩︎)" - .indenting(by: indent) + terminator: "", + to: &out + ) + } + if tracker.visitedItems.contains(rhsItem) { + var rhsID: String { + let id = tracker.idPerItem[rhsItem, default: occurrence] + tracker.idPerItem[rhsItem] = id + return id > 0 ? "#\(id) " : "" + } + print( + "\(rhsName.map { "\($0): " } ?? "")\(rhsID)\(subjectType)(↩︎)" + .indenting(by: indent) + .indenting(with: format.second + " "), + terminator: "", + to: &out + ) + } else { + print( + _customDump( + rhs, + name: rhsName, + indent: indent, + isRoot: isRoot, + maxDepth: .max, + tracker: &tracker + ) .indenting(with: format.second + " "), - 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) - ) + terminator: "", + to: &out + ) + } } case (_, .collection?, _, .collection?): @@ -458,21 +509,47 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String }, areInIncreasingOrder: lhsMirror.subjectType is _UnorderedCollection.Type ? { - guard + let (lhsValue, rhsValue): (Any, Any) + if let lhs = $0.value as? (key: AnyHashable, value: Any), let rhs = $1.value as? (key: AnyHashable, value: Any) - else { - return _customDump($0.value, name: nil, indent: 0, isRoot: false, maxDepth: 1) - < _customDump($1.value, name: nil, indent: 0, isRoot: false, maxDepth: 1) + { + lhsValue = lhs.key.base + rhsValue = rhs.key.base + } else { + lhsValue = $0.value + rhsValue = $1.value } - return _customDump(lhs.key.base, name: nil, indent: 0, isRoot: false, maxDepth: 1) - < _customDump(rhs.key.base, name: nil, indent: 0, isRoot: false, maxDepth: 1) + let lhsDump = _customDump( + lhsValue, + name: nil, + indent: 0, + isRoot: false, + maxDepth: 1, + tracker: &tracker + ) + let rhsDump = _customDump( + rhsValue, + name: nil, + indent: 0, + isRoot: false, + maxDepth: 1, + tracker: &tracker + ) + return lhsDump < rhsDump } : nil ) { child, _ in guard let pair = child.value as? (key: AnyHashable, value: Any) else { return } child = ( - _customDump(pair.key.base, name: nil, indent: 0, isRoot: false, maxDepth: 1), + _customDump( + pair.key.base, + name: nil, + indent: 0, + isRoot: false, + maxDepth: 1, + tracker: &tracker + ), pair.value ) } @@ -549,8 +626,23 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String }, areInIncreasingOrder: lhsMirror.subjectType is _UnorderedCollection.Type ? { - _customDump($0.value, name: nil, indent: 0, isRoot: false, maxDepth: 1) - < _customDump($1.value, name: nil, indent: 0, isRoot: false, maxDepth: 1) + let lhsDump = _customDump( + $0.value, + name: nil, + indent: 0, + isRoot: false, + maxDepth: 1, + tracker: &tracker + ) + let rhsDump = _customDump( + $1.value, + name: nil, + indent: 0, + isRoot: false, + maxDepth: 1, + tracker: &tracker + ) + return lhsDump < rhsDump } : nil ) diff --git a/Sources/CustomDump/Dump.swift b/Sources/CustomDump/Dump.swift index c845619..0218de2 100644 --- a/Sources/CustomDump/Dump.swift +++ b/Sources/CustomDump/Dump.swift @@ -45,6 +45,12 @@ extension String { } } +struct ObjectTracker { + var idPerItem: [ObjectIdentifier: UInt] = [:] + var occurrencePerType: [String: UInt] = [:] + var visitedItems: Set = [] +} + /// Dumps the given value's contents using its mirror to the specified output stream. /// /// - Parameters: @@ -65,7 +71,16 @@ public func customDump( indent: Int = 0, maxDepth: Int = .max ) -> T where TargetStream: TextOutputStream { - _customDump(value, to: &target, name: name, indent: indent, isRoot: true, maxDepth: maxDepth) + var tracker = ObjectTracker() + return _customDump( + value, + to: &target, + name: name, + indent: indent, + isRoot: true, + maxDepth: maxDepth, + tracker: &tracker + ) } @discardableResult @@ -75,12 +90,9 @@ func _customDump( name: String?, indent: Int, isRoot: Bool, - maxDepth: Int + maxDepth: Int, + tracker: inout ObjectTracker ) -> T where TargetStream: TextOutputStream { - var idPerItem: [ObjectIdentifier: UInt] = [:] - var occurrencePerType: [String: UInt] = [:] - var visitedItems: Set = [] - func customDumpHelp( _ value: InnerT, to target: inout InnerTargetStream, @@ -101,8 +113,9 @@ func _customDump( of mirror: Mirror, prefix: String, suffix: String, + shouldSort: Bool, filter isIncluded: (Mirror.Child) -> Bool = { _ in true }, - by areInIncreasingOrder: ((Mirror.Child, Mirror.Child) -> Bool)? = nil, + by areInIncreasingOrder: (Mirror.Child, Mirror.Child) -> Bool = { _, _ in false }, map transform: (inout Mirror.Child, Int) -> Void = { _, _ in } ) { out.write(prefix) @@ -135,7 +148,7 @@ func _customDump( out.write("\n") var children = Array(mirror.children) children.removeAll(where: { !isIncluded($0) }) - if let areInIncreasingOrder = areInIncreasingOrder { + if shouldSort { children.sort(by: areInIncreasingOrder) } for (offset, var child) in children.enumerated() { @@ -170,22 +183,49 @@ func _customDump( value.customDumpValue, to: &out, name: nil, indent: 0, isRoot: false, maxDepth: maxDepth ) + case let (value as _CustomDiffObject, _): + let item = value._objectIdentifier + let (_, value) = value._customDiffValues + var occurrence = tracker.occurrencePerType[typeName(mirror.subjectType), default: 1] { + didSet { tracker.occurrencePerType[typeName(mirror.subjectType)] = occurrence } + } + + var id: String { + let id = tracker.idPerItem[item, default: occurrence] + tracker.idPerItem[item] = id + + return id > 0 ? "#\(id)" : "" + } + if !id.isEmpty { + out.write("\(id) ") + } + if tracker.visitedItems.contains(item) { + out.write("\(typeName(type(of: value)))(↩︎)") + } else { + tracker.visitedItems.insert(item) + occurrence += 1 + customDumpHelp(value, to: &out, name: nil, indent: 0, isRoot: false, maxDepth: maxDepth) + } + case let (value as AnyObject, .class?): let item = ObjectIdentifier(value) - var occurrence = occurrencePerType[typeName(mirror.subjectType), default: 0] { - didSet { occurrencePerType[typeName(mirror.subjectType)] = occurrence } + var occurrence = tracker.occurrencePerType[typeName(mirror.subjectType), default: 0] { + didSet { tracker.occurrencePerType[typeName(mirror.subjectType)] = occurrence } } var id: String { - let id = idPerItem[item, default: occurrence] - idPerItem[item] = id + let id = tracker.idPerItem[item, default: occurrence] + tracker.idPerItem[item] = id - return id > 1 ? "#\(id)" : "" + return id > 0 ? "#\(id)" : "" + } + if !id.isEmpty { + out.write("\(id) ") } - if visitedItems.contains(item) { - out.write("\(typeName(mirror.subjectType))\(id)(↩︎)") + if tracker.visitedItems.contains(item) { + out.write("\(typeName(mirror.subjectType))(↩︎)") } else { - visitedItems.insert(item) + tracker.visitedItems.insert(item) occurrence += 1 var children = Array(mirror.children) @@ -196,14 +236,23 @@ func _customDump( } dumpChildren( of: Mirror(value, children: children), - prefix: "\(typeName(mirror.subjectType))\(id)(", + prefix: "\(typeName(mirror.subjectType))(", suffix: ")", + shouldSort: false, filter: macroPropertyFilter(for: value) ) } case (_, .collection?): - dumpChildren(of: mirror, prefix: "[", suffix: "]", map: { $0.label = "[\($1)]" }) + dumpChildren( + of: mirror, + prefix: "[", + suffix: "]", + shouldSort: false, + map: { + $0.label = "[\($1)]" + } + ) case (_, .dictionary?): if mirror.children.isEmpty { @@ -212,21 +261,40 @@ func _customDump( dumpChildren( of: mirror, prefix: "[", suffix: "]", - by: mirror.subjectType is _UnorderedCollection.Type - ? { - guard - let (lhsKey, _) = $0.value as? (key: AnyHashable, value: Any), - let (rhsKey, _) = $1.value as? (key: AnyHashable, value: Any) - else { return false } + shouldSort: mirror.subjectType is _UnorderedCollection.Type, + by: { + guard + let (lhsKey, _) = $0.value as? (key: AnyHashable, value: Any), + let (rhsKey, _) = $1.value as? (key: AnyHashable, value: Any) + else { return false } - return _customDump(lhsKey.base, name: nil, indent: 0, isRoot: false, maxDepth: 1) - < _customDump(rhsKey.base, name: nil, indent: 0, isRoot: false, maxDepth: 1) - } - : nil, + let lhsDump = _customDump( + lhsKey.base, + name: nil, + indent: 0, + isRoot: false, + maxDepth: 1, + tracker: &tracker + ) + let rhsDump = _customDump( + rhsKey.base, + name: nil, + indent: 0, + isRoot: false, + maxDepth: 1, + tracker: &tracker + ) + return lhsDump < rhsDump + }, map: { child, _ in guard let pair = child.value as? (key: AnyHashable, value: Any) else { return } let key = _customDump( - pair.key.base, name: nil, indent: 0, isRoot: false, maxDepth: maxDepth - 1 + pair.key.base, + name: nil, + indent: 0, + isRoot: false, + maxDepth: maxDepth - 1, + tracker: &tracker ) child = (key, pair.value) } @@ -245,6 +313,7 @@ func _customDump( of: associatedValuesMirror, prefix: "\(child.label ?? "@unknown")(", suffix: ")", + shouldSort: false, map: { child, _ in if child.label?.first == "." { child.label = nil @@ -266,12 +335,26 @@ func _customDump( dumpChildren( of: mirror, prefix: "Set([", suffix: "])", - by: mirror.subjectType is _UnorderedCollection.Type - ? { - _customDump($0.value, name: nil, indent: 0, isRoot: false, maxDepth: 1) - < _customDump($1.value, name: nil, indent: 0, isRoot: false, maxDepth: 1) - } - : nil + shouldSort: mirror.subjectType is _UnorderedCollection.Type, + by: { + let lhs = _customDump( + $0.value, + name: nil, + indent: 0, + isRoot: false, + maxDepth: 1, + tracker: &tracker + ) + let rhs = _customDump( + $1.value, + name: nil, + indent: 0, + isRoot: false, + maxDepth: 1, + tracker: &tracker + ) + return lhs < rhs + } ) case (_, .struct?): @@ -279,6 +362,7 @@ func _customDump( of: mirror, prefix: "\(typeName(mirror.subjectType))(", suffix: ")", + shouldSort: false, filter: macroPropertyFilter(for: value) ) @@ -287,6 +371,7 @@ func _customDump( of: mirror, prefix: "(", suffix: ")", + shouldSort: false, map: { child, _ in if child.label?.first == "." { child.label = nil @@ -326,9 +411,24 @@ func _customDump( return value } -func _customDump(_ value: Any, name: String?, indent: Int, isRoot: Bool, maxDepth: Int) -> String { +func _customDump( + _ value: Any, + name: String?, + indent: Int, + isRoot: Bool, + maxDepth: Int, + tracker: inout ObjectTracker +) -> String { var out = "" - _customDump(value, to: &out, name: name, indent: indent, isRoot: isRoot, maxDepth: maxDepth) + _customDump( + value, + to: &out, + name: name, + indent: indent, + isRoot: isRoot, + maxDepth: maxDepth, + tracker: &tracker + ) return out } diff --git a/Tests/CustomDumpTests/DiffTests.swift b/Tests/CustomDumpTests/DiffTests.swift index e48e520..4ae681b 100644 --- a/Tests/CustomDumpTests/DiffTests.swift +++ b/Tests/CustomDumpTests/DiffTests.swift @@ -56,11 +56,14 @@ final class DiffTests: XCTestCase { ) ), """ - UserClass( - id: 42, + - UserClass( + - id: 42, - name: "Blob" + - ) + + #1 UserClass( + + id: 42, + name: "Blob, Jr." - ) + + ) """ ) @@ -71,7 +74,7 @@ final class DiffTests: XCTestCase { ), """ - NSObject() - + NSObject() + + #1 NSObject() """ ) @@ -81,38 +84,74 @@ final class DiffTests: XCTestCase { RepeatedObject(id: "b") ), """ - RepeatedObject( - child: RepeatedObject.Child( + - RepeatedObject( + - child: RepeatedObject.Child( - grandchild: RepeatedObject.Grandchild(id: "a") - + grandchild: RepeatedObject.Grandchild(id: "b") - ), + - ), - grandchild: RepeatedObject.Grandchild(↩︎) - + grandchild: RepeatedObject.Grandchild(↩︎) - ) + - ) + + #1 RepeatedObject( + + child: #1 RepeatedObject.Child( + + grandchild: #1 RepeatedObject.Grandchild(id: "b") + + ), + + grandchild: #1 RepeatedObject.Grandchild(↩︎) + + ) """ ) } - func testClassObjectIdentity() { - class User: NSObject { - let id = 42 - let name = "Blob" + func testClass_Repeated() { + class User { + let id: Int + let name: String + init(id: Int, name: String) { + self.id = id + self.name = name + } } + let u1 = User(id: 1, name: "Blob") + let u2 = User(id: 2, name: "Blob Jr.") + let u3 = User(id: 3, name: "Blob Sr.") + + struct Three { let u1: User, u2: User, u3: User} XCTAssertNoDifference( diff( - User(), - User() - )?.replacingOccurrences(of: "0x[[:xdigit:]]+", with: "0x…", options: .regularExpression), + Three(u1: u1, u2: u2, u3: u2), + Three(u1: u1, u2: u2, u3: u3) + ), """ - DiffTests.User( - - _: ObjectIdentifier(0x…), - + _: ObjectIdentifier(0x…), - id: 42, - name: "Blob" + DiffTests.Three( + u1: DiffTests.User(…), + u2: #1 DiffTests.User(…), + - u3: #1 DiffTests.User(↩︎) + + u3: #2 DiffTests.User( + + id: 3, + + name: "Blob Sr." + + ) ) """ ) + + XCTAssertNoDifference( + diff( + [u1, u2, u2], + [u1, u2, u3] + ), + """ + [ + … (2 unchanged), + - [2]: DiffTests.User( + - id: 2, + - name: "Blob Jr." + - ) + + [2]: #1 DiffTests.User( + + id: 3, + + name: "Blob Sr." + + ) + ] + """ + ) } func testCollection() { @@ -968,7 +1007,7 @@ final class DiffTests: XCTestCase { ), """ - Namespaced.Class(x: 0) - + Namespaced.Class(x: 1) + + #1 Namespaced.Class(x: 1) """ ) @@ -1183,6 +1222,20 @@ final class DiffTests: XCTestCase { } func testDiffableObject() { + class DiffableObject: _CustomDiffObject, Equatable { + var _customDiffValues: (Any, Any) { + ("before", "after") + } + static func == (lhs: DiffableObject, rhs: DiffableObject) -> Bool { + false + } + } + + struct DiffableObjects: Equatable { + var obj1: DiffableObject + var obj2: DiffableObject + } + let obj = DiffableObject() XCTAssertNoDifference( diff(obj, obj), @@ -1196,31 +1249,17 @@ final class DiffTests: XCTestCase { XCTAssertNoDifference( diff(bar, bar), """ - DiffableObjects( + DiffTests.DiffableObjects( - obj1: "before", + obj1: "after", - - obj2: "before" - + obj2: "after" + - obj2: #1 DiffTests.DiffableObject(↩︎) + + obj2: #1 DiffTests.DiffableObject(↩︎) ) """ ) } } -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 { static func == (lhs: Self, rhs: Self) -> Bool { zip(lhs.elements, rhs.elements).allSatisfy(==) diff --git a/Tests/CustomDumpTests/DumpTests.swift b/Tests/CustomDumpTests/DumpTests.swift index 8cb85c4..304d9ec 100644 --- a/Tests/CustomDumpTests/DumpTests.swift +++ b/Tests/CustomDumpTests/DumpTests.swift @@ -1145,23 +1145,23 @@ final class DumpTests: XCTestCase { name: "Virginia", parent: DumpTests.Parent(↩︎) ), - [1]: DumpTests.Child#2( + [1]: #1 DumpTests.Child( name: "Ronald", parent: DumpTests.Parent(↩︎) ), - [2]: DumpTests.Child#3( + [2]: #2 DumpTests.Child( name: "Fred", parent: DumpTests.Parent(↩︎) ), - [3]: DumpTests.Child#4( + [3]: #3 DumpTests.Child( name: "George", parent: DumpTests.Parent(↩︎) ), - [4]: DumpTests.Child#5( + [4]: #4 DumpTests.Child( name: "Percy", parent: DumpTests.Parent(↩︎) ), - [5]: DumpTests.Child#6( + [5]: #5 DumpTests.Child( name: "Charles", parent: DumpTests.Parent(↩︎) ) @@ -1217,9 +1217,9 @@ final class DumpTests: XCTestCase { [0]: DumpTests.Human(name: "John"), [1]: DumpTests.Human(↩︎), [2]: DumpTests.Human(↩︎), - [3]: DumpTests.Human#2(name: "John"), - [4]: DumpTests.Human#2(↩︎), - [5]: DumpTests.Human#2(↩︎), + [3]: #1 DumpTests.Human(name: "John"), + [4]: #1 DumpTests.Human(↩︎), + [5]: #1 DumpTests.Human(↩︎), [6]: DumpTests.User( name: "John", email: "john@me.com", @@ -1228,14 +1228,14 @@ final class DumpTests: XCTestCase { ), [7]: DumpTests.User(↩︎), [8]: DumpTests.User(↩︎), - [9]: DumpTests.User#2( + [9]: #1 DumpTests.User( name: "John", email: "john@me.com", age: 97, - human: DumpTests.Human#2(↩︎) + human: #1 DumpTests.Human(↩︎) ), - [10]: DumpTests.User#2(↩︎), - [11]: DumpTests.User#2(↩︎) + [10]: #1 DumpTests.User(↩︎), + [11]: #1 DumpTests.User(↩︎) ] """ ) @@ -1354,4 +1354,117 @@ final class DumpTests: XCTestCase { """ ) } + + func testDiffableObject() { + struct Login { + var email: String + } + + class DiffableObject: _CustomDiffObject, Equatable { + var _customDiffValues: (Any, Any) { + (Login(email: "blob@pointfree.co"), Login(email: "admin@pointfree.co")) + } + static func == (lhs: DiffableObject, rhs: DiffableObject) -> Bool { + false + } + } + + struct DiffableObjects: Equatable { + var obj1: DiffableObject + var obj2: DiffableObject + } + + struct DiffableObjectsParent: Equatable { + var objs1: DiffableObjects + var objs2: DiffableObjects + } + + let obj1 = DiffableObject() + let obj2 = DiffableObject() + + XCTAssertNoDifference( + String( + customDumping: DiffableObjectsParent( + objs1: DiffableObjects(obj1: obj1, obj2: obj1), + objs2: DiffableObjects(obj1: obj2, obj2: obj2) + ) + ), + """ + DumpTests.DiffableObjectsParent( + objs1: DumpTests.DiffableObjects( + obj1: #1 DumpTests.Login(email: "admin@pointfree.co"), + obj2: #1 DumpTests.Login(↩︎) + ), + objs2: DumpTests.DiffableObjects( + obj1: #2 DumpTests.Login(email: "admin@pointfree.co"), + obj2: #2 DumpTests.Login(↩︎) + ) + ) + """ + ) + + XCTAssertNoDifference( + String( + customDumping: DiffableObjectsParent( + objs1: DiffableObjects(obj1: obj1, obj2: obj2), + objs2: DiffableObjects(obj1: obj2, obj2: obj1) + ) + ), + """ + DumpTests.DiffableObjectsParent( + objs1: DumpTests.DiffableObjects( + obj1: #1 DumpTests.Login(email: "admin@pointfree.co"), + obj2: #2 DumpTests.Login(email: "admin@pointfree.co") + ), + objs2: DumpTests.DiffableObjects( + obj1: #2 DumpTests.Login(↩︎), + obj2: #1 DumpTests.Login(↩︎) + ) + ) + """ + ) + } + + func testDiffableObject_Primitive() { + class DiffableObject: _CustomDiffObject, Equatable { + var _customDiffValues: (Any, Any) { + ("before", "after") + } + static func == (lhs: DiffableObject, rhs: DiffableObject) -> Bool { + false + } + } + + struct DiffableObjects: Equatable { + var obj1: DiffableObject + var obj2: DiffableObject + } + + struct DiffableObjectsParent: Equatable { + var objs1: DiffableObjects + var objs2: DiffableObjects + } + + let obj1 = DiffableObject() + let obj2 = DiffableObject() + let objs1 = DiffableObjects(obj1: obj1, obj2: obj1) + let objs2 = DiffableObjects(obj1: obj2, obj2: obj2) + let objsParent = DiffableObjectsParent(objs1: objs1, objs2: objs2) + + XCTAssertNoDifference( + String(customDumping: objsParent), + """ + DumpTests.DiffableObjectsParent( + objs1: DumpTests.DiffableObjects( + obj1: #1 "after", + obj2: #1 String(↩︎) + ), + objs2: DumpTests.DiffableObjects( + obj1: #2 "after", + obj2: #2 String(↩︎) + ) + ) + """ + ) + } } From d7355dd024de90a21f96991bb50ba23d4c176c25 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 7 Mar 2024 17:17:54 -0800 Subject: [PATCH 03/19] wip --- Sources/CustomDump/Diff.swift | 179 +++++++++++++++++++------- Sources/CustomDump/Dump.swift | 17 +-- Tests/CustomDumpTests/DiffTests.swift | 73 ++++++++++- 3 files changed, 208 insertions(+), 61 deletions(-) diff --git a/Sources/CustomDump/Diff.swift b/Sources/CustomDump/Diff.swift index 10648a8..67ea1d2 100644 --- a/Sources/CustomDump/Diff.swift +++ b/Sources/CustomDump/Diff.swift @@ -99,8 +99,13 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String } func diffChildren( + lhs: Any = lhs, + rhs: Any = rhs, _ lhsMirror: Mirror, _ rhsMirror: Mirror, + lhsName: String? = lhsName, + rhsName: String? = rhsName, + nameSuffix: String = ": ", prefix: String, suffix: String, elementIndent: Int, @@ -114,8 +119,10 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String var lhsChildren = Array(lhsMirror.children) var rhsChildren = Array(rhsMirror.children) - guard !isMirrorEqual(lhsChildren, rhsChildren) - else { + if isMirrorEqual(lhsChildren, rhsChildren), + !(lhs is _CustomDiffObject), + !(rhs is _CustomDiffObject) + { let lhsDump = _customDump( lhs, name: lhsName, @@ -185,7 +192,7 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String lhsChildren.removeAll(where: { !isIncluded($0) }) rhsChildren.removeAll(where: { !isIncluded($0) }) - let name = rhsName.map { "\($0): " } ?? "" + let name = rhsName.map { "\($0)\(nameSuffix)" } ?? "" print( name .appending(prefix) @@ -336,52 +343,130 @@ 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._objectIdentifier == rhs._objectIdentifier: - let item = lhs._objectIdentifier - let name = typeName(lhsMirror.subjectType) - var occurrence = tracker.occurrencePerType[name, default: 1] { - didSet { tracker.occurrencePerType[name] = occurrence } - } - var id: String { - let id = tracker.idPerItem[item, default: occurrence] - tracker.idPerItem[item] = id - - return id > 0 ? "#\(id)" : "" - } - let subjectType = "\(id) \(name)" - if tracker.visitedItems.contains(item) { - 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 { - tracker.visitedItems.insert(item) - occurrence += 1 + case let (lhs as _CustomDiffObject, _, rhs as _CustomDiffObject, _): + let lhsItem = lhs._objectIdentifier + let rhsItem = rhs._objectIdentifier + 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 - ) + let subjectType = typeName(type(of: lhs)) + var occurrence = tracker.occurrencePerType[subjectType, default: 1] { + didSet { tracker.occurrencePerType[subjectType] = occurrence } + } + var id: UInt { + let id = tracker.idPerItem[lhsItem, default: occurrence] + tracker.idPerItem[lhsItem] = id + return id + } + if tracker.visitedItems.contains(lhsItem) { + print( + "\(lhsName.map { "\($0): " } ?? "")#\(id) \(subjectType)(↩︎)\(separator)" + .indenting(by: indent) + .indenting(with: format.first + " "), + to: &out + ) + print( + "\(rhsName.map { "\($0): " } ?? "")#\(id) \(subjectType)(↩︎)\(separator)" + .indenting(by: indent) + .indenting(with: format.second + " "), + terminator: "", + to: &out + ) + } else { + diffChildren( + lhs: lhs, + rhs: rhs, + Mirror(customDumpReflecting: lhs), + Mirror(customDumpReflecting: rhs), + lhsName: "#\(id)", + rhsName: "#\(id)", + nameSuffix: " ", + prefix: "\(subjectType)(", + suffix: ")", + elementIndent: 2, + elementSeparator: ",", + collapseUnchanged: false, + filter: macroPropertyFilter(for: lhs) + ) + tracker.visitedItems.insert(lhsItem) + occurrence += 1 + } + } else { + diffEverything() } +// let subjectType = typeName(lhsMirror.subjectType) +// if !tracker.visitedItems.contains(lhsItem) && !tracker.visitedItems.contains(rhsItem) { +// if lhsItem == rhsItem { +// diffChildren( +// lhsMirror, +// rhsMirror, +// prefix: "\(subjectType)(", +// suffix: ")", +// elementIndent: 2, +// elementSeparator: ",", +// collapseUnchanged: false, +// filter: macroPropertyFilter(for: lhs) +// ) +// } else { +// diffEverything() +// } +// } else { +// var occurrence: UInt { tracker.occurrencePerType[subjectType, default: 0] } +// if tracker.visitedItems.contains(lhsItem) { +// var lhsID: String { +// let id = tracker.idPerItem[lhsItem, default: occurrence] +// tracker.idPerItem[lhsItem] = id +// return id > 0 ? "#\(id) " : "" +// } +// print( +// "\(lhsName.map { "\($0): " } ?? "")\(lhsID)\(subjectType)(↩︎)" +// .indenting(by: indent) +// .indenting(with: format.first + " "), +// to: &out +// ) +// } else { +// print( +// _customDump( +// lhs, +// name: lhsName, +// indent: indent, +// isRoot: isRoot, +// maxDepth: .max, +// tracker: &tracker +// ) +// .indenting(with: format.first + " "), +// terminator: "", +// to: &out +// ) +// } +// if tracker.visitedItems.contains(rhsItem) { +// var rhsID: String { +// let id = tracker.idPerItem[rhsItem, default: occurrence] +// tracker.idPerItem[rhsItem] = id +// return id > 0 ? "#\(id) " : "" +// } +// print( +// "\(rhsName.map { "\($0): " } ?? "")\(rhsID)\(subjectType)(↩︎)" +// .indenting(by: indent) +// .indenting(with: format.second + " "), +// terminator: "", +// to: &out +// ) +// } else { +// print( +// _customDump( +// rhs, +// name: rhsName, +// indent: indent, +// isRoot: isRoot, +// maxDepth: .max, +// tracker: &tracker +// ) +// .indenting(with: format.second + " "), +// terminator: "", +// to: &out +// ) +// } +// } case let (lhs as CustomDumpRepresentable, _, rhs as CustomDumpRepresentable, _): out.write( diff --git a/Sources/CustomDump/Dump.swift b/Sources/CustomDump/Dump.swift index 0218de2..36c2623 100644 --- a/Sources/CustomDump/Dump.swift +++ b/Sources/CustomDump/Dump.swift @@ -178,16 +178,12 @@ func _customDump( case let (value as CustomDumpStringConvertible, _): out.write(value.customDumpDescription) - case let (value as CustomDumpRepresentable, _): - customDumpHelp( - value.customDumpValue, to: &out, name: nil, indent: 0, isRoot: false, maxDepth: maxDepth - ) - case let (value as _CustomDiffObject, _): let item = value._objectIdentifier let (_, value) = value._customDiffValues - var occurrence = tracker.occurrencePerType[typeName(mirror.subjectType), default: 1] { - didSet { tracker.occurrencePerType[typeName(mirror.subjectType)] = occurrence } + let subjectType = typeName(type(of: value)) + var occurrence = tracker.occurrencePerType[subjectType, default: 1] { + didSet { tracker.occurrencePerType[subjectType] = occurrence } } var id: String { @@ -200,13 +196,18 @@ func _customDump( out.write("\(id) ") } if tracker.visitedItems.contains(item) { - out.write("\(typeName(type(of: value)))(↩︎)") + out.write("\(subjectType)(↩︎)") } else { tracker.visitedItems.insert(item) occurrence += 1 customDumpHelp(value, to: &out, name: nil, indent: 0, isRoot: false, maxDepth: maxDepth) } + case let (value as CustomDumpRepresentable, _): + customDumpHelp( + value.customDumpValue, to: &out, name: nil, indent: 0, isRoot: false, maxDepth: maxDepth + ) + case let (value as AnyObject, .class?): let item = ObjectIdentifier(value) var occurrence = tracker.occurrencePerType[typeName(mirror.subjectType), default: 0] { diff --git a/Tests/CustomDumpTests/DiffTests.swift b/Tests/CustomDumpTests/DiffTests.swift index 4ae681b..422786a 100644 --- a/Tests/CustomDumpTests/DiffTests.swift +++ b/Tests/CustomDumpTests/DiffTests.swift @@ -1222,6 +1222,67 @@ final class DiffTests: XCTestCase { } func testDiffableObject() { + struct User: Equatable { + let id = 1 + var name = "Blob" + } + class Shared: _CustomDiffObject, Equatable { + var _customDiffValues: (Any, Any) { + (User(), User(name: "Blob, Jr")) + } + static func == (lhs: Shared, rhs: Shared) -> Bool { + false + } + } + + let obj = Shared() + XCTAssertNoDifference( + diff(obj, obj), + """ + #1 DiffTests.User( + id: 1, + - name: "Blob" + + name: "Blob, Jr" + ) + """ + ) + + XCTAssertNoDifference( + diff(Shared(), Shared()), + """ + - #1 DiffTests.User( + - id: 1, + - name: "Blob, Jr" + - ) + + #2 DiffTests.User( + + id: 1, + + name: "Blob, Jr" + + ) + """ + ) + + XCTAssertNoDifference( + diff([obj, obj, obj], [obj, obj, Shared()]), + """ + [ + #1 DiffTests.User( + id: 1, + - name: "Blob" + + name: "Blob, Jr" + ), + - [1]: #1 DiffTests.User(↩︎), + + [1]: #1 DiffTests.User(↩︎), + - [2]: #1 DiffTests.User(↩︎) + + [2]: #2 DiffTests.User( + + id: 1, + + name: "Blob, Jr" + + ) + ] + """ + ) + } + + func testDiffableObject_Advanced() { class DiffableObject: _CustomDiffObject, Equatable { var _customDiffValues: (Any, Any) { ("before", "after") @@ -1240,8 +1301,8 @@ final class DiffTests: XCTestCase { XCTAssertNoDifference( diff(obj, obj), """ - - "before" - + "after" + - #1: "before" + + #1: "after" """ ) @@ -1250,10 +1311,10 @@ final class DiffTests: XCTestCase { diff(bar, bar), """ DiffTests.DiffableObjects( - - obj1: "before", - + obj1: "after", - - obj2: #1 DiffTests.DiffableObject(↩︎) - + obj2: #1 DiffTests.DiffableObject(↩︎) + - #1: "before" + + #1: "after" + - obj2: #1 String(↩︎) + + obj2: #1 String(↩︎) ) """ ) From 87bcdf74800a08077552f4c06b7e89900f24f1f6 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 7 Mar 2024 17:46:58 -0800 Subject: [PATCH 04/19] wip --- Sources/CustomDump/Diff.swift | 14 ++-- Sources/CustomDump/Dump.swift | 50 ++++++++++-- Tests/CustomDumpTests/DiffTests.swift | 111 ++++++++++++++++---------- 3 files changed, 120 insertions(+), 55 deletions(-) diff --git a/Sources/CustomDump/Diff.swift b/Sources/CustomDump/Diff.swift index 67ea1d2..0e5692c 100644 --- a/Sources/CustomDump/Diff.swift +++ b/Sources/CustomDump/Diff.swift @@ -105,7 +105,7 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String _ rhsMirror: Mirror, lhsName: String? = lhsName, rhsName: String? = rhsName, - nameSuffix: String = ": ", + nameSuffix: String = ":", prefix: String, suffix: String, elementIndent: Int, @@ -126,6 +126,7 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String let lhsDump = _customDump( lhs, name: lhsName, + nameSuffix: nameSuffix, indent: indent, isRoot: false, maxDepth: 0, @@ -134,6 +135,7 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String let rhsDump = _customDump( rhs, name: rhsName, + nameSuffix: nameSuffix, indent: indent, isRoot: false, maxDepth: 0, @@ -165,6 +167,7 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String _customDump( lhs, name: lhsName, + nameSuffix: nameSuffix, indent: indent, isRoot: isRoot, maxDepth: .max, @@ -177,6 +180,7 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String _customDump( rhs, name: rhsName, + nameSuffix: nameSuffix, indent: indent, isRoot: isRoot, maxDepth: .max, @@ -192,7 +196,7 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String lhsChildren.removeAll(where: { !isIncluded($0) }) rhsChildren.removeAll(where: { !isIncluded($0) }) - let name = rhsName.map { "\($0)\(nameSuffix)" } ?? "" + let name = rhsName.map { "\($0)\(nameSuffix) " } ?? "" print( name .appending(prefix) @@ -377,9 +381,9 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String rhs: rhs, Mirror(customDumpReflecting: lhs), Mirror(customDumpReflecting: rhs), - lhsName: "#\(id)", - rhsName: "#\(id)", - nameSuffix: " ", + lhsName: "\(lhsName.map { "\($0): " } ?? "")#\(id)", + rhsName: "\(rhsName.map { "\($0): " } ?? "")#\(id)", + nameSuffix: "", prefix: "\(subjectType)(", suffix: ")", elementIndent: 2, diff --git a/Sources/CustomDump/Dump.swift b/Sources/CustomDump/Dump.swift index 36c2623..0e6b2dc 100644 --- a/Sources/CustomDump/Dump.swift +++ b/Sources/CustomDump/Dump.swift @@ -88,6 +88,7 @@ func _customDump( _ value: T, to target: inout TargetStream, name: String?, + nameSuffix: String = ":", indent: Int, isRoot: Bool, maxDepth: Int, @@ -97,12 +98,17 @@ func _customDump( _ value: InnerT, to target: inout InnerTargetStream, name: String?, + nameSuffix: String, indent: Int, isRoot: Bool, maxDepth: Int ) where InnerTargetStream: TextOutputStream { if InnerT.self is AnyObject.Type, withUnsafeBytes(of: value, { $0.allSatisfy { $0 == 0 } }) { - target.write((name.map { "\($0): " } ?? "").appending("(null pointer)").indenting(by: indent)) + target.write( + (name.map { "\($0)\(nameSuffix) " } ?? "") + .appending("(null pointer)") + .indenting(by: indent) + ) return } @@ -127,6 +133,7 @@ func _customDump( child.value, to: &childOut, name: child.label, + nameSuffix: ":", indent: 0, isRoot: false, maxDepth: maxDepth - 1 @@ -157,6 +164,7 @@ func _customDump( child.value, to: &out, name: child.label, + nameSuffix: ":", indent: 2, isRoot: false, maxDepth: maxDepth - 1 @@ -200,12 +208,26 @@ func _customDump( } else { tracker.visitedItems.insert(item) occurrence += 1 - customDumpHelp(value, to: &out, name: nil, indent: 0, isRoot: false, maxDepth: maxDepth) + customDumpHelp( + value, + to: &out, + name: nil, + nameSuffix: "", + indent: 0, + isRoot: false, + maxDepth: maxDepth + ) } case let (value as CustomDumpRepresentable, _): customDumpHelp( - value.customDumpValue, to: &out, name: nil, indent: 0, isRoot: false, maxDepth: maxDepth + value.customDumpValue, + to: &out, + name: nil, + nameSuffix: "", + indent: 0, + isRoot: false, + maxDepth: maxDepth ) case let (value as AnyObject, .class?): @@ -327,7 +349,15 @@ func _customDump( case (_, .optional?): if let value = mirror.children.first?.value { - customDumpHelp(value, to: &out, name: nil, indent: 0, isRoot: false, maxDepth: maxDepth) + customDumpHelp( + value, + to: &out, + name: nil, + nameSuffix: "", + indent: 0, + isRoot: false, + maxDepth: maxDepth + ) } else { out.write("nil") } @@ -403,11 +433,17 @@ func _customDump( } } - target.write((name.map { "\($0): " } ?? "").appending(out).indenting(by: indent)) + target.write((name.map { "\($0)\(nameSuffix) " } ?? "").appending(out).indenting(by: indent)) } customDumpHelp( - value, to: &target, name: name, indent: indent, isRoot: isRoot, maxDepth: maxDepth + value, + to: &target, + name: name, + nameSuffix: nameSuffix, + indent: indent, + isRoot: isRoot, + maxDepth: maxDepth ) return value } @@ -415,6 +451,7 @@ func _customDump( func _customDump( _ value: Any, name: String?, + nameSuffix: String = ":", indent: Int, isRoot: Bool, maxDepth: Int, @@ -425,6 +462,7 @@ func _customDump( value, to: &out, name: name, + nameSuffix: nameSuffix, indent: indent, isRoot: isRoot, maxDepth: maxDepth, diff --git a/Tests/CustomDumpTests/DiffTests.swift b/Tests/CustomDumpTests/DiffTests.swift index 422786a..d2a987a 100644 --- a/Tests/CustomDumpTests/DiffTests.swift +++ b/Tests/CustomDumpTests/DiffTests.swift @@ -1227,8 +1227,14 @@ final class DiffTests: XCTestCase { var name = "Blob" } class Shared: _CustomDiffObject, Equatable { + let before: Any + let after: Any + init(before: Any = User(), after: Any = User(name: "Blob, Jr")) { + self.before = before + self.after = after + } var _customDiffValues: (Any, Any) { - (User(), User(name: "Blob, Jr")) + (self.before, self.after) } static func == (lhs: Shared, rhs: Shared) -> Bool { false @@ -1236,50 +1242,67 @@ final class DiffTests: XCTestCase { } let obj = Shared() +// XCTAssertNoDifference( +// diff(obj, obj), +// """ +// #1 DiffTests.User( +// id: 1, +// - name: "Blob" +// + name: "Blob, Jr" +// ) +// """ +// ) +// +// XCTAssertNoDifference( +// diff(Shared(), Shared()), +// """ +// - #1 DiffTests.User( +// - id: 1, +// - name: "Blob, Jr" +// - ) +// + #2 DiffTests.User( +// + id: 1, +// + name: "Blob, Jr" +// + ) +// """ +// ) + +// XCTAssertNoDifference( +// diff([obj, obj, obj], [obj, obj, Shared()]), +// """ +// [ +// [0]: #1 DiffTests.User( +// id: 1, +// - name: "Blob" +// + name: "Blob, Jr" +// ), +// - [1]: #1 DiffTests.User(↩︎), +// + [1]: #1 DiffTests.User(↩︎), +// - [2]: #1 DiffTests.User(↩︎) +// + [2]: #2 DiffTests.User( +// + id: 1, +// + name: "Blob, Jr" +// + ) +// ] +// """ +// ) + + struct State { + var stats: Shared + } + struct Stats { + var count = 0 + } + let stats = State(stats: Shared(before: Stats(), after: Stats(count: 1))) XCTAssertNoDifference( - diff(obj, obj), + diff(stats, stats), """ - #1 DiffTests.User( - id: 1, - - name: "Blob" - + name: "Blob, Jr" + DiffTests.State( + - stats: #1 DiffTests.Stats(count: 0) + + stats: #1 DiffTests.Stats(count: 1) ) """ ) - - XCTAssertNoDifference( - diff(Shared(), Shared()), - """ - - #1 DiffTests.User( - - id: 1, - - name: "Blob, Jr" - - ) - + #2 DiffTests.User( - + id: 1, - + name: "Blob, Jr" - + ) - """ - ) - - XCTAssertNoDifference( - diff([obj, obj, obj], [obj, obj, Shared()]), - """ - [ - #1 DiffTests.User( - id: 1, - - name: "Blob" - + name: "Blob, Jr" - ), - - [1]: #1 DiffTests.User(↩︎), - + [1]: #1 DiffTests.User(↩︎), - - [2]: #1 DiffTests.User(↩︎) - + [2]: #2 DiffTests.User( - + id: 1, - + name: "Blob, Jr" - + ) - ] - """ - ) } func testDiffableObject_Advanced() { @@ -1301,8 +1324,8 @@ final class DiffTests: XCTestCase { XCTAssertNoDifference( diff(obj, obj), """ - - #1: "before" - + #1: "after" + - #1 "before" + + #1 "after" """ ) @@ -1311,8 +1334,8 @@ final class DiffTests: XCTestCase { diff(bar, bar), """ DiffTests.DiffableObjects( - - #1: "before" - + #1: "after" + - obj1: #1 "before" + + obj1: #1 "after" - obj2: #1 String(↩︎) + obj2: #1 String(↩︎) ) From cc5827b3091ce707fd111ec370bb3c8ed8144635 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 7 Mar 2024 19:17:25 -0800 Subject: [PATCH 05/19] wip --- Sources/CustomDump/Diff.swift | 78 +-------------------------- Tests/CustomDumpTests/DiffTests.swift | 4 +- 2 files changed, 4 insertions(+), 78 deletions(-) diff --git a/Sources/CustomDump/Diff.swift b/Sources/CustomDump/Diff.swift index 0e5692c..e018fd6 100644 --- a/Sources/CustomDump/Diff.swift +++ b/Sources/CustomDump/Diff.swift @@ -131,7 +131,7 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String isRoot: false, maxDepth: 0, tracker: &tracker - ) + ) + separator let rhsDump = _customDump( rhs, name: rhsName, @@ -140,7 +140,7 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String isRoot: false, maxDepth: 0, tracker: &tracker - ) + ) + separator if lhsDump == rhsDump { print( "// Not equal but no difference detected:" @@ -397,80 +397,6 @@ public func diff(_ lhs: T, _ rhs: T, format: DiffFormat = .default) -> String } else { diffEverything() } -// let subjectType = typeName(lhsMirror.subjectType) -// if !tracker.visitedItems.contains(lhsItem) && !tracker.visitedItems.contains(rhsItem) { -// if lhsItem == rhsItem { -// diffChildren( -// lhsMirror, -// rhsMirror, -// prefix: "\(subjectType)(", -// suffix: ")", -// elementIndent: 2, -// elementSeparator: ",", -// collapseUnchanged: false, -// filter: macroPropertyFilter(for: lhs) -// ) -// } else { -// diffEverything() -// } -// } else { -// var occurrence: UInt { tracker.occurrencePerType[subjectType, default: 0] } -// if tracker.visitedItems.contains(lhsItem) { -// var lhsID: String { -// let id = tracker.idPerItem[lhsItem, default: occurrence] -// tracker.idPerItem[lhsItem] = id -// return id > 0 ? "#\(id) " : "" -// } -// print( -// "\(lhsName.map { "\($0): " } ?? "")\(lhsID)\(subjectType)(↩︎)" -// .indenting(by: indent) -// .indenting(with: format.first + " "), -// to: &out -// ) -// } else { -// print( -// _customDump( -// lhs, -// name: lhsName, -// indent: indent, -// isRoot: isRoot, -// maxDepth: .max, -// tracker: &tracker -// ) -// .indenting(with: format.first + " "), -// terminator: "", -// to: &out -// ) -// } -// if tracker.visitedItems.contains(rhsItem) { -// var rhsID: String { -// let id = tracker.idPerItem[rhsItem, default: occurrence] -// tracker.idPerItem[rhsItem] = id -// return id > 0 ? "#\(id) " : "" -// } -// print( -// "\(rhsName.map { "\($0): " } ?? "")\(rhsID)\(subjectType)(↩︎)" -// .indenting(by: indent) -// .indenting(with: format.second + " "), -// terminator: "", -// to: &out -// ) -// } else { -// print( -// _customDump( -// rhs, -// name: rhsName, -// indent: indent, -// isRoot: isRoot, -// maxDepth: .max, -// tracker: &tracker -// ) -// .indenting(with: format.second + " "), -// terminator: "", -// to: &out -// ) -// } -// } case let (lhs as CustomDumpRepresentable, _, rhs as CustomDumpRepresentable, _): out.write( diff --git a/Tests/CustomDumpTests/DiffTests.swift b/Tests/CustomDumpTests/DiffTests.swift index d2a987a..f69fa4a 100644 --- a/Tests/CustomDumpTests/DiffTests.swift +++ b/Tests/CustomDumpTests/DiffTests.swift @@ -1334,8 +1334,8 @@ final class DiffTests: XCTestCase { diff(bar, bar), """ DiffTests.DiffableObjects( - - obj1: #1 "before" - + obj1: #1 "after" + - obj1: #1 "before", + + obj1: #1 "after", - obj2: #1 String(↩︎) + obj2: #1 String(↩︎) ) From c10255c4097b9e74299b31e3f1b8efe7e94241ad Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 7 Mar 2024 19:18:38 -0800 Subject: [PATCH 06/19] wip --- Tests/CustomDumpTests/DiffTests.swift | 88 +++++++++++++-------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/Tests/CustomDumpTests/DiffTests.swift b/Tests/CustomDumpTests/DiffTests.swift index f69fa4a..8438965 100644 --- a/Tests/CustomDumpTests/DiffTests.swift +++ b/Tests/CustomDumpTests/DiffTests.swift @@ -1242,50 +1242,50 @@ final class DiffTests: XCTestCase { } let obj = Shared() -// XCTAssertNoDifference( -// diff(obj, obj), -// """ -// #1 DiffTests.User( -// id: 1, -// - name: "Blob" -// + name: "Blob, Jr" -// ) -// """ -// ) -// -// XCTAssertNoDifference( -// diff(Shared(), Shared()), -// """ -// - #1 DiffTests.User( -// - id: 1, -// - name: "Blob, Jr" -// - ) -// + #2 DiffTests.User( -// + id: 1, -// + name: "Blob, Jr" -// + ) -// """ -// ) - -// XCTAssertNoDifference( -// diff([obj, obj, obj], [obj, obj, Shared()]), -// """ -// [ -// [0]: #1 DiffTests.User( -// id: 1, -// - name: "Blob" -// + name: "Blob, Jr" -// ), -// - [1]: #1 DiffTests.User(↩︎), -// + [1]: #1 DiffTests.User(↩︎), -// - [2]: #1 DiffTests.User(↩︎) -// + [2]: #2 DiffTests.User( -// + id: 1, -// + name: "Blob, Jr" -// + ) -// ] -// """ -// ) + XCTAssertNoDifference( + diff(obj, obj), + """ + #1 DiffTests.User( + id: 1, + - name: "Blob" + + name: "Blob, Jr" + ) + """ + ) + + XCTAssertNoDifference( + diff(Shared(), Shared()), + """ + - #1 DiffTests.User( + - id: 1, + - name: "Blob, Jr" + - ) + + #2 DiffTests.User( + + id: 1, + + name: "Blob, Jr" + + ) + """ + ) + + XCTAssertNoDifference( + diff([obj, obj, obj], [obj, obj, Shared()]), + """ + [ + [0]: #1 DiffTests.User( + id: 1, + - name: "Blob" + + name: "Blob, Jr" + ), + - [1]: #1 DiffTests.User(↩︎), + + [1]: #1 DiffTests.User(↩︎), + - [2]: #1 DiffTests.User(↩︎) + + [2]: #2 DiffTests.User( + + id: 1, + + name: "Blob, Jr" + + ) + ] + """ + ) struct State { var stats: Shared From 5ad01b10cafedd9ae1d2a00881c91a24e0c6425f Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 9 Mar 2024 17:47:19 -0800 Subject: [PATCH 07/19] wip --- Tests/CustomDumpTests/DiffTests.swift | 7 ++----- Tests/CustomDumpTests/DumpTests.swift | 15 ++++++--------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/Tests/CustomDumpTests/DiffTests.swift b/Tests/CustomDumpTests/DiffTests.swift index 8438965..323cb06 100644 --- a/Tests/CustomDumpTests/DiffTests.swift +++ b/Tests/CustomDumpTests/DiffTests.swift @@ -1306,16 +1306,13 @@ final class DiffTests: XCTestCase { } func testDiffableObject_Advanced() { - class DiffableObject: _CustomDiffObject, Equatable { + class DiffableObject: _CustomDiffObject { var _customDiffValues: (Any, Any) { ("before", "after") } - static func == (lhs: DiffableObject, rhs: DiffableObject) -> Bool { - false - } } - struct DiffableObjects: Equatable { + struct DiffableObjects { var obj1: DiffableObject var obj2: DiffableObject } diff --git a/Tests/CustomDumpTests/DumpTests.swift b/Tests/CustomDumpTests/DumpTests.swift index 304d9ec..3158fc4 100644 --- a/Tests/CustomDumpTests/DumpTests.swift +++ b/Tests/CustomDumpTests/DumpTests.swift @@ -1360,21 +1360,18 @@ final class DumpTests: XCTestCase { var email: String } - class DiffableObject: _CustomDiffObject, Equatable { + class DiffableObject: _CustomDiffObject { var _customDiffValues: (Any, Any) { (Login(email: "blob@pointfree.co"), Login(email: "admin@pointfree.co")) } - static func == (lhs: DiffableObject, rhs: DiffableObject) -> Bool { - false - } } - struct DiffableObjects: Equatable { + struct DiffableObjects { var obj1: DiffableObject var obj2: DiffableObject } - struct DiffableObjectsParent: Equatable { + struct DiffableObjectsParent { var objs1: DiffableObjects var objs2: DiffableObjects } @@ -1426,7 +1423,7 @@ final class DumpTests: XCTestCase { } func testDiffableObject_Primitive() { - class DiffableObject: _CustomDiffObject, Equatable { + class DiffableObject: _CustomDiffObject { var _customDiffValues: (Any, Any) { ("before", "after") } @@ -1435,12 +1432,12 @@ final class DumpTests: XCTestCase { } } - struct DiffableObjects: Equatable { + struct DiffableObjects { var obj1: DiffableObject var obj2: DiffableObject } - struct DiffableObjectsParent: Equatable { + struct DiffableObjectsParent { var objs1: DiffableObjects var objs2: DiffableObjects } From 6de5f92b1582f75a3fe50a78907287f505d380fd Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Sat, 9 Mar 2024 18:14:12 -0800 Subject: [PATCH 08/19] wip --- Tests/CustomDumpTests/DiffTests.swift | 35 --------------------------- 1 file changed, 35 deletions(-) diff --git a/Tests/CustomDumpTests/DiffTests.swift b/Tests/CustomDumpTests/DiffTests.swift index 323cb06..5333b95 100644 --- a/Tests/CustomDumpTests/DiffTests.swift +++ b/Tests/CustomDumpTests/DiffTests.swift @@ -1304,41 +1304,6 @@ final class DiffTests: XCTestCase { """ ) } - - func testDiffableObject_Advanced() { - class DiffableObject: _CustomDiffObject { - var _customDiffValues: (Any, Any) { - ("before", "after") - } - } - - struct DiffableObjects { - var obj1: DiffableObject - var obj2: DiffableObject - } - - let obj = DiffableObject() - XCTAssertNoDifference( - diff(obj, obj), - """ - - #1 "before" - + #1 "after" - """ - ) - - let bar = DiffableObjects(obj1: obj, obj2: obj) - XCTAssertNoDifference( - diff(bar, bar), - """ - DiffTests.DiffableObjects( - - obj1: #1 "before", - + obj1: #1 "after", - - obj2: #1 String(↩︎) - + obj2: #1 String(↩︎) - ) - """ - ) - } } private struct Stack: CustomDumpReflectable, Equatable { From a4d74a8702a8c2deb01f61a4b4562cdde6a32a3d Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 12 Mar 2024 10:13:45 -0700 Subject: [PATCH 09/19] fix --- Tests/CustomDumpTests/DiffTests.swift | 45 ++++++++++++++------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/Tests/CustomDumpTests/DiffTests.swift b/Tests/CustomDumpTests/DiffTests.swift index 5333b95..686d72a 100644 --- a/Tests/CustomDumpTests/DiffTests.swift +++ b/Tests/CustomDumpTests/DiffTests.swift @@ -1226,26 +1226,12 @@ final class DiffTests: XCTestCase { let id = 1 var name = "Blob" } - class Shared: _CustomDiffObject, Equatable { - let before: Any - let after: Any - init(before: Any = User(), after: Any = User(name: "Blob, Jr")) { - self.before = before - self.after = after - } - var _customDiffValues: (Any, Any) { - (self.before, self.after) - } - static func == (lhs: Shared, rhs: Shared) -> Bool { - false - } - } let obj = Shared() XCTAssertNoDifference( diff(obj, obj), """ - #1 DiffTests.User( + #1 User( id: 1, - name: "Blob" + name: "Blob, Jr" @@ -1256,11 +1242,11 @@ final class DiffTests: XCTestCase { XCTAssertNoDifference( diff(Shared(), Shared()), """ - - #1 DiffTests.User( + - #1 User( - id: 1, - name: "Blob, Jr" - ) - + #2 DiffTests.User( + + #2 User( + id: 1, + name: "Blob, Jr" + ) @@ -1271,15 +1257,15 @@ final class DiffTests: XCTestCase { diff([obj, obj, obj], [obj, obj, Shared()]), """ [ - [0]: #1 DiffTests.User( + [0]: #1 User( id: 1, - name: "Blob" + name: "Blob, Jr" ), - - [1]: #1 DiffTests.User(↩︎), - + [1]: #1 DiffTests.User(↩︎), - - [2]: #1 DiffTests.User(↩︎) - + [2]: #2 DiffTests.User( + - [1]: #1 User(↩︎), + + [1]: #1 User(↩︎), + - [2]: #1 User(↩︎) + + [2]: #2 User( + id: 1, + name: "Blob, Jr" + ) @@ -1306,6 +1292,21 @@ final class DiffTests: XCTestCase { } } +private class Shared: _CustomDiffObject, Equatable { + let before: Any + let after: Any + init(before: Any = User(id: 1, name: "Blob"), after: Any = User(id: 1, name: "Blob, Jr")) { + self.before = before + self.after = after + } + var _customDiffValues: (Any, Any) { + (self.before, self.after) + } + static func == (lhs: Shared, rhs: Shared) -> Bool { + false + } +} + private struct Stack: CustomDumpReflectable, Equatable { static func == (lhs: Self, rhs: Self) -> Bool { zip(lhs.elements, rhs.elements).allSatisfy(==) From 8cb770921e291d12f0344736431ae71f8b74a95b Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 12 Mar 2024 12:40:25 -0700 Subject: [PATCH 10/19] Swift 5.7 fix --- Sources/CustomDump/Dump.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/CustomDump/Dump.swift b/Sources/CustomDump/Dump.swift index 0e6b2dc..510b394 100644 --- a/Sources/CustomDump/Dump.swift +++ b/Sources/CustomDump/Dump.swift @@ -458,6 +458,8 @@ func _customDump( tracker: inout ObjectTracker ) -> String { var out = "" + var t = tracker + defer { tracker = t } _customDump( value, to: &out, @@ -466,7 +468,7 @@ func _customDump( indent: indent, isRoot: isRoot, maxDepth: maxDepth, - tracker: &tracker + tracker: &t ) return out } From 707ee1914509b371f223cfb667d35c7a91b2de4d Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 12 Mar 2024 12:58:30 -0700 Subject: [PATCH 11/19] wip --- .github/workflows/ci.yml | 36 ++++++++++----------------- Package.swift | 2 +- Package@swift-5.5.swift | 36 --------------------------- Tests/CustomDumpTests/DumpTests.swift | 2 +- 4 files changed, 15 insertions(+), 61 deletions(-) delete mode 100644 Package@swift-5.5.swift diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b478b57..8443371 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,15 +14,15 @@ concurrency: cancel-in-progress: true jobs: - macos-13: - name: macOS 13 (Xcode ${{ matrix.xcode }}) + macos-14: + name: macOS 14 (Xcode ${{ matrix.xcode }}) runs-on: macOS-13 strategy: matrix: xcode: - - '14.3.1' + - '15.2' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Select Xcode ${{ matrix.xcode }} run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app - name: Print Swift version @@ -32,18 +32,15 @@ jobs: - name: Run tests (platforms) run: make test-platforms - macos-12: - name: macOS 12 (Xcode ${{ matrix.xcode }}) - runs-on: macOS-12 + macos-13: + name: macOS 13 (Xcode ${{ matrix.xcode }}) + runs-on: macOS-13 strategy: matrix: xcode: - - '13.3.1' - - '13.4.1' - - '14.0.1' - - '14.1' + - '14.3.1' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Select Xcode ${{ matrix.xcode }} run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app - name: Print Swift version @@ -59,12 +56,10 @@ jobs: strategy: matrix: swift: - - 5.5 - - 5.6 - 5.7 - 5.8 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run tests run: make test-linux SWIFT_VERSION=${{ matrix.swift }} @@ -87,15 +82,10 @@ jobs: steps: - uses: compnerd/gha-setup-swift@main with: - branch: swift-5.8.1-release - tag: 5.8.1-RELEASE - - uses: actions/checkout@v3 + branch: swift-5.9.2-release + tag: 5.9.2-RELEASE + - uses: actions/checkout@v4 - name: Build All Configurations run: swift build -c ${{ matrix.config }} - name: Run tests (debug only) - # There is an issue that exists in the 5.8.1 toolchain - # which fails on release configuration testing, but - # this issue is fixed 5.9 so we can remove the if once - # that is generally available. - if: ${{ matrix.config == 'debug' }} run: swift test diff --git a/Package.swift b/Package.swift index f557dda..e5c40e1 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.1 +// swift-tools-version:5.7 import PackageDescription diff --git a/Package@swift-5.5.swift b/Package@swift-5.5.swift deleted file mode 100644 index 97034a5..0000000 --- a/Package@swift-5.5.swift +++ /dev/null @@ -1,36 +0,0 @@ -// swift-tools-version:5.5 - -import PackageDescription - -let package = Package( - name: "swift-custom-dump", - platforms: [ - .iOS(.v13), - .macOS(.v10_15), - .tvOS(.v13), - .watchOS(.v6), - ], - products: [ - .library( - name: "CustomDump", - targets: ["CustomDump"] - ) - ], - dependencies: [ - .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0") - ], - targets: [ - .target( - name: "CustomDump", - dependencies: [ - .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay") - ] - ), - .testTarget( - name: "CustomDumpTests", - dependencies: [ - "CustomDump" - ] - ), - ] -) diff --git a/Tests/CustomDumpTests/DumpTests.swift b/Tests/CustomDumpTests/DumpTests.swift index 3158fc4..fbe9d97 100644 --- a/Tests/CustomDumpTests/DumpTests.swift +++ b/Tests/CustomDumpTests/DumpTests.swift @@ -709,7 +709,7 @@ final class DumpTests: XCTestCase { func testKeyPath() { var dump = "" - #if swift(>=5.9) + #if swift(>=5.9) && (os(iOS) || os(macOS) || os(tvOS) || os(watchOS)) if #available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) { dump = "" customDump(\UserClass.name, to: &dump) From 1f31a07104506e93c8b133f16fe210110b6ad4cd Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 12 Mar 2024 13:00:34 -0700 Subject: [PATCH 12/19] wip --- .github/workflows/ci.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8443371..f4cabca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,8 +56,7 @@ jobs: strategy: matrix: swift: - - 5.7 - - 5.8 + - 5.10 steps: - uses: actions/checkout@v4 - name: Run tests @@ -82,8 +81,8 @@ jobs: steps: - uses: compnerd/gha-setup-swift@main with: - branch: swift-5.9.2-release - tag: 5.9.2-RELEASE + branch: swift-5.10-release + tag: 5.10-RELEASE - uses: actions/checkout@v4 - name: Build All Configurations run: swift build -c ${{ matrix.config }} From 31ac464b43086cf31f39a04550758a6c5ca5c1f9 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 12 Mar 2024 13:01:25 -0700 Subject: [PATCH 13/19] wip --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4cabca..5572ddb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,7 @@ jobs: strategy: matrix: swift: - - 5.10 + - '5.10' steps: - uses: actions/checkout@v4 - name: Run tests From 38710b38e0cca6835bd2a0af173f103e8694ff0a Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 12 Mar 2024 13:05:11 -0700 Subject: [PATCH 14/19] wip --- Tests/CustomDumpTests/DumpTests.swift | 54 --------------------------- 1 file changed, 54 deletions(-) diff --git a/Tests/CustomDumpTests/DumpTests.swift b/Tests/CustomDumpTests/DumpTests.swift index fbe9d97..b37fd5e 100644 --- a/Tests/CustomDumpTests/DumpTests.swift +++ b/Tests/CustomDumpTests/DumpTests.swift @@ -824,60 +824,6 @@ final class DumpTests: XCTestCase { """# ) } - #else - dump = "" - customDump(\UserClass.name, to: &dump) - XCTAssertNoDifference( - dump, - #""" - KeyPath - """# - ) - - dump = "" - customDump(\Pair.driver.name, to: &dump) - XCTAssertNoDifference( - dump, - #""" - KeyPath - """# - ) - - dump = "" - customDump(\User.name.count, to: &dump) - XCTAssertNoDifference( - dump, - #""" - KeyPath - """# - ) - - dump = "" - customDump(\(x: Double, y: Double).x, to: &dump) - XCTAssertNoDifference( - dump, - #""" - WritableKeyPath<(x: Double, y: Double), Double> - """# - ) - - dump = "" - customDump(\Item.$isInStock, to: &dump) - XCTAssertNoDifference( - dump, - #""" - KeyPath> - """# - ) - - dump = "" - customDump(\Wrapped.count, to: &dump) - XCTAssertNoDifference( - dump, - #""" - KeyPath, Int> - """# - ) #endif } From 13797032258e77d379adaaf298cb680794ebee55 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 12 Mar 2024 13:15:05 -0700 Subject: [PATCH 15/19] wip --- Tests/CustomDumpTests/DumpTests.swift | 123 ++------------------------ 1 file changed, 6 insertions(+), 117 deletions(-) diff --git a/Tests/CustomDumpTests/DumpTests.swift b/Tests/CustomDumpTests/DumpTests.swift index b37fd5e..69d6e03 100644 --- a/Tests/CustomDumpTests/DumpTests.swift +++ b/Tests/CustomDumpTests/DumpTests.swift @@ -708,123 +708,12 @@ final class DumpTests: XCTestCase { } func testKeyPath() { - var dump = "" - #if swift(>=5.9) && (os(iOS) || os(macOS) || os(tvOS) || os(watchOS)) - if #available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) { - dump = "" - customDump(\UserClass.name, to: &dump) - XCTAssertNoDifference( - dump, - #""" - \UserClass.name - """# - ) - - dump = "" - customDump(\Pair.driver.name, to: &dump) - XCTAssertNoDifference( - dump, - #""" - \Pair.driver.name - """# - ) - - dump = "" - customDump(\User.name.count, to: &dump) - XCTAssertNoDifference( - dump, - #""" - \User.name.count - """# - ) - - dump = "" - customDump(\(x: Double, y: Double).x, to: &dump) - XCTAssertNoDifference( - dump, - #""" - \(x: Double, y: Double).x - """# - ) - - dump = "" - customDump(\Item.$isInStock, to: &dump) - XCTAssertNoDifference( - dump, - #""" - \Item.$isInStock - """# - ) - - dump = "" - customDump(\Wrapped.count, to: &dump) - XCTAssertNoDifference( - dump, - #""" - \Wrapped.subscript(dynamicMember: ) - """# - ) - return - } - #endif - #if os(iOS) || os(macOS) || os(tvOS) || os(watchOS) - // Run twice to exercise cached lookup - for _ in 1...2 { - dump = "" - customDump(\UserClass.name, to: &dump) - XCTAssertNoDifference( - dump, - #""" - \UserClass.name - """# - ) - - dump = "" - customDump(\Pair.driver.name, to: &dump) - XCTAssertNoDifference( - dump, - #""" - \Pair.driver.name - """# - ) - - dump = "" - customDump(\User.name.count, to: &dump) - XCTAssertNoDifference( - dump, - #""" - KeyPath - """# - ) - - dump = "" - customDump(\(x: Double, y: Double).x, to: &dump) - XCTAssertNoDifference( - dump, - #""" - WritableKeyPath<(x: Double, y: Double), Double> - """# - ) - - dump = "" - customDump(\Item.$isInStock, to: &dump) - XCTAssertNoDifference( - dump, - #""" - KeyPath> - """# - ) - - dump = "" - customDump(\Wrapped.count, to: &dump) - XCTAssertNoDifference( - dump, - #""" - KeyPath, Int> - """# - ) - } - #endif + _ = String(customDumping: \UserClass.name) + _ = String(customDumping: \Pair.driver.name) + _ = String(customDumping: \User.name.count) + _ = String(customDumping: \(x: Double, y: Double).x) + _ = String(customDumping: \Item.$isInStock) + _ = String(customDumping: \Wrapped.count) } func testNamespacedTypes() { From 7367b6b6728138d90211faa327558dabc6f6a1fa Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 12 Mar 2024 13:28:52 -0700 Subject: [PATCH 16/19] wip --- .github/workflows/ci.yml | 4 ---- Makefile | 9 +++------ 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5572ddb..3f214ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,8 +27,6 @@ jobs: run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app - name: Print Swift version run: swift --version - - name: Run tests (Swift) - run: make test-swift - name: Run tests (platforms) run: make test-platforms @@ -45,8 +43,6 @@ jobs: run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app - name: Print Swift version run: swift --version - - name: Run tests (Swift) - run: make test-swift - name: Run tests (platforms) run: make test-platforms diff --git a/Makefile b/Makefile index 6836ec8..da51dab 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,7 @@ PLATFORM_IOS = iOS Simulator,name=iPhone 11 Pro Max PLATFORM_MACOS = macOS PLATFORM_MAC_CATALYST = macOS,variant=Mac Catalyst PLATFORM_TVOS = tvOS Simulator,name=Apple TV -SWIFT_VERSION = 5.5 -ifeq ($(SWIFT_VERSION),5.3) -SWIFT_BUILD_ARGS = --enable-test-discovery -endif +SWIFT_VERSION = 5.7 SWIFT_TEST_ARGS = --parallel test-all: test-linux test-swift test-platforms @@ -19,8 +16,8 @@ test-linux: bash -c 'apt-get update && apt-get -y install make && make test-swift SWIFT_VERSION=$(SWIFT_VERSION)' test-swift: - swift test $(SWIFT_BUILD_ARGS) $(SWIFT_TEST_ARGS) - swift test --configuration release $(SWIFT_BUILD_ARGS) $(SWIFT_TEST_ARGS) + swift test $(SWIFT_TEST_ARGS) + swift test --configuration release $(SWIFT_TEST_ARGS) test-platforms: xcodebuild test \ From e4ef18b8594299e77376ff9a1289aedbf670749e Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 12 Mar 2024 13:35:27 -0700 Subject: [PATCH 17/19] wip --- Sources/CustomDump/Conformances/KeyPath.swift | 852 +----------------- 1 file changed, 5 insertions(+), 847 deletions(-) diff --git a/Sources/CustomDump/Conformances/KeyPath.swift b/Sources/CustomDump/Conformances/KeyPath.swift index 4ad07fd..90a8f25 100644 --- a/Sources/CustomDump/Conformances/KeyPath.swift +++ b/Sources/CustomDump/Conformances/KeyPath.swift @@ -8,852 +8,10 @@ extension AnyKeyPath: CustomDumpStringConvertible { return self.debugDescription } #endif - #if DEBUG && (os(iOS) || os(macOS) || os(tvOS) || os(watchOS)) - keyPathToNameLock.lock() - defer { keyPathToNameLock.unlock() } - - guard let name = keyPathToName[self] else { - func reflectName() -> String { - var namedKeyPaths = Reflection.allNamedKeyPaths(forUnderlyingTypeOf: Self.rootType) - while !namedKeyPaths.isEmpty { - let (name, keyPath) = namedKeyPaths.removeFirst() - if keyPath == self { - return #"\\#(typeName(Self.rootType)).\#(name)"# - } - let valueType = type(of: keyPath).valueType - let valueNamedKeyPaths = Reflection.allNamedKeyPaths(forUnderlyingTypeOf: valueType) - for (valueName, valueKeyPath) in valueNamedKeyPaths { - if let appendedKeyPath = keyPath.appending(path: valueKeyPath) { - namedKeyPaths.append(("\(name).\(valueName)", appendedKeyPath)) - } - } - } - return """ - \(typeName(Self.self))<\ - \(typeName(Self.rootType, genericsAbbreviated: false)), \ - \(typeName(Self.valueType, genericsAbbreviated: false))> - """ - } - let name = reflectName() - keyPathToName[self] = name - return name - } - return name - #else - return """ - \(typeName(Self.self))<\ - \(typeName(Self.rootType, genericsAbbreviated: false)), \ - \(typeName(Self.valueType, genericsAbbreviated: false))> - """ - #endif + return """ + \(typeName(Self.self))<\ + \(typeName(Self.rootType, genericsAbbreviated: false)), \ + \(typeName(Self.valueType, genericsAbbreviated: false))> + """ } } - -#if DEBUG && (os(iOS) || os(macOS) || os(tvOS) || os(watchOS)) - private var keyPathToNameLock = NSRecursiveLock() - private var keyPathToName: [AnyKeyPath: String] = [:] - - // The source code below was extracted from the "KeyPath Reflection" branch of Apple's - // "Swift Evolution Staging" package: - // - // https://github.com/apple/swift-evolution-staging/tree/reflection - - //===----------------------------------------------------------------------===// - // - // This source file is part of the Swift.org open source project - // - // Copyright (c) 2020 Apple Inc. and the Swift project authors - // Licensed under Apache License v2.0 with Runtime Library Exception - // - // See https://swift.org/LICENSE.txt for license information - // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors - // - //===----------------------------------------------------------------------===// - - private protocol RelativePointer { - associatedtype Pointee - - var offset: Int32 { get } - - func address(from ptr: UnsafeRawPointer) -> UnsafePointer - func pointee(from ptr: UnsafeRawPointer) -> Pointee? - } - - extension RelativePointer { - fileprivate func address(from ptr: UnsafeRawPointer) -> UnsafePointer { - let newPtr = UnsafeRawPointer( - bitPattern: UInt(bitPattern: ptr) &+ UInt(bitPattern: Int(offset)))! - return newPtr.assumingMemoryBound(to: Pointee.self) - } - } - - private struct RelativeDirectPointer: RelativePointer { - let offset: Int32 - - func pointee(from ptr: UnsafeRawPointer) -> Pointee? { - guard offset != 0 else { - return nil - } - - return address(from: ptr).pointee - } - } - - extension UnsafeRawPointer { - fileprivate func relativeDirect(as type: T.Type) -> UnsafePointer { - let relativePointer = RelativeDirectPointer( - offset: load(as: Int32.self) - ) - return relativePointer.address(from: self) - } - } - - private struct RelativeIndirectPointer: RelativePointer { - typealias Pointee = UnsafePointer - - let offset: Int32 - - func pointee(from ptr: UnsafeRawPointer) -> Pointee? { - guard offset != 0 else { - return nil - } - - return address(from: ptr).pointee - } - } - - private struct RelativeIndirectablePointer: RelativePointer { - let offset: Int32 - - func address(from ptr: UnsafeRawPointer) -> UnsafePointer { - UnsafePointer((ptr + Int(offset & ~1))._rawValue) - } - - func pointee(from ptr: UnsafeRawPointer) -> Pointee? { - guard offset != 0 else { - return nil - } - - if offset & 1 == 1 { - let pointer = UnsafeRawPointer(address(from: ptr)) - .load(as: UnsafePointer.self) - return pointer.pointee - } else { - return address(from: ptr).pointee - } - } - } - - //===----------------------------------------------------------------------===// - // Metadata Structures - //===----------------------------------------------------------------------===// - - // MetadataKind is the discriminator value found at the start of all metadata - // records to determine what kind is a metadata. - private enum MetadataKind: Int { - case `class` = 0 - case `struct` = 512 - } - - // Metadata refers to the runtime representation of a type in Swift. This - // protocol is the generic version handed out by various methods to retrieve - // metadata from types. - private protocol Metadata { - // The required backing pointer which points at the metadata record. - var pointer: UnsafeRawPointer { get } - - // The discriminator which determines what kind of metadata this is. - var kind: MetadataKind { get } - } - - extension Metadata { - // The type representation of the metadata. - fileprivate var type: Any.Type { - unsafeBitCast(pointer, to: Any.Type.self) - } - } - - // Given an arbitrary type of anything, produce the metadata that represents - // said type. - // FIXME: Right now this only supports structs and class types, but in the - // future if we ever want to produce keypaths for tuples, enums, etc. handle - // that here. - private func getMetadata(for type: Any.Type) -> Metadata? { - let pointer = unsafeBitCast(type, to: UnsafeRawPointer.self) - let int = pointer.load(as: Int.self) - - guard let kind = MetadataKind(rawValue: int) else { - // If the metadata kind is greater than 2047, then it's an ISA pointer - // meaning we have some class metadata. - guard int > 2047 else { - return nil - } - - return ClassMetadata(pointer: pointer) - } - - switch kind { - case .class: - return ClassMetadata(pointer: pointer) - case .struct: - return StructMetadata(pointer: pointer) - } - } - - // Type Metadata - - // Type Metadata is a more specialized metadata in that only struct, class, and - // enum types conform to. There's more detail about the type and its properties - // in the context descriptors, the generic types that make up said type, etc. - private protocol TypeMetadata: Metadata {} - - extension TypeMetadata { - // The context descriptors describes more in detail about the type. Some of - // this information includes number of properties, the property names, the - // name of this type, generic requirements, etc. - fileprivate var contextDescriptor: TypeContextDescriptor { - switch self { - case let structMetadata as StructMetadata: - return structMetadata.descriptor - case let classMetadata as ClassMetadata: - return classMetadata.descriptor - default: - fatalError("TypeMetadata conformance we don't know about?") - } - } - - // An array of integers that represent the offset to a certain field. This - // corresponds to the index of fields in the field descriptor. - fileprivate var fieldOffsets: [Int] { - switch self { - case let structMetadata as StructMetadata: - return structMetadata.fieldOffsets - case let classMetadata as ClassMetadata: - return classMetadata.fieldOffsets - default: - fatalError("TypeMetadata conformance we don't know about?") - } - } - - // The pointer to the beginning of this type's generic arguments. - fileprivate var genericArgumentPointer: UnsafeRawPointer { - switch self { - case is StructMetadata: - return pointer + MemoryLayout<_StructMetadata>.size - case let classMetadata as ClassMetadata: - let descriptor = classMetadata.descriptor - - guard !descriptor.typeFlags.classHasResilientSuperclass else { - let memberOffset = descriptor.resilientBounds._immediateMembersOffset - return pointer + memberOffset - } - - let negativeSize = descriptor.negativeSize - let positiveSize = descriptor.positiveSize - let numImmediateMembers = descriptor.numImmediateMembers - - if descriptor.typeFlags.classAreImmediateMembersNegative { - return pointer + MemoryLayout.size * -negativeSize - } else { - return pointer + MemoryLayout.size * (positiveSize - numImmediateMembers) - } - default: - fatalError("TypeMetadata conformance we don't know about?") - } - } - - // Given a mangled name (preferrably one of the property type name's), return - // the type as represented by the mangled name within this type's context. - fileprivate func type(of mangledName: UnsafePointer) -> Any.Type? { - let type = _getTypeByMangledNameInContext( - UnsafePointer(mangledName._rawValue), - UInt(getSymbolicMangledNameLength(UnsafeRawPointer(mangledName))), - genericContext: contextDescriptor.pointer, - genericArguments: genericArgumentPointer - ) - - return type - } - } - - // Struct Metadata - - // Struct Metadata refers to types whom are implemented via a struct. Consider - // the standard library type 'Int', it's implemented using a struct, so getting - // the type metadata for that type will return an instance of struct metadata. - private struct StructMetadata: TypeMetadata, LayoutWrapper { - typealias Layout = _StructMetadata - - // The backing metadata pointer. - let pointer: UnsafeRawPointer - - // The metadata discriminator. - var kind: MetadataKind { - .struct - } - - // The context descriptor of this struct. - var descriptor: StructDescriptor { - layout.descriptor - } - - // An array of integers with the offsets for each stored field in this struct. - var fieldOffsets: [Int] { - let fieldOffsetVectorOffset = descriptor.fieldOffsetVectorOffset - let start = pointer + MemoryLayout.size * fieldOffsetVectorOffset - let buffer = UnsafeBufferPointer( - start: UnsafePointer(start._rawValue), - count: descriptor.numFields - ) - return Array(buffer).map { Int($0) } - } - } - - private struct _StructMetadata { - let kind: Int - let descriptor: StructDescriptor - } - - // Class Metadata - - // Class Metadata refers to types whom are implemented via a class. Consider - // the standard library type 'KeyPath', it's implemented using a class, so - // getting the type metadata for that type will return an instance of class - // metadata. - private struct ClassMetadata: TypeMetadata, LayoutWrapper { - typealias Layout = _ClassMetadata - - // The backing metadata pointer. - let pointer: UnsafeRawPointer - - // The metadata discriminator. - var kind: MetadataKind { - .class - } - - // The context descriptor of this class. - var descriptor: ClassDescriptor { - layout._descriptor - } - - // An array of integers with the offsets for each stored field in this class. - var fieldOffsets: [Int] { - let fieldOffsetVectorOffset = descriptor.fieldOffsetVectorOffset - let start = pointer + MemoryLayout.size * fieldOffsetVectorOffset - let buffer = UnsafeBufferPointer( - start: UnsafePointer(start._rawValue), - count: descriptor.numFields - ) - return Array(buffer) - } - - // The required size of instances of this type. - var instanceSize: Int { - Int(layout._instanceSize) - } - - // The alignment mask of the address point for instances of this type. - var instanceAlignMask: Int { - Int(layout._instanceAlignMask) - } - } - - private struct _ClassMetadata { - let _kind: Int - let _superclass: Any.Type? - let _reserved: (Int, Int) - let _rodata: Int - let _flags: UInt32 - let _instanceAddressPoint: UInt32 - let _instanceSize: UInt32 - let _instanceAlignMask: UInt16 - let _runtimeReserved: UInt16 - let _classSize: UInt32 - let _classAddressPoint: UInt32 - let _descriptor: ClassDescriptor - } - - //===----------------------------------------------------------------------===// - // Context Descriptor Structures - //===----------------------------------------------------------------------===// - - // A context descriptor describes in entity in Swift who declares some context - // which other declarations can be declared within. - private protocol ContextDescriptor { - // The backing context descriptor pointer. - var pointer: UnsafeRawPointer { get } - } - - extension ContextDescriptor { - // The base structural representation of a context descriptor. - fileprivate var _contextDescriptor: _ContextDescriptor { - pointer.load(as: _ContextDescriptor.self) - } - - // Flags that describe this context which include what kind it is, whether - // or not it's a generic context, whether or not it's unique, etc. - fileprivate var flags: ContextDescriptorFlags { - _contextDescriptor._flags - } - } - - private struct _ContextDescriptor { - let _flags: ContextDescriptorFlags - let _parent: RelativeIndirectablePointer<_ContextDescriptor> - } - - // Flags that describe this context which include what kind it is, whether - // or not it's a generic context, whether or not it's unique, etc. - private struct ContextDescriptorFlags { - // The backing integer representation of these flags. - let bits: UInt32 - - // The reserved bits for other flags that are interpretted differently by - // conforming context descriptor types. - var kindSpecificFlags: UInt16 { - UInt16((bits >> 0x10) & 0xFFFF) - } - } - - // Type Context Descriptor - - // A type context descriptor is a refined context descriptor who describes a - // type in Swift. This includes structs, classes, and enums. Protocols also - // define a new type in Swift, but aren't considered type contexts. - private protocol TypeContextDescriptor: ContextDescriptor { - // The field descriptor that describes the stored representation of this type. - var fields: FieldDescriptor { get } - } - - extension TypeContextDescriptor { - // The base structural representation of a type context descriptor. - fileprivate var _typeDescriptor: _TypeContextDescriptor { - pointer.load(as: _TypeContextDescriptor.self) - } - - // The field descriptor that describes the stored representation of this type. - fileprivate var fields: FieldDescriptor { - let offset = pointer.advanced(by: MemoryLayout.size * 4) - let address = UnsafeRawPointer(_typeDescriptor._fields.address(from: offset)) - return FieldDescriptor(signedPointer: address) - } - - // Certain flags specific to types in Swift, such as whether or not a class - // has a resilient superclass. - fileprivate var typeFlags: TypeContextDescriptorFlags { - TypeContextDescriptorFlags(bits: flags.kindSpecificFlags) - } - } - - private struct _TypeContextDescriptor { - let _base: _ContextDescriptor - let _name: RelativeDirectPointer - let _accessor: RelativeDirectPointer - let _fields: RelativeDirectPointer<_FieldDescriptor> - } - - // Certain flags specific to types in Swift, such as whether or not a class - // has a resilient superclass. - private struct TypeContextDescriptorFlags { - // The backing integer representation of these flags. - let bits: UInt16 - - // Whether or not the class's members are negative. - var classAreImmediateMembersNegative: Bool { - bits & 0x1000 != 0 - } - - // Whether or not the class has a resilient superclass. - var classHasResilientSuperclass: Bool { - bits & 0x2000 != 0 - } - } - - // Struct Descriptor - - // A struct descriptor that describes some structure context. - private struct StructDescriptor: TypeContextDescriptor, PointerAuthenticatedLayoutWrapper { - typealias Layout = _StructDescriptor - - // The backing context descriptor pointer. - let signedPointer: UnsafeRawPointer - - // The offset to the field offset vector found in the metadata. - var fieldOffsetVectorOffset: Int { - Int(layout._fieldOffsetVectorOffset) - } - - // The number of stored properties this struct has. - var numFields: Int { - Int(layout._numFields) - } - } - - private struct _StructDescriptor { - let _base: _TypeContextDescriptor - let _numFields: UInt32 - let _fieldOffsetVectorOffset: UInt32 - } - - // Class Descriptor - - // A class descriptor that descibes some class context. - private struct ClassDescriptor: TypeContextDescriptor, PointerAuthenticatedLayoutWrapper { - typealias Layout = _ClassDescriptor - - // The backing context descriptor pointer. - let signedPointer: UnsafeRawPointer - - // The offset to the field offset vector found in the metadata. - var fieldOffsetVectorOffset: Int { - Int(layout._fieldOffsetVectorOffset) - } - - // The negative size of the metadata objects in this class. - var negativeSize: Int { - assert(!typeFlags.classHasResilientSuperclass) - return Int(layout._negativeSizeOrResilientBounds) - } - - // The number of stored properties this class defines. - var numFields: Int { - Int(layout._numFields) - } - - // The total number of members this class defines (not including it's - // superclass, if it has one). - var numImmediateMembers: Int { - Int(layout._numImmediateMembers) - } - - // The positive size of the metadata objects in this class. - var positiveSize: Int { - assert(!typeFlags.classHasResilientSuperclass) - return Int(layout._positiveSizeOrExtraFlags) - } - - // The resilient bounds for this class. - var resilientBounds: _StoredClassMetadataBounds { - let addr = address(for: \._negativeSizeOrResilientBounds) - let pointer = UnsafeRawPointer(addr) - return pointer.relativeDirect(as: _StoredClassMetadataBounds.self).pointee - } - } - - private struct _ClassDescriptor { - let _base: _TypeContextDescriptor - let _superclassMangledName: RelativeDirectPointer - let _negativeSizeOrResilientBounds: Int32 - let _positiveSizeOrExtraFlags: Int32 - let _numImmediateMembers: UInt32 - let _numFields: UInt32 - let _fieldOffsetVectorOffset: UInt32 - } - - private struct _StoredClassMetadataBounds { - let _immediateMembersOffset: Int - } - - // Field Descriptor - - // A special descriptor that describes a type's fields. - private struct FieldDescriptor: PointerAuthenticatedLayoutWrapper { - typealias Layout = _FieldDescriptor - - // The backing field descriptor pointer. - let signedPointer: UnsafeRawPointer - - // The number of fields this type has. This could mean different things - // depending on what kind of type this is found under. For example, this is - // the number of stored properties found within a struct, but for enums this - // is the number of cases. - var numFields: Int { - Int(layout._numFields) - } - - // An array of the field record information. Field record information contains - // things like it's mangled type, whether or not its a var, indirect, etc. - var records: [FieldRecord] { - var result = [FieldRecord]() - result.reserveCapacity(numFields) - - for i in 0...size * i - result.append(FieldRecord(signedPointer: address)) - } - - return result - } - } - - // A record that describes a single stored property or an enum case. - private struct FieldRecord: PointerAuthenticatedLayoutWrapper { - typealias Layout = _FieldRecord - - // The backing field record pointer. - let signedPointer: UnsafeRawPointer - - // The flags that describe this field record. - var flags: FieldRecordFlags { - layout._flags - } - - // The mangled type name that demangles to the field's type. - var mangledTypeName: UnsafePointer { - address(for: \._mangledTypeName) - } - - // The name of the stored property/enum case. - var name: String { - String(cString: address(for: \._fieldName)) - } - } - - private struct _FieldDescriptor { - let _mangledTypeName: RelativeDirectPointer - let _superclassMangledTypeName: RelativeDirectPointer - let _kind: UInt16 - let _recordSize: UInt16 - let _numFields: UInt32 - } - - private struct _FieldRecord { - let _flags: FieldRecordFlags - let _mangledTypeName: RelativeDirectPointer - let _fieldName: RelativeDirectPointer - } - - // The flags which describe a field record. - private struct FieldRecordFlags { - // The backing integer representation of these flags. - let bits: UInt32 - - // Whether or not this stored property is a var. - var isVar: Bool { - bits & 0x2 != 0 - } - } - - //===----------------------------------------------------------------------===// - // Misc. Utilities - //===----------------------------------------------------------------------===// - - private protocol LayoutWrapper { - associatedtype Layout - var pointer: UnsafeRawPointer { get } - } - - private protocol PointerAuthenticatedLayoutWrapper: LayoutWrapper { - var signedPointer: UnsafeRawPointer { get } - } - - extension PointerAuthenticatedLayoutWrapper { - fileprivate var pointer: UnsafeRawPointer { - signedPointer - } - } - - extension LayoutWrapper { - fileprivate var layout: Layout { - pointer.load(as: Layout.self) - } - - fileprivate var trailing: UnsafeRawPointer { - pointer + MemoryLayout.size - } - - fileprivate func address(for field: KeyPath) -> UnsafePointer { - let offset = MemoryLayout.offset(of: field)! - return UnsafePointer((pointer + offset)._rawValue) - } - - fileprivate func address( - for field: KeyPath - ) -> UnsafePointer where T.Pointee == U { - let offset = MemoryLayout.offset(of: field)! - return layout[keyPath: field].address(from: pointer + offset) - } - } - - // This is a utility within KeyPath.swift in the standard library. If this - // gets moved into there, then this goes away, but will have to rethink if this - // goes into a different module. - private func getSymbolicMangledNameLength(_ base: UnsafeRawPointer) -> Int { - var end = base - while let current = Optional(end.load(as: UInt8.self)), current != 0 { - // Skip the current character - end = end + 1 - - // Skip over a symbolic reference - if current >= 0x1 && current <= 0x17 { - end += 4 - } else if current >= 0x18 && current <= 0x1F { - end += MemoryLayout.size - } - } - - return end - base - } - - @_silgen_name("swift_allocObject") - internal func _allocObject(_: UnsafeMutableRawPointer, _: Int, _: Int) -> AnyObject? - - // This is a utility within KeyPath.swift in the standard library. If this - // gets moved into there, then this goes away, but will have to rethink if this - // goes into a different module. - extension AnyKeyPath { - fileprivate static func _create( - capacityInBytes bytes: Int, - initializedBy body: (UnsafeMutableRawBufferPointer) -> Void - ) -> Self { - assert( - bytes > 0 && bytes % 4 == 0, - "capacity must be multiple of 4 bytes") - let metadata = getMetadata(for: self) as! ClassMetadata - var size = metadata.instanceSize - - let tailStride = MemoryLayout.stride - let tailAlignMask = MemoryLayout.alignment - 1 - - size += tailAlignMask - size &= ~tailAlignMask - size += tailStride * (bytes / 4) - - let alignment = metadata.instanceAlignMask | tailAlignMask - - let object = _allocObject( - UnsafeMutableRawPointer(mutating: metadata.pointer), - size, - alignment - ) - - guard object != nil else { - fatalError("Allocating \(self) instance failed for keypath reflection") - } - - // This memory layout of Int by 2 is the size of a heap object which object - // points to. Tail members appear immediately afterwards. - let base = - unsafeBitCast(object, to: UnsafeMutableRawPointer.self) + MemoryLayout.size * 2 - - // The first word is the kvc string pointer. Set it to 0 (nil). - base.storeBytes(of: 0, as: Int.self) - - // Return an offseted base after the kvc string pointer. - let newBase = base + MemoryLayout.size - let newBytes = bytes - MemoryLayout.size - - body(UnsafeMutableRawBufferPointer(start: newBase, count: newBytes)) - - return unsafeBitCast(object, to: self) - } - } - - // Helper struct to represent the keypath buffer header. This structure is also - // found within KeyPath.swift, so if this gets moved there this goes away. - private struct KeyPathBufferHeader { - let bits: UInt32 - - init(hasReferencePrefix: Bool, isTrivial: Bool, size: UInt32) { - var bits = size - - if hasReferencePrefix { - bits |= 0x4000_0000 - } - - if isTrivial { - bits |= 0x8000_0000 - } - - self.bits = bits - } - } - - // This initializes the raw keypath buffer with the field offset information. - private func instantiateKeyPathBuffer( - _ metadata: TypeMetadata, - _ leafIndex: Int, - _ data: UnsafeMutableRawBufferPointer - ) { - let header = KeyPathBufferHeader( - hasReferencePrefix: false, - isTrivial: true, - size: UInt32(MemoryLayout.size) - ) - - data.storeBytes(of: header, as: KeyPathBufferHeader.self) - - var componentBits = UInt32(metadata.fieldOffsets[leafIndex]) - componentBits |= metadata.kind == .struct ? 1 << 24 : 3 << 24 - - data.storeBytes( - of: componentBits, - toByteOffset: MemoryLayout.size, - as: UInt32.self - ) - } - - // Returns a concrete type for which this keypath is going to be given a root - // and leaf type. - private func getKeyPathType( - from root: TypeMetadata, - for leaf: FieldRecord - ) -> AnyKeyPath.Type { - let leafType = root.type(of: leaf.mangledTypeName)! - - func openRoot(_: Root.Type) -> AnyKeyPath.Type { - func openLeaf(_: Value.Type) -> AnyKeyPath.Type { - if leaf.flags.isVar { - return root.kind == .class - ? ReferenceWritableKeyPath.self - : WritableKeyPath.self - } - return KeyPath.self - } - return _openExistential(leafType, do: openLeaf) - } - return _openExistential(root.type, do: openRoot) - } - - // Given a root type and a leaf index, create a concrete keypath object at - // runtime. - private func createKeyPath(root: TypeMetadata, leaf: Int) -> AnyKeyPath { - let field = root.contextDescriptor.fields.records[leaf] - - let keyPathTy = getKeyPathType(from: root, for: field) - let size = MemoryLayout.size * 3 - let instance = keyPathTy._create(capacityInBytes: size) { - instantiateKeyPathBuffer(root, leaf, $0) - } - - let heapObj = UnsafeRawPointer(Unmanaged.passRetained(instance).autorelease().toOpaque()) - let keyPath = unsafeBitCast(heapObj, to: AnyKeyPath.self) - return keyPath - } - - private enum Reflection { - /// Returns the collection of all named key paths of this type. - /// - /// - Parameter value: A value of any type to return the stored key paths of. - /// - Returns: An array of tuples with both the name and partial key path - /// for this value. - static func allNamedKeyPaths( - forUnderlyingTypeOf type: Any.Type - ) -> [(name: String, keyPath: AnyKeyPath)] { - guard let metadata = getMetadata(for: type) as? TypeMetadata else { - return [] - } - - var result = [(name: String, keyPath: AnyKeyPath)]() - result.reserveCapacity(metadata.contextDescriptor.fields.numFields) - - for i in 0.. Date: Tue, 12 Mar 2024 13:45:26 -0700 Subject: [PATCH 18/19] wip --- Tests/CustomDumpTests/DumpTests.swift | 120 ++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 6 deletions(-) diff --git a/Tests/CustomDumpTests/DumpTests.swift b/Tests/CustomDumpTests/DumpTests.swift index 69d6e03..c67f7d3 100644 --- a/Tests/CustomDumpTests/DumpTests.swift +++ b/Tests/CustomDumpTests/DumpTests.swift @@ -708,12 +708,120 @@ final class DumpTests: XCTestCase { } func testKeyPath() { - _ = String(customDumping: \UserClass.name) - _ = String(customDumping: \Pair.driver.name) - _ = String(customDumping: \User.name.count) - _ = String(customDumping: \(x: Double, y: Double).x) - _ = String(customDumping: \Item.$isInStock) - _ = String(customDumping: \Wrapped.count) + // NB: While this should run on >=5.9, it currently crashes CI on Xcode 15.2 + #if swift(>=5.10) && (os(iOS) || os(macOS) || os(tvOS) || os(watchOS)) + var dump = "" + if #available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) { + dump = "" + customDump(\UserClass.name, to: &dump) + XCTAssertNoDifference( + dump, + #""" + \UserClass.name + """# + ) + + dump = "" + customDump(\Pair.driver.name, to: &dump) + XCTAssertNoDifference( + dump, + #""" + \Pair.driver.name + """# + ) + + dump = "" + customDump(\User.name.count, to: &dump) + XCTAssertNoDifference( + dump, + #""" + \User.name.count + """# + ) + + dump = "" + customDump(\(x: Double, y: Double).x, to: &dump) + XCTAssertNoDifference( + dump, + #""" + \(x: Double, y: Double).x + """# + ) + + dump = "" + customDump(\Item.$isInStock, to: &dump) + XCTAssertNoDifference( + dump, + #""" + \Item.$isInStock + """# + ) + + dump = "" + customDump(\Wrapped.count, to: &dump) + XCTAssertNoDifference( + dump, + #""" + \Wrapped.subscript(dynamicMember: ) + """# + ) + return + } else { + dump = "" + customDump(\UserClass.name, to: &dump) + XCTAssertNoDifference( + dump, + #""" + \UserClass.name + """# + ) + + dump = "" + customDump(\Pair.driver.name, to: &dump) + XCTAssertNoDifference( + dump, + #""" + \Pair.driver.name + """# + ) + + dump = "" + customDump(\User.name.count, to: &dump) + XCTAssertNoDifference( + dump, + #""" + KeyPath + """# + ) + + dump = "" + customDump(\(x: Double, y: Double).x, to: &dump) + XCTAssertNoDifference( + dump, + #""" + WritableKeyPath<(x: Double, y: Double), Double> + """# + ) + + dump = "" + customDump(\Item.$isInStock, to: &dump) + XCTAssertNoDifference( + dump, + #""" + KeyPath> + """# + ) + + dump = "" + customDump(\Wrapped.count, to: &dump) + XCTAssertNoDifference( + dump, + #""" + KeyPath, Int> + """# + ) + } + #endif } func testNamespacedTypes() { From 042ae840e8f6b4d40a8ca3edc80a721f26db2796 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 12 Mar 2024 14:05:40 -0700 Subject: [PATCH 19/19] wip --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a14ad4..d93c580 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,7 @@ jobs: name: SwiftWasm runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: bytecodealliance/actions/wasmtime/setup@v1 - uses: swiftwasm/setup-swiftwasm@v1 with: