Skip to content

Commit 9866e69

Browse files
committed
[PlaygroundLogger] Reimplemented the proper rules for summaries.
Unfortunately, summaries aren't just as simple as calling `String(describing:)`. The legacy logger had some somewhat complex rules, but ultimately it fell back to the type name if nothing user-provided was available. This reimplements that logic (save for truncation behavior).
1 parent 8bc37c7 commit 9866e69

File tree

1 file changed

+60
-19
lines changed

1 file changed

+60
-19
lines changed

PlaygroundLogger/PlaygroundLogger/LogEntry+Reflection.swift

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,42 +21,62 @@ fileprivate let emptyNameString = ""
2121

2222
extension LogEntry {
2323
init(describing instance: Any, name: String? = nil, policy: LogPolicy) throws {
24-
self = try .init(describing: instance, name: name ?? emptyNameString, typeName: nil, summary: nil, policy: policy, currentDepth: 0)
24+
self = try .init(describing: instance, name: name ?? emptyNameString, typeName: nil, policy: policy, currentDepth: 0)
2525
}
2626

27-
fileprivate init(describing instance: Any, name: String, typeName passedInTypeName: String?, summary passedInSummary: String?, policy: LogPolicy, currentDepth: Int) throws {
27+
fileprivate init(describing instance: Any, name: String, typeName passedInTypeName: String?, policy: LogPolicy, currentDepth: Int) throws {
2828
guard currentDepth <= policy.maximumDepth else {
2929
// We're trying to log an instance that is "too deep"; as a result, we need to just return a gap.
3030
self = .gap
3131
return
3232
}
3333

34-
// Returns either the passed-in type name/summary or the type name/summary of `instance`.
35-
var typeName: String { return passedInTypeName ?? normalizedName(of: type(of: instance)) }
36-
var summary: String { return passedInSummary ?? String(describing: instance) }
34+
// Lazily-load the Mirror for this instance. (This is factored out this way as the Mirror is needed in a few different code paths.)
35+
var _mirrorStorage: Mirror? = nil
36+
var mirror: Mirror {
37+
if let mirror = _mirrorStorage {
38+
return mirror
39+
}
40+
41+
let mirror = Mirror(reflecting: instance)
42+
_mirrorStorage = mirror
43+
return mirror
44+
}
45+
46+
// Lazily-load the normalized type name for this instance. (This is factored out this way as the type name is expensive to compute, so we only want to do it once.)
47+
var _typeNameStorage: String? = nil
48+
var typeName: String {
49+
if let typeName = _typeNameStorage {
50+
return typeName
51+
}
52+
53+
let typeName = passedInTypeName ?? normalizedName(of: type(of: instance))
54+
_typeNameStorage = typeName
55+
return typeName
56+
}
3757

3858
// For types which conform to the `CustomPlaygroundDisplayConvertible` protocol, get their custom representation and then run it back through the initializer.
3959
if let customPlaygroundDisplayConvertible = instance as? CustomPlaygroundDisplayConvertible {
40-
self = try .init(describing: customPlaygroundDisplayConvertible.playgroundDescription, name: name, typeName: typeName, summary: nil, policy: policy, currentDepth: currentDepth)
60+
self = try .init(describing: customPlaygroundDisplayConvertible.playgroundDescription, name: name, typeName: typeName, policy: policy, currentDepth: currentDepth)
4161
}
4262

4363
// For types which conform to the `CustomOpaqueLoggable` protocol, get their custom representation and construct an opaque log entry. (This is checked *second* so that user implementations of `CustomPlaygroundDisplayConvertible` are honored over this framework's implementations of `CustomOpaqueLoggable`.)
4464
else if let customOpaqueLoggable = instance as? CustomOpaqueLoggable {
4565
// TODO: figure out when to set `preferBriefSummary` to true
46-
self = try .opaque(name: name, typeName: typeName, summary: summary, preferBriefSummary: false, representation: customOpaqueLoggable.opaqueRepresentation())
66+
self = try .opaque(name: name, typeName: typeName, summary: generateSummary(for: instance, withTypeName: typeName, using: mirror), preferBriefSummary: false, representation: customOpaqueLoggable.opaqueRepresentation())
4767
}
4868

4969
// For types which conform to the legacy `CustomPlaygroundQuickLookable` or `_DefaultCustomPlaygroundQuickLookable` protocols, get their `PlaygroundQuickLook` and use that for logging.
5070
else if let customQuickLookable = instance as? CustomPlaygroundQuickLookable {
51-
self = try .init(playgroundQuickLook: customQuickLookable.customPlaygroundQuickLook, name: name, typeName: typeName, summary: summary)
71+
self = try .init(playgroundQuickLook: customQuickLookable.customPlaygroundQuickLook, name: name, typeName: typeName, summary: generateSummary(for: instance, withTypeName: typeName, using: mirror))
5272
}
5373
else if let defaultQuickLookable = instance as? _DefaultCustomPlaygroundQuickLookable {
54-
self = try .init(playgroundQuickLook: defaultQuickLookable._defaultCustomPlaygroundQuickLook, name: name, typeName: typeName, summary: summary)
74+
self = try .init(playgroundQuickLook: defaultQuickLookable._defaultCustomPlaygroundQuickLook, name: name, typeName: typeName, summary: generateSummary(for: instance, withTypeName: typeName, using: mirror))
5575
}
5676

5777
// If a type implements the `debugQuickLookObject()` Objective-C method, then get their debug quick look object and use that for logging (by passing it back through this initializer).
5878
else if let debugQuickLookObjectMethod = (instance as AnyObject).debugQuickLookObject, let debugQuickLookObject = debugQuickLookObjectMethod() {
59-
self = try .init(describing: debugQuickLookObject, name: name, typeName: typeName, summary: nil, policy: policy, currentDepth: currentDepth)
79+
self = try .init(describing: debugQuickLookObject, name: name, typeName: typeName, policy: policy, currentDepth: currentDepth)
6080
}
6181

6282
// Otherwise, first check if this is an interesting CF type before logging structure.
@@ -65,23 +85,20 @@ extension LogEntry {
6585
switch CFGetTypeID(instance as CFTypeRef) {
6686
case CGColor.typeID:
6787
let cgColor = instance as! CGColor
68-
self = .opaque(name: name, typeName: typeName, summary: summary, preferBriefSummary: false, representation: cgColor.opaqueRepresentation())
88+
self = .opaque(name: name, typeName: typeName, summary: generateSummary(for: instance, withTypeName: typeName, using: mirror), preferBriefSummary: false, representation: cgColor.opaqueRepresentation())
6989
case CGImage.typeID:
7090
let cgImage = instance as! CGImage
71-
self = .opaque(name: name, typeName: typeName, summary: summary, preferBriefSummary: false, representation: cgImage.opaqueRepresentation())
91+
self = .opaque(name: name, typeName: typeName, summary: generateSummary(for: instance, withTypeName: typeName, using: mirror), preferBriefSummary: false, representation: cgImage.opaqueRepresentation())
7292
default:
7393
// This isn't one of the CF types we want to specially handle, so the log entry should just reflect the instance's structure.
74-
75-
// Get a Mirror which reflects the instance being logged.
76-
let mirror = Mirror(reflecting: instance)
77-
94+
7895
if mirror.displayStyle == .optional && mirror.children.count == 1 {
7996
// If the mirror displays as an Optional and has exactly one child, then we want to unwrap the optionality and generate a log entry for the child.
80-
self = try .init(describing: mirror.children.first!.value, name: name, typeName: nil, summary: nil, policy: policy, currentDepth: currentDepth)
97+
self = try .init(describing: mirror.children.first!.value, name: name, typeName: nil, policy: policy, currentDepth: currentDepth)
8198
}
8299
else {
83100
// Otherwise, we want to generate a log entry with the structure from the mirror.
84-
self = .init(structureFrom: mirror, name: name, typeName: typeName, summary: summary, policy: policy, currentDepth: currentDepth)
101+
self = .init(structureFrom: mirror, name: name, typeName: typeName, summary: generateSummary(for: instance, withTypeName: typeName, using: mirror), policy: policy, currentDepth: currentDepth)
85102
}
86103
}
87104
}
@@ -167,7 +184,7 @@ extension Mirror {
167184

168185
func logEntry(forChild child: Mirror.Child) -> LogEntry {
169186
do {
170-
return try LogEntry(describing: child.value, name: child.label ?? emptyNameString, typeName: nil, summary: nil, policy: policy, currentDepth: childDepth)
187+
return try LogEntry(describing: child.value, name: child.label ?? emptyNameString, typeName: nil, policy: policy, currentDepth: childDepth)
171188
}
172189
catch let LoggingError.failedToGenerateOpaqueRepresentation(reason) {
173190
return LogEntry.error(reason: reason)
@@ -249,3 +266,27 @@ extension Mirror {
249266
return LogEntry(structureFrom: self, name: name, typeName: subjectTypeName, summary: subjectTypeName, policy: policy, currentDepth: depth)
250267
}
251268
}
269+
270+
/// Construct the summary for `instance`.
271+
///
272+
/// In precedence order, the rules are:
273+
/// - If the instance is itself a `String`, return the instance
274+
/// - If the instance is `CustomStringConvertible` or `CustomDebugStringConvertible`, use `String(reflecting:)`
275+
/// - If the instance is an enum (as reported using Mirror), use `String(describing:)`
276+
/// - Otherwise, use the normalized type name
277+
fileprivate func generateSummary(for instance: Any, withTypeName typeNameProvider: @autoclosure () -> String, using mirrorProvider: @autoclosure () -> Mirror) -> String {
278+
if let string = instance as? String {
279+
return string
280+
}
281+
282+
if instance is CustomStringConvertible || instance is CustomDebugStringConvertible {
283+
return String(reflecting: instance)
284+
}
285+
286+
let mirror = mirrorProvider()
287+
if mirror.displayStyle == .enum {
288+
return String(describing: instance)
289+
}
290+
291+
return typeNameProvider()
292+
}

0 commit comments

Comments
 (0)