While implementing the infrastructure changes for the proposed Clang diagnostics improvements, it was discovered that I was conflating multiple issues that can be independently implemented. As articulated in the improvements thread, the diagnostics that Clang currently issues are terse. We currently aim to balance getting the necessary information across and to not bog the user down in text that they don’t need. This is a product of having unstructured text that’s output to a terminal, but structured diagnostics need not have this model. Structured diagnostics that are consumed by a second tool to render the diagnostics for humans, and can provide an interface that progressively discloses information at a user’s behest.
Progressive disclosure within the context of a single diagnostic requires identifying what information should be presented first, and what information may sometimes need a bit of effort to reveal. Progressive disclosure isn’t really something that tooling can work out on its own (unless AI is involved, which isn’t an equitable expectation). The current Clang model requires us to put the entire diagnostic in a single string. By splitting up the string into a statement that summarises the problem, and a string that can potentially give more background, we can provide tooling with a way to present relevant information at appropriate times.
Design
The following design is intended to be backwards-compatible with the current diagnostic model. We intend to add a second (optional) configurable field to the diagnostic interface that allows us to provide a reason. The following is an example of what that might look like.
Before
class Diagnostic<string text, DiagClass DC, Severity defaultmapping> {
...
string Text = text;
}
...
class Error<string str> : Diagnostic<str, CLASS_ERROR, SEV_Error>, SFINAEFailure {
bit ShowInSystemHeader = 1;
}
After
class DiagReason<string value> {
string Value = value;
}
def NO_REASON_YET : DiagReason<"">;
class Diagnostic<string summary, DiagClass DC, Severity defaultmapping, DiagReason reason> {
...
string Summary = summary;
...
DiagReason Reason = reason;
}
...
class Error<string str, DiagReason reason = NO_REASON_YET> : Diagnostic<str, CLASS_ERROR, SEV_Error, reason>, SFINAEFailure {
bit ShowInSystemHeader = 1;
}
...
This will make it possible for diagnostics to have a reason, but doesn’t require tens of thousands of reasons to immediately manifest for this design to be viable by defaulting them to the empty string. When writing a diagnostic, it will be good for developers to assume that the reason will be displayed, because when it’s present, it will be. To be fully backwards-compatible, we intend for diagnostics that are split over the summary
field and the reason
field to be concatenated in the form summary + ": " + reason
for the command line. This will make it possible for diagnostics to be presented as they already are (barring rephrasing). Finally, there won’t be any changes to the way parameters are consumed: we’ll still be using %0
, %select
, etc., in both the summary field and the reason field.