Hello all,
We would like to add a macro, to the Swift standard library, that improves debugging in the following ways:
- Debugger users will be able to see debug descriptions in circumstances they previously could not, such as Xcode’s variable view and crashlogs
- Code authors can implement, in Swift, how the debugger summarizes their data type – via compatible
debugDescription
ordescription
implementations
To demonstrate, consider an example:
struct Student: CustomDebugStringConvertible {
var name: String
var id: Int
// more properties
var debugDescription: String {
"\(id): \(name)"
}
}
To display this debug description, LLDB needs to perform expression evaluation. LLDB does expression evaluation on demand, commonly using the po
command. Outside of explicit commands, expression evaluation is generally not performed, and in some cases not even possible.
Expression evaluation can be avoided in this case, by defining an LLDB Type Summary. The following manually constructed command creates a type summary using LLDB’s summary string format:
(lldb) type summary add -s '${var.id}: ${var.name}' PupilKit.Student
In this case, and many others, the Swift source of debugDescription
can be converted to an LLDB type summary. Any debugDescription
implementation that references only stored properties, should in theory be convertible.
We propose a macro which converts compatible implementations of debugDescription
or description
into LLDB summary strings, and will embed that string into the binary. LLDB will load these records automatically. An additional benefit of this reuse is authors can write unit test for their debug descriptions, to catch regressions.
In the above example, the change is the minor addition of the macro:
@DebugDescription
struct Student: CustomDebugStringConvertible {
// same as before
}
The @DebugDescription
macro generates global constants (via the peer
role) that contains the type name, and the converted summary string. For demonstration only, the expanded macro might look like:
@_section("__DATA_CONST,__lldbsummaries")
let Student_lldb_summary = ("PupilKit.Student", "${var.id}: ${var.name}")
The reason this is demonstration only is that @_section
globals do not support String
values. Instead, the values emitted will be tuples of UInt8
(in UTF-8 encoding). Additionally, the implementation would support other platform specific section names (the above is Darwin specific).
When attached to an incompatible implementation of debugDescription
or description
, the macro will emit a warning or error. An incompatible implementation is one that requires expression evaluation, this includes function calls, initializers, arithmetic and other operators, casting, etc. Anything outside of property reads can be assumed to require expression evaluation. The initial version will support string literals, as they map to directly to summary strings. To support a wider range of implementations, such as those that construct strings using conditionals or loops (but still not function calls) the macro would instead generate an LLDB script. This functionality would be a future improvement.
Unfortunately, there are cases where the macro would be not be able to identify an incompatible implementation. A computed property, unlike a stored property, requires expression evaluation. At the AST level, some references to computed properties can appear indistinguishable from a reference to a stored property. This is an inherent constraint of macros and the scope of the AST that’s made available to them. When the macro is unable to identify use of a computed property at compile time, LLDB will emit a warning at debug time.
Thank you for your feedback and ideas!