mirror of
https://github.com/pointfreeco/swift-composable-architecture.git
synced 2025-12-24 12:14:25 +01:00
Gracefully handle recursive state debug output (#117)
* Gracefully handle recursive state debug output * Fix
This commit is contained in:
@@ -1,104 +1,147 @@
|
||||
import Foundation
|
||||
|
||||
func debugOutput(_ value: Any, indent: Int = 0) -> String {
|
||||
let mirror = Mirror(reflecting: value)
|
||||
switch (value, mirror.displayStyle) {
|
||||
case let (value as CustomDebugOutputConvertible, _):
|
||||
return value.debugOutput.indent(by: indent)
|
||||
case (_, .collection?):
|
||||
return """
|
||||
[
|
||||
\(mirror.children.map { "\(debugOutput($0.value, indent: 2)),\n" }.joined())]
|
||||
"""
|
||||
.indent(by: indent)
|
||||
case (_, .dictionary?):
|
||||
let pairs = mirror.children.map { label, value -> String in
|
||||
let pair = value as! (key: AnyHashable, value: Any)
|
||||
return "\("\(debugOutput(pair.key.base)): \(debugOutput(pair.value)),".indent(by: 2))\n"
|
||||
}
|
||||
return """
|
||||
[
|
||||
\(pairs.sorted().joined())]
|
||||
"""
|
||||
.indent(by: indent)
|
||||
case (_, .set?):
|
||||
return """
|
||||
Set([
|
||||
\(mirror.children.map { "\(debugOutput($0.value, indent: 2)),\n" }.sorted().joined())])
|
||||
"""
|
||||
.indent(by: indent)
|
||||
case (_, .optional?):
|
||||
return mirror.children.isEmpty
|
||||
? "nil".indent(by: indent)
|
||||
: debugOutput(mirror.children.first!.value, indent: indent)
|
||||
case (_, .enum?) where !mirror.children.isEmpty:
|
||||
let child = mirror.children.first!
|
||||
let childMirror = Mirror(reflecting: child.value)
|
||||
let elements =
|
||||
childMirror.displayStyle != .tuple
|
||||
? debugOutput(child.value, indent: 2)
|
||||
: childMirror.children.map { child -> String in
|
||||
let label = child.label!
|
||||
return "\(label.hasPrefix(".") ? "" : "\(label): ")\(debugOutput(child.value))"
|
||||
var visitedItems: Set<ObjectIdentifier> = []
|
||||
|
||||
func debugOutputHelp(_ value: Any, indent: Int = 0) -> String {
|
||||
let mirror = Mirror(reflecting: value)
|
||||
switch (value, mirror.displayStyle) {
|
||||
case let (value as CustomDebugOutputConvertible, _):
|
||||
return value.debugOutput.indent(by: indent)
|
||||
case (_, .collection?):
|
||||
return """
|
||||
[
|
||||
\(mirror.children.map { "\(debugOutput($0.value, indent: 2)),\n" }.joined())]
|
||||
"""
|
||||
.indent(by: indent)
|
||||
|
||||
case (_, .dictionary?):
|
||||
let pairs = mirror.children.map { label, value -> String in
|
||||
let pair = value as! (key: AnyHashable, value: Any)
|
||||
return
|
||||
"\("\(debugOutputHelp(pair.key.base)): \(debugOutputHelp(pair.value)),".indent(by: 2))\n"
|
||||
}
|
||||
.joined(separator: ",\n")
|
||||
.indent(by: 2)
|
||||
return """
|
||||
\(mirror.subjectType).\(child.label!)(
|
||||
\(elements)
|
||||
)
|
||||
"""
|
||||
.indent(by: indent)
|
||||
case (_, .enum?):
|
||||
return """
|
||||
\(mirror.subjectType).\(value)
|
||||
"""
|
||||
.indent(by: indent)
|
||||
case (_, .struct?) where !mirror.children.isEmpty, (_, .class?) where !mirror.children.isEmpty:
|
||||
let elements = mirror.children
|
||||
.map { "\($0.label.map { "\($0): " } ?? "")\(debugOutput($0.value))".indent(by: 2) }
|
||||
.joined(separator: ",\n")
|
||||
return """
|
||||
\(mirror.subjectType)(
|
||||
\(elements)
|
||||
)
|
||||
"""
|
||||
.indent(by: indent)
|
||||
case let (value as CustomStringConvertible, .class?):
|
||||
return value.description
|
||||
.replacingOccurrences(of: #"^<([^:]+): 0x[^>]+>$"#, with: "$1()", options: .regularExpression)
|
||||
.indent(by: indent)
|
||||
case let (value as CustomDebugStringConvertible, _):
|
||||
return value.debugDescription
|
||||
.replacingOccurrences(of: #"^<([^:]+): 0x[^>]+>$"#, with: "$1()", options: .regularExpression)
|
||||
.indent(by: indent)
|
||||
case let (value as CustomStringConvertible, _):
|
||||
return value.description
|
||||
.indent(by: indent)
|
||||
case (_, .struct?), (_, .class?):
|
||||
return "\(mirror.subjectType)()"
|
||||
.indent(by: indent)
|
||||
case (_, .tuple?) where mirror.children.isEmpty:
|
||||
return "()"
|
||||
.indent(by: indent)
|
||||
case (_, .tuple?):
|
||||
let elements = mirror.children.map { child -> String in
|
||||
let label = child.label!
|
||||
return "\(label.hasPrefix(".") ? "" : "\(label): ")\(debugOutput(child.value))".indent(by: 2)
|
||||
return """
|
||||
[
|
||||
\(pairs.sorted().joined())]
|
||||
"""
|
||||
.indent(by: indent)
|
||||
|
||||
case (_, .set?):
|
||||
return """
|
||||
Set([
|
||||
\(mirror.children.map { "\(debugOutputHelp($0.value, indent: 2)),\n" }.sorted().joined())])
|
||||
"""
|
||||
.indent(by: indent)
|
||||
|
||||
case (_, .optional?):
|
||||
return mirror.children.isEmpty
|
||||
? "nil".indent(by: indent)
|
||||
: debugOutputHelp(mirror.children.first!.value, indent: indent)
|
||||
|
||||
case (_, .enum?) where !mirror.children.isEmpty:
|
||||
let child = mirror.children.first!
|
||||
let childMirror = Mirror(reflecting: child.value)
|
||||
let elements =
|
||||
childMirror.displayStyle != .tuple
|
||||
? debugOutputHelp(child.value, indent: 2)
|
||||
: childMirror.children.map { child -> String in
|
||||
let label = child.label!
|
||||
return "\(label.hasPrefix(".") ? "" : "\(label): ")\(debugOutputHelp(child.value))"
|
||||
}
|
||||
.joined(separator: ",\n")
|
||||
.indent(by: 2)
|
||||
return """
|
||||
\(mirror.subjectType).\(child.label!)(
|
||||
\(elements)
|
||||
)
|
||||
"""
|
||||
.indent(by: indent)
|
||||
|
||||
case (_, .enum?):
|
||||
return """
|
||||
\(mirror.subjectType).\(value)
|
||||
"""
|
||||
.indent(by: indent)
|
||||
|
||||
case (_, .struct?) where !mirror.children.isEmpty:
|
||||
let elements = mirror.children
|
||||
.map { "\($0.label.map { "\($0): " } ?? "")\(debugOutputHelp($0.value))".indent(by: 2) }
|
||||
.joined(separator: ",\n")
|
||||
return """
|
||||
\(mirror.subjectType)(
|
||||
\(elements)
|
||||
)
|
||||
"""
|
||||
.indent(by: indent)
|
||||
|
||||
case let (value as AnyObject, .class?)
|
||||
where !mirror.children.isEmpty && !visitedItems.contains(ObjectIdentifier(value)):
|
||||
visitedItems.insert(ObjectIdentifier(value))
|
||||
let elements = mirror.children
|
||||
.map { "\($0.label.map { "\($0): " } ?? "")\(debugOutputHelp($0.value))".indent(by: 2) }
|
||||
.joined(separator: ",\n")
|
||||
return """
|
||||
\(mirror.subjectType)(
|
||||
\(elements)
|
||||
)
|
||||
"""
|
||||
.indent(by: indent)
|
||||
|
||||
case let (value as AnyObject, .class?)
|
||||
where !mirror.children.isEmpty && visitedItems.contains(ObjectIdentifier(value)):
|
||||
return "\(mirror.subjectType)(↩︎)"
|
||||
|
||||
case let (value as CustomStringConvertible, .class?):
|
||||
return value.description
|
||||
.replacingOccurrences(
|
||||
of: #"^<([^:]+): 0x[^>]+>$"#, with: "$1()", options: .regularExpression
|
||||
)
|
||||
.indent(by: indent)
|
||||
|
||||
case let (value as CustomDebugStringConvertible, _):
|
||||
return value.debugDescription
|
||||
.replacingOccurrences(
|
||||
of: #"^<([^:]+): 0x[^>]+>$"#, with: "$1()", options: .regularExpression
|
||||
)
|
||||
.indent(by: indent)
|
||||
|
||||
case let (value as CustomStringConvertible, _):
|
||||
return value.description
|
||||
.indent(by: indent)
|
||||
|
||||
case (_, .struct?), (_, .class?):
|
||||
return "\(mirror.subjectType)()"
|
||||
.indent(by: indent)
|
||||
|
||||
case (_, .tuple?) where mirror.children.isEmpty:
|
||||
return "()"
|
||||
.indent(by: indent)
|
||||
|
||||
case (_, .tuple?):
|
||||
let elements = mirror.children.map { child -> String in
|
||||
let label = child.label!
|
||||
return "\(label.hasPrefix(".") ? "" : "\(label): ")\(debugOutputHelp(child.value))"
|
||||
.indent(by: 2)
|
||||
}
|
||||
return """
|
||||
(
|
||||
\(elements.joined(separator: ",\n"))
|
||||
)
|
||||
"""
|
||||
.indent(by: indent)
|
||||
|
||||
case (_, nil):
|
||||
return "\(value)"
|
||||
.indent(by: indent)
|
||||
|
||||
@unknown default:
|
||||
return "\(value)"
|
||||
.indent(by: indent)
|
||||
}
|
||||
return """
|
||||
(
|
||||
\(elements.joined(separator: ",\n"))
|
||||
)
|
||||
"""
|
||||
.indent(by: indent)
|
||||
case (_, nil):
|
||||
return "\(value)"
|
||||
.indent(by: indent)
|
||||
@unknown default:
|
||||
return "\(value)"
|
||||
.indent(by: indent)
|
||||
}
|
||||
|
||||
return debugOutputHelp(value, indent: indent)
|
||||
}
|
||||
|
||||
func debugDiff<T>(_ before: T, _ after: T, printer: (T) -> String = { debugOutput($0) }) -> String?
|
||||
|
||||
@@ -373,6 +373,22 @@ final class DebugTests: XCTestCase {
|
||||
)
|
||||
}
|
||||
|
||||
func testRecursiveOutput() {
|
||||
class Foo {
|
||||
var foo: Foo?
|
||||
}
|
||||
let foo = Foo()
|
||||
foo.foo = foo
|
||||
XCTAssertEqual(
|
||||
debugOutput(foo),
|
||||
"""
|
||||
Foo(
|
||||
foo: Foo(↩︎)
|
||||
)
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
func testEffectOutput() {
|
||||
// XCTAssertEqual(
|
||||
// Effect<Int, Never>(value: 42)
|
||||
|
||||
Reference in New Issue
Block a user