17
17
#include " swift/Frontend/DiagnosticVerifier.h"
18
18
#include " swift/Basic/SourceManager.h"
19
19
#include " swift/Parse/Lexer.h"
20
+ #include " llvm/ADT/STLExtras.h"
20
21
#include " llvm/Support/FileSystem.h"
21
22
#include " llvm/Support/FormatVariadic.h"
22
23
#include " llvm/Support/MemoryBuffer.h"
24
+ #include " llvm/Support/Path.h"
23
25
#include " llvm/Support/raw_ostream.h"
24
26
25
27
using namespace swift ;
@@ -36,6 +38,7 @@ struct ExpectedFixIt {
36
38
namespace {
37
39
38
40
static constexpr StringLiteral fixitExpectationNoneString (" none" );
41
+ static constexpr StringLiteral educationalNotesSpecifier (" educational-notes=" );
39
42
40
43
struct ExpectedDiagnosticInfo {
41
44
// This specifies the full range of the "expected-foo {{}}" specifier.
@@ -65,6 +68,17 @@ struct ExpectedDiagnosticInfo {
65
68
// Loc of {{none}}
66
69
const char *noneMarkerStartLoc = nullptr ;
67
70
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
+
68
82
ExpectedDiagnosticInfo (const char *ExpectedStart,
69
83
DiagnosticKind Classification)
70
84
: ExpectedStart(ExpectedStart), Classification(Classification) {}
@@ -85,6 +99,18 @@ static std::string getDiagKindString(DiagnosticKind Kind) {
85
99
llvm_unreachable (" Unhandled DiagKind in switch." );
86
100
}
87
101
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
+
88
114
// / If we find the specified diagnostic in the list, return it.
89
115
// / Otherwise return CapturedDiagnostics.end().
90
116
static std::vector<CapturedDiagnosticInfo>::iterator
@@ -439,38 +465,67 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
439
465
StringRef ExtraChecks = MatchStart.substr (End+2 ).ltrim (" \t " );
440
466
while (ExtraChecks.startswith (" {{" )) {
441
467
// 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) {
444
470
addError (ExtraChecks.data (),
445
- " didn't find '}}' to match '{{' in fix-it verification" );
471
+ " didn't find '}}' to match '{{' in diagnostic verification" );
446
472
break ;
447
473
}
448
-
474
+
449
475
// 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);
454
485
// 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) {
456
487
addError (ExtraChecks.data (), " didn't find '}}' to match '{{' in "
457
- " fix-it verification" );
488
+ " diagnostic verification" );
458
489
break ;
459
490
}
460
-
491
+
461
492
// 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.
464
519
// Special case for specifying no fixits should appear.
465
- if (FixItStr == fixitExpectationNoneString) {
520
+ if (CheckStr == fixitExpectationNoneString) {
466
521
if (Expected.noneMarkerStartLoc ) {
467
- addError (FixItStr .data () - 2 ,
522
+ addError (CheckStr .data () - 2 ,
468
523
Twine (" A second {{" ) + fixitExpectationNoneString +
469
524
" }} was found. It may only appear once in an expectation." );
470
525
break ;
471
526
}
472
527
473
- Expected.noneMarkerStartLoc = FixItStr .data () - 2 ;
528
+ Expected.noneMarkerStartLoc = CheckStr .data () - 2 ;
474
529
continue ;
475
530
}
476
531
@@ -482,14 +537,14 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
482
537
}
483
538
484
539
// Parse the pieces of the fix-it.
485
- size_t MinusLoc = FixItStr .find (' -' );
540
+ size_t MinusLoc = CheckStr .find (' -' );
486
541
if (MinusLoc == StringRef::npos) {
487
- addError (FixItStr .data (), " expected '-' in fix-it verification" );
542
+ addError (CheckStr .data (), " expected '-' in fix-it verification" );
488
543
continue ;
489
544
}
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
+
493
548
size_t EqualLoc = AfterMinus.find (' =' );
494
549
if (EqualLoc == StringRef::npos) {
495
550
addError (AfterMinus.data (),
@@ -500,8 +555,8 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
500
555
StringRef AfterEqual = AfterMinus.substr (EqualLoc+1 );
501
556
502
557
ExpectedFixIt FixIt;
503
- FixIt.StartLoc = StartColStr. data ()- 2 ;
504
- FixIt.EndLoc = FixItStr. data ()+EndLoc ;
558
+ FixIt.StartLoc = OpenLoc ;
559
+ FixIt.EndLoc = CloseLoc ;
505
560
if (StartColStr.getAsInteger (10 , FixIt.StartCol )) {
506
561
addError (StartColStr.data (),
507
562
" invalid column number in fix-it verification" );
@@ -514,7 +569,7 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
514
569
}
515
570
516
571
// Translate literal "\\n" into '\n', inefficiently.
517
- StringRef fixItText = AfterEqual.slice (0 , EndLoc );
572
+ StringRef fixItText = AfterEqual.slice (0 , EndIndex );
518
573
for (const char *current = fixItText.begin (), *end = fixItText.end ();
519
574
current != end; /* in loop */ ) {
520
575
if (*current == ' \\ ' && current + 1 < end) {
@@ -681,6 +736,33 @@ DiagnosticVerifier::Result DiagnosticVerifier::verifyFile(unsigned BufferID) {
681
736
replEndLoc, actualFixits);
682
737
}
683
738
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
+
684
766
// Actually remove the diagnostic from the list, so we don't match it
685
767
// again. We do have to do this after checking fix-its, though, because
686
768
// the diagnostic owns its fix-its.
@@ -866,6 +948,11 @@ void DiagnosticVerifier::handleDiagnostic(SourceManager &SM,
866
948
SmallVector<DiagnosticInfo::FixIt, 2 > fixIts;
867
949
std::copy (Info.FixIts .begin (), Info.FixIts .end (), std::back_inserter (fixIts));
868
950
951
+ llvm::SmallVector<std::string, 1 > eduNotes;
952
+ for (auto ¬ePath : Info.EducationalNotePaths ) {
953
+ eduNotes.push_back (llvm::sys::path::stem (notePath));
954
+ }
955
+
869
956
llvm::SmallString<128 > message;
870
957
{
871
958
llvm::raw_svector_ostream Out (message);
@@ -878,10 +965,10 @@ void DiagnosticVerifier::handleDiagnostic(SourceManager &SM,
878
965
const auto fileName = SM.getDisplayNameForLoc (Info.Loc );
879
966
CapturedDiagnostics.emplace_back (message, fileName, Info.Kind , Info.Loc ,
880
967
lineAndColumn.first , lineAndColumn.second ,
881
- fixIts);
968
+ fixIts, eduNotes );
882
969
} else {
883
970
CapturedDiagnostics.emplace_back (message, StringRef (), Info.Kind , Info.Loc ,
884
- 0 , 0 , fixIts);
971
+ 0 , 0 , fixIts, eduNotes );
885
972
}
886
973
}
887
974
0 commit comments