Skip to content

Commit a500882

Browse files
committed
Diagnostics: Put notes under associated diag for clients and json output
stack-info: PR: #1925, branch: AndrewNolte/stack/25
1 parent 70e1d10 commit a500882

7 files changed

Lines changed: 337 additions & 52 deletions

File tree

include/slang/diagnostics/DiagnosticEngine.h

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,30 @@ class DiagnosticClient;
2929
class SourceManager;
3030
class WaiverManager;
3131

32-
struct SLANG_EXPORT ReportedDiagnostic {
32+
/// Data shared by primary diagnostics and their associated notes after preparation for reporting.
33+
struct SLANG_EXPORT ReportedDiagnosticInfo {
3334
const Diagnostic& originalDiagnostic;
34-
std::span<const SourceLocation> expansionLocs;
35+
SmallVector<SourceLocation> expansionLocs;
3536
std::span<const SourceRange> ranges;
3637
SourceLocation location;
37-
std::string_view formattedMessage;
38-
DiagnosticSeverity severity = DiagnosticSeverity::Ignored;
38+
std::string formattedMessage;
3939
bool shouldShowIncludeStack = false;
4040

41-
ReportedDiagnostic(const Diagnostic& original) : originalDiagnostic(original) {}
41+
ReportedDiagnosticInfo(const Diagnostic& original) : originalDiagnostic(original) {}
42+
};
43+
44+
/// A note associated with a ReportedDiagnostic.
45+
struct SLANG_EXPORT ReportedNote : ReportedDiagnosticInfo {
46+
ReportedNote(ReportedDiagnosticInfo&& info) : ReportedDiagnosticInfo(std::move(info)) {}
47+
};
48+
49+
/// A primary diagnostic prepared for reporting to a DiagnosticClient.
50+
struct SLANG_EXPORT ReportedDiagnostic : ReportedDiagnosticInfo {
51+
std::vector<ReportedNote> notes;
52+
DiagnosticSeverity severity;
53+
54+
ReportedDiagnostic(ReportedDiagnosticInfo&& info, DiagnosticSeverity severity) :
55+
ReportedDiagnosticInfo(std::move(info)), severity(severity) {}
4256
};
4357

4458
/// The DiagnosticEngine is the central point for controlling how diagnostics are
@@ -300,7 +314,9 @@ class SLANG_EXPORT DiagnosticEngine {
300314
std::optional<DiagnosticSeverity> findPerBufferSeverity(DiagCode code, SourceLocation location,
301315
bool& overrideWarnAsError) const;
302316

303-
bool issueImpl(const Diagnostic& diagnostic, DiagnosticSeverity severity);
317+
// Resolves and formats a diagnostic for reporting. Returns nullopt if the diagnostic is
318+
// suppressed by a path filter or waiver. Note diagnostics are never suppressed here.
319+
std::optional<ReportedDiagnosticInfo> getReportedDiag(const Diagnostic& diagnostic);
304320

305321
template<typename TDirective>
306322
void setMappingsFromPragmasImpl(BufferID buffer, std::span<const TDirective> directives,

include/slang/diagnostics/JsonDiagnosticClient.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ namespace slang {
1515

1616
class JsonWriter;
1717

18-
/// A diagnostic client that serializes each diagnostic to JSON format.
18+
/// A diagnostic client that serializes diagnostics to JSON format, with associated
19+
/// notes nested under their parent diagnostic.
1920
class SLANG_EXPORT JsonDiagnosticClient : public DiagnosticClient {
2021
public:
2122
/// Constructs a new JsonDiagnosticClient that outputs to the given JsonWriter.
@@ -27,9 +28,7 @@ class SLANG_EXPORT JsonDiagnosticClient : public DiagnosticClient {
2728
private:
2829
JsonWriter& writer;
2930

30-
void formatDiag(SourceLocation loc, std::span<const SourceRange> ranges,
31-
DiagnosticSeverity severity, std::string_view message,
32-
std::string_view optionName);
31+
void writeDiagnostic(const ReportedDiagnosticInfo& diagnostic);
3332
};
3433

3534
} // namespace slang

include/slang/diagnostics/TextDiagnosticClient.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class SLANG_EXPORT TextDiagnosticClient : public DiagnosticClient {
9797
bool includeExpansion = true;
9898
ShowHierarchyPathOption includeHierarchy = ShowHierarchyPathOption::Auto;
9999

100+
void writeDiagnostic(const ReportedDiagnosticInfo& diagnostic, DiagnosticSeverity severity);
100101
void formatDiag(SourceLocation loc, std::span<const SourceRange> ranges,
101102
DiagnosticSeverity severity, std::string_view message,
102103
std::string_view optionName);

source/diagnostics/DiagnosticEngine.cpp

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -213,13 +213,43 @@ void DiagnosticEngine::issue(const Diagnostic& diagnostic) {
213213
severity == DiagnosticSeverity::Fatal;
214214
if (isError && errorLimit && numErrors >= errorLimit) {
215215
Diagnostic diag(diag::TooManyErrors, SourceLocation::NoLocation);
216-
issueImpl(diag, DiagnosticSeverity::Fatal);
216+
auto info = getReportedDiag(diag);
217+
SLANG_ASSERT(info);
218+
ReportedDiagnostic report(std::move(*info), DiagnosticSeverity::Fatal);
219+
for (auto& client : clients)
220+
client->report(report);
217221
issuedOverLimitErr = true;
218222
return;
219223
}
220224

221-
if (!issueImpl(diagnostic, severity))
225+
auto info = getReportedDiag(diagnostic);
226+
if (!info)
222227
return;
228+
ReportedDiagnostic report(std::move(*info), severity);
229+
230+
for (const Diagnostic& note : diagnostic.notes) {
231+
// Notes shouldn't have subnotes. We should change this shape too eventually, unless we
232+
// want to support that.
233+
SLANG_ASSERT(note.notes.empty());
234+
235+
// Notes are ignored if location is "NoLocation" since they frequently make no
236+
// sense without location information.
237+
if (note.location == SourceLocation::NoLocation && !note.code.showNoteWithNoLocation())
238+
continue;
239+
240+
auto noteSeverity = getSeverity(note.code, note.location);
241+
if (noteSeverity == DiagnosticSeverity::Ignored)
242+
continue;
243+
244+
SLANG_ASSERT(noteSeverity == DiagnosticSeverity::Note);
245+
246+
auto noteInfo = getReportedDiag(note);
247+
SLANG_ASSERT(noteInfo);
248+
report.notes.emplace_back(std::move(*noteInfo));
249+
}
250+
251+
for (auto& client : clients)
252+
client->report(report);
223253

224254
if (severity == DiagnosticSeverity::Warning)
225255
numWarnings++;
@@ -232,7 +262,8 @@ void DiagnosticEngine::issue(const Diagnostics& diagnostics) {
232262
issue(diag);
233263
}
234264

235-
bool DiagnosticEngine::issueImpl(const Diagnostic& diagnostic, DiagnosticSeverity severity) {
265+
std::optional<ReportedDiagnosticInfo> DiagnosticEngine::getReportedDiag(
266+
const Diagnostic& diagnostic) {
236267
// Walk out until we find a location for this diagnostic that isn't inside a macro.
237268
SmallVector<SourceLocation, 8> expansionLocs;
238269
SourceLocation loc = diagnostic.location;
@@ -256,12 +287,22 @@ bool DiagnosticEngine::issueImpl(const Diagnostic& diagnostic, DiagnosticSeverit
256287
}
257288

258289
showIncludeStack = reportedIncludeStack.emplace(loc.buffer()).second;
290+
}
291+
292+
ReportedDiagnosticInfo report(diagnostic);
293+
report.expansionLocs.assign(expansionLocs.begin() + ptrdiff_t(ignoreExpansionsUntil),
294+
expansionLocs.end());
295+
report.ranges = diagnostic.ranges;
296+
report.location = loc;
297+
report.shouldShowIncludeStack = showIncludeStack;
259298

260-
auto checkSuppressed = [&](const std::vector<fs::path>& patterns, SourceLocation loc) {
299+
if (loc != SourceLocation::NoLocation &&
300+
getDefaultSeverity(diagnostic.code) != DiagnosticSeverity::Note) {
301+
auto checkPath = [&](const std::vector<fs::path>& patterns, SourceLocation location) {
261302
if (patterns.empty())
262303
return false;
263304

264-
auto& path = sourceManager.getFullPath(loc.buffer());
305+
auto& path = sourceManager.getFullPath(location.buffer());
265306
for (auto& pattern : patterns) {
266307
if (svGlobMatches(path, pattern))
267308
return true;
@@ -270,43 +311,22 @@ bool DiagnosticEngine::issueImpl(const Diagnostic& diagnostic, DiagnosticSeverit
270311
};
271312

272313
if (getDefaultSeverity(diagnostic.code) == DiagnosticSeverity::Warning) {
273-
if (checkSuppressed(ignoreWarnPatterns, loc))
274-
return false;
314+
if (checkPath(ignoreWarnPatterns, loc))
315+
return std::nullopt;
275316

276-
if (ignoreExpansionsUntil < expansionLocs.size() && !ignoreMacroWarnPatterns.empty()) {
277-
auto originalLoc = sourceManager.getFullyOriginalLoc(
278-
expansionLocs[ignoreExpansionsUntil]);
279-
280-
if (checkSuppressed(ignoreMacroWarnPatterns, originalLoc))
281-
return false;
317+
if (!report.expansionLocs.empty() && !ignoreMacroWarnPatterns.empty()) {
318+
auto originalLoc = sourceManager.getFullyOriginalLoc(report.expansionLocs.front());
319+
if (checkPath(ignoreMacroWarnPatterns, originalLoc))
320+
return std::nullopt;
282321
}
283322
}
284323

285324
if (waiverManager && waiverManager->shouldWaive(diagnostic, loc, sourceManager, *this))
286-
return false;
287-
}
288-
289-
std::string message = formatMessage(diagnostic);
290-
291-
ReportedDiagnostic report(diagnostic);
292-
report.expansionLocs = std::span<SourceLocation>(expansionLocs).subspan(ignoreExpansionsUntil);
293-
report.ranges = diagnostic.ranges;
294-
report.location = loc;
295-
report.severity = severity;
296-
report.formattedMessage = message;
297-
report.shouldShowIncludeStack = showIncludeStack;
298-
299-
for (auto& client : clients)
300-
client->report(report);
301-
302-
// Notes are ignored if location is "NoLocation" since they frequently make no
303-
// sense without location information.
304-
for (const Diagnostic& note : diagnostic.notes) {
305-
if (note.location != SourceLocation::NoLocation || note.code.showNoteWithNoLocation())
306-
issue(note);
325+
return std::nullopt;
307326
}
308327

309-
return true;
328+
report.formattedMessage = formatMessage(diagnostic);
329+
return report;
310330
}
311331

312332
const DiagnosticEngine::FormatterMap& DiagnosticEngine::getFormatters() const {

source/diagnostics/JsonDiagnosticClient.cpp

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,28 @@ void JsonDiagnosticClient::report(const ReportedDiagnostic& diag) {
1818
writer.startObject();
1919
writer.writeProperty("severity");
2020
writer.writeValue(getSeverityString(diag.severity));
21+
22+
writeDiagnostic(diag);
23+
24+
if (!diag.notes.empty()) {
25+
writer.writeProperty("notes");
26+
writer.startArray();
27+
for (auto& note : diag.notes) {
28+
writer.startObject();
29+
writeDiagnostic(note);
30+
writer.endObject();
31+
}
32+
writer.endArray();
33+
}
34+
35+
writer.endObject();
36+
}
37+
38+
void JsonDiagnosticClient::writeDiagnostic(const ReportedDiagnosticInfo& diag) {
2139
writer.writeProperty("message");
2240
writer.writeValue(diag.formattedMessage);
41+
writer.writeProperty("code");
42+
writer.writeValue(toString(diag.originalDiagnostic.code));
2343

2444
auto optionName = engine->getOptionName(diag.originalDiagnostic.code);
2545
if (!optionName.empty()) {
@@ -34,6 +54,33 @@ void JsonDiagnosticClient::report(const ReportedDiagnostic& diag) {
3454
sourceManager->getLineNumber(loc), getColumnNumber(loc)));
3555
}
3656

57+
SmallVector<SourceRange> mappedRanges;
58+
engine->mapSourceRanges(diag.location, diag.ranges, mappedRanges);
59+
if (!mappedRanges.empty()) {
60+
auto writeLocation = [&](SourceLocation loc) {
61+
writer.startObject();
62+
writer.writeProperty("file");
63+
writer.writeValue(getFileName(loc));
64+
writer.writeProperty("line");
65+
writer.writeValue(sourceManager->getLineNumber(loc));
66+
writer.writeProperty("column");
67+
writer.writeValue(getColumnNumber(loc));
68+
writer.endObject();
69+
};
70+
71+
writer.writeProperty("ranges");
72+
writer.startArray();
73+
for (auto range : mappedRanges) {
74+
writer.startObject();
75+
writer.writeProperty("start");
76+
writeLocation(range.start());
77+
writer.writeProperty("end");
78+
writeLocation(range.end());
79+
writer.endObject();
80+
}
81+
writer.endArray();
82+
}
83+
3784
if (diag.shouldShowIncludeStack) {
3885
SmallVector<SourceLocation> includeStack;
3986
getIncludeStack(diag.location.buffer(), includeStack);
@@ -81,7 +128,6 @@ void JsonDiagnosticClient::report(const ReportedDiagnostic& diag) {
81128
}
82129
writer.endArray();
83130
}
84-
writer.endObject();
85131
}
86132

87133
} // namespace slang

source/diagnostics/TextDiagnosticClient.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ TerminalColor TextDiagnosticClient::getSeverityColor(DiagnosticSeverity severity
4747
}
4848

4949
void TextDiagnosticClient::report(const ReportedDiagnostic& diag) {
50+
writeDiagnostic(diag, diag.severity);
51+
for (auto& note : diag.notes)
52+
writeDiagnostic(note, DiagnosticSeverity::Note);
53+
}
54+
55+
void TextDiagnosticClient::writeDiagnostic(const ReportedDiagnosticInfo& diag,
56+
DiagnosticSeverity severity) {
5057
if (diag.shouldShowIncludeStack && includeFileStack) {
5158
SmallVector<SourceLocation> includeStack;
5259
getIncludeStack(diag.location.buffer(), includeStack);
@@ -79,7 +86,7 @@ void TextDiagnosticClient::report(const ReportedDiagnostic& diag) {
7986
engine->mapSourceRanges(diag.location, diag.ranges, mappedRanges);
8087

8188
// Write the diagnostic.
82-
formatDiag(diag.location, mappedRanges, diag.severity, diag.formattedMessage,
89+
formatDiag(diag.location, mappedRanges, severity, diag.formattedMessage,
8390
engine->getOptionName(diag.originalDiagnostic.code));
8491

8592
// Write out macro expansions, if we have any, in reverse order.

0 commit comments

Comments
 (0)