Skip to content

Commit cfbedd8

Browse files
committed
[DiagnosticVerifier] Add support for asserting presence of edu notes
1 parent 0366e61 commit cfbedd8

File tree

3 files changed

+129
-28
lines changed

3 files changed

+129
-28
lines changed

include/swift/Frontend/DiagnosticVerifier.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,19 @@ struct CapturedDiagnosticInfo {
4646
unsigned Line;
4747
unsigned Column;
4848
SmallVector<DiagnosticInfo::FixIt, 2> FixIts;
49+
SmallVector<std::string, 1> EducationalNotes;
4950

5051
CapturedDiagnosticInfo(llvm::SmallString<128> Message,
5152
llvm::SmallString<32> FileName,
5253
DiagnosticKind Classification, SourceLoc Loc,
5354
unsigned Line, unsigned Column,
54-
SmallVector<DiagnosticInfo::FixIt, 2> FixIts)
55+
SmallVector<DiagnosticInfo::FixIt, 2> FixIts,
56+
SmallVector<std::string, 1> EducationalNotes)
5557
: Message(Message), FileName(FileName), Classification(Classification),
56-
Loc(Loc), Line(Line), Column(Column), FixIts(FixIts) {}
58+
Loc(Loc), Line(Line), Column(Column), FixIts(FixIts),
59+
EducationalNotes(EducationalNotes) {
60+
std::sort(EducationalNotes.begin(), EducationalNotes.end());
61+
}
5762
};
5863
/// This class implements support for -verify mode in the compiler. It
5964
/// buffers up diagnostics produced during compilation, then checks them

lib/Frontend/DiagnosticVerifier.cpp

Lines changed: 113 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
#include "swift/Frontend/DiagnosticVerifier.h"
1818
#include "swift/Basic/SourceManager.h"
1919
#include "swift/Parse/Lexer.h"
20+
#include "llvm/ADT/STLExtras.h"
2021
#include "llvm/Support/FileSystem.h"
2122
#include "llvm/Support/FormatVariadic.h"
2223
#include "llvm/Support/MemoryBuffer.h"
24+
#include "llvm/Support/Path.h"
2325
#include "llvm/Support/raw_ostream.h"
2426

2527
using namespace swift;
@@ -36,6 +38,7 @@ struct ExpectedFixIt {
3638
namespace {
3739

3840
static constexpr StringLiteral fixitExpectationNoneString("none");
41+
static constexpr StringLiteral educationalNotesSpecifier("educational-notes=");
3942

4043
struct ExpectedDiagnosticInfo {
4144
// This specifies the full range of the "expected-foo {{}}" specifier.
@@ -65,6 +68,17 @@ struct ExpectedDiagnosticInfo {
6568
// Loc of {{none}}
6669
const char *noneMarkerStartLoc = nullptr;
6770

71+
/// Represents a specifier of the form '{{educational-notes=note1,note2}}'.
72+
struct ExpectedEducationalNotes {
73+
const char *StartLoc, *EndLoc; // The loc of the {{ and }}'s.
74+
llvm::SmallVector<StringRef, 1> Names; // Names of expected notes.
75+
76+
ExpectedEducationalNotes(const char *StartLoc, const char *EndLoc,
77+
llvm::SmallVector<StringRef, 1> Names)
78+
: StartLoc(StartLoc), EndLoc(EndLoc), Names(Names) {}
79+
};
80+
Optional<ExpectedEducationalNotes> EducationalNotes;
81+
6882
ExpectedDiagnosticInfo(const char *ExpectedStart,
6983
DiagnosticKind Classification)
7084
: ExpectedStart(ExpectedStart), Classification(Classification) {}
@@ -85,6 +99,18 @@ static std::string getDiagKindString(DiagnosticKind Kind) {
8599
llvm_unreachable("Unhandled DiagKind in switch.");
86100
}
87101

102+
/// Render the verifier syntax for a given set of educational notes.
103+
static std::string
104+
renderEducationalNotes(llvm::SmallVectorImpl<std::string> &EducationalNotes) {
105+
std::string Result;
106+
llvm::raw_string_ostream OS(Result);
107+
OS << "{{" << educationalNotesSpecifier;
108+
interleave(EducationalNotes, [&](const auto &Note) { OS << Note; },
109+
[&] { OS << ','; });
110+
OS << "}}";
111+
return OS.str();
112+
}
113+
88114
/// If we find the specified diagnostic in the list, return it.
89115
/// Otherwise return CapturedDiagnostics.end().
90116
static std::vector<CapturedDiagnosticInfo>::iterator
@@ -439,38 +465,67 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
439465
StringRef ExtraChecks = MatchStart.substr(End+2).ltrim(" \t");
440466
while (ExtraChecks.startswith("{{")) {
441467
// First make sure we have a closing "}}".
442-
size_t EndLoc = ExtraChecks.find("}}");
443-
if (EndLoc == StringRef::npos) {
468+
size_t EndIndex = ExtraChecks.find("}}");
469+
if (EndIndex == StringRef::npos) {
444470
addError(ExtraChecks.data(),
445-
"didn't find '}}' to match '{{' in fix-it verification");
471+
"didn't find '}}' to match '{{' in diagnostic verification");
446472
break;
447473
}
448-
474+
449475
// Allow for close braces to appear in the replacement text.
450-
while (EndLoc+2 < ExtraChecks.size() && ExtraChecks[EndLoc+2] == '}')
451-
++EndLoc;
452-
453-
StringRef FixItStr = ExtraChecks.slice(2, EndLoc);
476+
while (EndIndex + 2 < ExtraChecks.size() &&
477+
ExtraChecks[EndIndex + 2] == '}')
478+
++EndIndex;
479+
480+
const char *OpenLoc = ExtraChecks.data(); // Beginning of opening '{{'.
481+
const char *CloseLoc =
482+
ExtraChecks.data() + EndIndex + 2; // End of closing '}}'.
483+
484+
StringRef CheckStr = ExtraChecks.slice(2, EndIndex);
454485
// Check for matching a later "}}" on a different line.
455-
if (FixItStr.find_first_of("\r\n") != StringRef::npos) {
486+
if (CheckStr.find_first_of("\r\n") != StringRef::npos) {
456487
addError(ExtraChecks.data(), "didn't find '}}' to match '{{' in "
457-
"fix-it verification");
488+
"diagnostic verification");
458489
break;
459490
}
460-
491+
461492
// Prepare for the next round of checks.
462-
ExtraChecks = ExtraChecks.substr(EndLoc+2).ltrim();
463-
493+
ExtraChecks = ExtraChecks.substr(EndIndex + 2).ltrim();
494+
495+
// If this check starts with 'educational-notes=', check for one or more
496+
// educational notes instead of a fix-it.
497+
if (CheckStr.startswith(educationalNotesSpecifier)) {
498+
if (Expected.EducationalNotes.hasValue()) {
499+
addError(CheckStr.data(),
500+
"each verified diagnostic may only have one "
501+
"{{educational-notes=<#notes#>}} declaration");
502+
continue;
503+
}
504+
StringRef NotesStr = CheckStr.substr(
505+
educationalNotesSpecifier.size()); // Trim 'educational-notes='.
506+
llvm::SmallVector<StringRef, 1> names;
507+
// Note names are comma-separated.
508+
std::pair<StringRef, StringRef> split;
509+
do {
510+
split = NotesStr.split(',');
511+
names.push_back(split.first);
512+
NotesStr = split.second;
513+
} while (!NotesStr.empty());
514+
Expected.EducationalNotes.emplace(OpenLoc, CloseLoc, names);
515+
continue;
516+
}
517+
518+
// This wasn't an educational notes specifier, so it must be a fix-it.
464519
// Special case for specifying no fixits should appear.
465-
if (FixItStr == fixitExpectationNoneString) {
520+
if (CheckStr == fixitExpectationNoneString) {
466521
if (Expected.noneMarkerStartLoc) {
467-
addError(FixItStr.data() - 2,
522+
addError(CheckStr.data() - 2,
468523
Twine("A second {{") + fixitExpectationNoneString +
469524
"}} was found. It may only appear once in an expectation.");
470525
break;
471526
}
472527

473-
Expected.noneMarkerStartLoc = FixItStr.data() - 2;
528+
Expected.noneMarkerStartLoc = CheckStr.data() - 2;
474529
continue;
475530
}
476531

@@ -482,14 +537,14 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
482537
}
483538

484539
// Parse the pieces of the fix-it.
485-
size_t MinusLoc = FixItStr.find('-');
540+
size_t MinusLoc = CheckStr.find('-');
486541
if (MinusLoc == StringRef::npos) {
487-
addError(FixItStr.data(), "expected '-' in fix-it verification");
542+
addError(CheckStr.data(), "expected '-' in fix-it verification");
488543
continue;
489544
}
490-
StringRef StartColStr = FixItStr.slice(0, MinusLoc);
491-
StringRef AfterMinus = FixItStr.substr(MinusLoc+1);
492-
545+
StringRef StartColStr = CheckStr.slice(0, MinusLoc);
546+
StringRef AfterMinus = CheckStr.substr(MinusLoc + 1);
547+
493548
size_t EqualLoc = AfterMinus.find('=');
494549
if (EqualLoc == StringRef::npos) {
495550
addError(AfterMinus.data(),
@@ -500,8 +555,8 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
500555
StringRef AfterEqual = AfterMinus.substr(EqualLoc+1);
501556

502557
ExpectedFixIt FixIt;
503-
FixIt.StartLoc = StartColStr.data()-2;
504-
FixIt.EndLoc = FixItStr.data()+EndLoc;
558+
FixIt.StartLoc = OpenLoc;
559+
FixIt.EndLoc = CloseLoc;
505560
if (StartColStr.getAsInteger(10, FixIt.StartCol)) {
506561
addError(StartColStr.data(),
507562
"invalid column number in fix-it verification");
@@ -514,7 +569,7 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
514569
}
515570

516571
// Translate literal "\\n" into '\n', inefficiently.
517-
StringRef fixItText = AfterEqual.slice(0, EndLoc);
572+
StringRef fixItText = AfterEqual.slice(0, EndIndex);
518573
for (const char *current = fixItText.begin(), *end = fixItText.end();
519574
current != end; /* in loop */) {
520575
if (*current == '\\' && current + 1 < end) {
@@ -681,6 +736,33 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
681736
replEndLoc, actualFixits);
682737
}
683738

739+
if (auto expectedNotes = expected.EducationalNotes) {
740+
// Verify educational notes
741+
for (auto &foundName : FoundDiagnostic.EducationalNotes) {
742+
llvm::erase_if(expectedNotes->Names,
743+
[&](std::string item) { return item == foundName; });
744+
}
745+
746+
if (!expectedNotes->Names.empty()) {
747+
if (FoundDiagnostic.EducationalNotes.empty()) {
748+
addError(expectedNotes->StartLoc,
749+
"expected educational note(s) not seen");
750+
} else {
751+
// If we had an incorrect expected note, render it and produce a fixit
752+
// of our own.
753+
auto actual =
754+
renderEducationalNotes(FoundDiagnostic.EducationalNotes);
755+
auto replStartLoc = SMLoc::getFromPointer(expectedNotes->StartLoc);
756+
auto replEndLoc = SMLoc::getFromPointer(expectedNotes->EndLoc);
757+
758+
llvm::SMFixIt fix(llvm::SMRange(replStartLoc, replEndLoc), actual);
759+
addError(expectedNotes->StartLoc,
760+
"expected educational note(s) not seen; actual educational "
761+
"note(s): " + actual, fix);
762+
}
763+
}
764+
}
765+
684766
// Actually remove the diagnostic from the list, so we don't match it
685767
// again. We do have to do this after checking fix-its, though, because
686768
// the diagnostic owns its fix-its.
@@ -866,6 +948,11 @@ void DiagnosticVerifier::handleDiagnostic(SourceManager &SM,
866948
SmallVector<DiagnosticInfo::FixIt, 2> fixIts;
867949
std::copy(Info.FixIts.begin(), Info.FixIts.end(), std::back_inserter(fixIts));
868950

951+
llvm::SmallVector<std::string, 1> eduNotes;
952+
for (auto &notePath : Info.EducationalNotePaths) {
953+
eduNotes.push_back(llvm::sys::path::stem(notePath));
954+
}
955+
869956
llvm::SmallString<128> message;
870957
{
871958
llvm::raw_svector_ostream Out(message);
@@ -878,10 +965,10 @@ void DiagnosticVerifier::handleDiagnostic(SourceManager &SM,
878965
const auto fileName = SM.getDisplayNameForLoc(Info.Loc);
879966
CapturedDiagnostics.emplace_back(message, fileName, Info.Kind, Info.Loc,
880967
lineAndColumn.first, lineAndColumn.second,
881-
fixIts);
968+
fixIts, eduNotes);
882969
} else {
883970
CapturedDiagnostics.emplace_back(message, StringRef(), Info.Kind, Info.Loc,
884-
0, 0, fixIts);
971+
0, 0, fixIts, eduNotes);
885972
}
886973
}
887974

test/diagnostics/verifier.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ func b() {
3333
// CHECK: unexpected warning produced: initialization of immutable value 'c' was never used
3434
// CHECK-WARNINGS-AS-ERRORS: unexpected error produced: initialization of immutable value 'c' was never used
3535

36+
extension (Int, Int) {} // expected-error {{non-nominal type '(Int, Int)' cannot be extended}} {{educational-notes=foo-bar-baz}}
37+
// CHECK: error: expected educational note(s) not seen; actual educational note(s): {{[{][{]}}educational-notes=nominal-types{{[}][}]}}
38+
39+
extension (Bool, Int) {} // expected-error {{non-nominal type '(Bool, Int)' cannot be extended}} {{educational-notes=nominal-types}} {{educational-notes=nominal-types}}
40+
// CHECK: error: each verified diagnostic may only have one {{[{][{]}}educational-notes=<#notes#>{{[}][}]}} declaration
41+
42+
extension (Bool, Bool) {} // expected-error {{non-nominal type '(Bool, Bool)' cannot be extended}} {{educational-notes=nominal-types,foo-bar-baz}}
43+
// CHECK: error: expected educational note(s) not seen; actual educational note(s): {{[{][{]}}educational-notes=nominal-types{{[}][}]}}
44+
3645
// Verify the serialized diags have the right magic at the top.
3746
// CHECK-SERIALIZED: DIA
3847

0 commit comments

Comments
 (0)