Index: llvm/include/llvm/Support/FileCheck.h =================================================================== --- llvm/include/llvm/Support/FileCheck.h +++ llvm/include/llvm/Support/FileCheck.h @@ -128,7 +128,8 @@ const StringMap &VariableTable, SMRange MatchRange = None) const; void PrintFuzzyMatch(const SourceMgr &SM, StringRef Buffer, - const StringMap &VariableTable) const; + const StringMap &VariableTable, + std::vector *Diags) const; bool hasVariable() const { return !(VariableUses.empty() && VariableDefs.empty()); @@ -159,15 +160,20 @@ unsigned CheckLine, CheckCol; /// What kind of match result does this diagnostic describe? /// + /// There might be more than one of these for the same directive. For + /// example, there might be a fuzzy match after a fail. + /// /// We iterate these types, so they must have contiguous values in /// [0, MatchTypeCount). enum MatchType { // TODO: More members will appear with later patches in this series. MatchNoneButExpected, //< no match for an expected pattern MatchTypeFirst = MatchNoneButExpected, + MatchFuzzy, //< a fuzzy match (because no perfect match) MatchTypeCount, } MatchTy; - /// The search range. + /// The match range if MatchTy is not MatchNoneButExpected, or the search + /// range otherwise. unsigned InputStartLine, InputStartCol, InputEndLine, InputEndCol; FileCheckDiag(const SourceMgr &SM, const Check::FileCheckType &CheckTy, SMLoc CheckLoc, MatchType MatchTy, SMRange InputRange); Index: llvm/lib/Support/FileCheck.cpp =================================================================== --- llvm/lib/Support/FileCheck.cpp +++ llvm/lib/Support/FileCheck.cpp @@ -429,7 +429,8 @@ void FileCheckPattern::PrintFuzzyMatch( const SourceMgr &SM, StringRef Buffer, - const StringMap &VariableTable) const { + const StringMap &VariableTable, + std::vector *Diags) const { // Attempt to find the closest/best fuzzy match. Usually an error happens // because some string in the output didn't exactly match. In these cases, we // would like to show the user a best guess at what "should have" matched, to @@ -463,8 +464,11 @@ // reasonable and not equal to what we showed in the "scanning from here" // line. if (Best && Best != StringRef::npos && BestQuality < 50) { - SM.PrintMessage(SMLoc::getFromPointer(Buffer.data() + Best), - SourceMgr::DK_Note, "possible intended match here"); + SMRange MatchRange = ProcessMatchResult( + FileCheckDiag::MatchFuzzy, SM, getLoc(), getCheckTy(), Buffer, Best, 0, + Diags); + SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note, + "possible intended match here"); // FIXME: If we wanted to be really friendly we would show why the match // failed, as it can be hard to spot simple one character differences. @@ -956,7 +960,7 @@ // Allow the pattern to print additional information if desired. Pat.PrintVariableUses(SM, Buffer, VariableTable); if (ExpectedMatch) - Pat.PrintFuzzyMatch(SM, Buffer, VariableTable); + Pat.PrintFuzzyMatch(SM, Buffer, VariableTable, Diags); } static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM, Index: llvm/test/FileCheck/dump-input-annotations.txt =================================================================== --- llvm/test/FileCheck/dump-input-annotations.txt +++ llvm/test/FileCheck/dump-input-annotations.txt @@ -29,7 +29,7 @@ ; ALIGN-NOT:{{.}} ;-------------------------------------------------- -; CHECK (also: multi-line search range) +; CHECK (also: multi-line search range, fuzzy match) ;-------------------------------------------------- ; Good match and no match. @@ -49,11 +49,12 @@ ; RUN: | FileCheck -match-full-lines %s -check-prefixes=CHK,CHK-V ; CHK: <<<<<< -; CHK-NEXT: 1: hello -; CHK-NEXT: 2: again -; CHK-NEXT: check:2 X~~~~ -; CHK-NEXT: 3: whirled -; CHK-NEXT: check:2 ~~~~~~~ +; CHK-NEXT: 1: hello +; CHK-NEXT: 2: again +; CHK-NEXT: check:2'0 X~~~~ +; CHK-NEXT: 3: whirled +; CHK-NEXT: check:2'0 ~~~~~~~ +; CHK-NEXT: check:2'1 ? ; CHK-NEXT: >>>>>> ; CHK-NOT: {{.}} @@ -228,13 +229,14 @@ ; RUN: | FileCheck -match-full-lines %s -check-prefixes=LAB,LAB-V,LAB-VV ; LAB: <<<<<< -; LAB-NEXT: 1: lab0 -; LAB-NEXT: 2: foo -; LAB-NEXT: label:3 X~~ -; LAB-NEXT: 3: lab1 -; LAB-NEXT: label:3 ~~~~ -; LAB-NEXT: 4: bar -; LAB-NEXT: label:3 ~~~ +; LAB-NEXT: 1: lab0 +; LAB-NEXT: 2: foo +; LAB-NEXT: label:3'0 X~~ +; LAB-NEXT: 3: lab1 +; LAB-NEXT: label:3'0 ~~~~ +; LAB-NEXT: label:3'1 ? +; LAB-NEXT: 4: bar +; LAB-NEXT: label:3'0 ~~~ ; LAB-NEXT: >>>>>> ; LAB-NOT: {{.}} Index: llvm/utils/FileCheck/FileCheck.cpp =================================================================== --- llvm/utils/FileCheck/FileCheck.cpp +++ llvm/utils/FileCheck/FileCheck.cpp @@ -130,6 +130,10 @@ return MatchTypeStyle('X', raw_ostream::RED, "the search range for an unmatched expected " "pattern (e.g., CHECK)"); + case FileCheckDiag::MatchFuzzy: + return MatchTypeStyle('?', raw_ostream::MAGENTA, + "a fuzzy match start for an otherwise unmatched " + "pattern"); case FileCheckDiag::MatchTypeCount: llvm_unreachable_internal("unexpected match type"); } @@ -149,19 +153,29 @@ // Labels for annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L"; - OS << " labels the match result for a pattern of type T from " + OS << " labels the only match result for a pattern of type T from " << "line L of\n" << " the check file\n"; + OS << " - "; + WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "T:L'N"; + OS << " labels the Nth match result for a pattern of type T from line " + << "L of\n" + << " the check file\n"; // Markers on annotation lines. OS << " - "; WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~"; - OS << " marks search range when no match is found\n"; + OS << " marks search range when no match is found\n" + << " - "; + WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "?"; + OS << " marks fuzzy match when no match is found\n"; // Colors. if (WithColor(OS).colorsEnabled()) { OS << " - color "; WithColor(OS, raw_ostream::RED, true) << "error"; + OS << ", "; + WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy"; OS << '\n'; } @@ -179,6 +193,8 @@ /// The check file line (one-origin indexing) where the directive that /// produced this annotation is located. unsigned CheckLine; + /// The index of the match result for this check. + unsigned CheckDiagIndex; /// The label for this annotation. std::string Label; /// What input line (one-origin indexing) this annotation marks. This might @@ -230,6 +246,8 @@ static void BuildInputAnnotations(const std::vector &Diags, std::vector &Annotations, unsigned &LabelWidth) { + // How many diagnostics has the current check seen so far? + unsigned CheckDiagCount = 0; // What's the widest label? LabelWidth = 0; for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; @@ -241,6 +259,19 @@ llvm::raw_string_ostream Label(A.Label); Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":" << DiagItr->CheckLine; + A.CheckDiagIndex = UINT_MAX; + auto DiagNext = std::next(DiagItr); + if (DiagNext != DiagEnd && DiagItr->CheckTy == DiagNext->CheckTy && + DiagItr->CheckLine == DiagNext->CheckLine) + A.CheckDiagIndex = CheckDiagCount++; + else if (CheckDiagCount) { + A.CheckDiagIndex = CheckDiagCount; + CheckDiagCount = 0; + } + if (A.CheckDiagIndex != UINT_MAX) + Label << "'" << A.CheckDiagIndex; + else + A.CheckDiagIndex = 0; Label.flush(); LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); @@ -272,6 +303,7 @@ break; InputAnnotation B; B.CheckLine = A.CheckLine; + B.CheckDiagIndex = A.CheckDiagIndex; B.Label = A.Label; B.InputLine = L; B.Mark = '~'; @@ -301,16 +333,21 @@ // // Second, for annotations for the same input line, sort in the order of the // FileCheck directive's line in the check file (where there's at most one - // directive per line). The rationale of this choice is that, for any input - // line, this sort establishes a total order of annotations that, with - // respect to match results, is consistent across multiple lines, thus - // making match results easier to track from one line to the next when they - // span multiple lines. + // directive per line) and then by the index of the match result for that + // directive. The rationale of this choice is that, for any input line, this + // sort establishes a total order of annotations that, with respect to match + // results, is consistent across multiple lines, thus making match results + // easier to track from one line to the next when they span multiple lines. std::sort(Annotations.begin(), Annotations.end(), [](const InputAnnotation &A, const InputAnnotation &B) { if (A.InputLine != B.InputLine) return A.InputLine < B.InputLine; - return A.CheckLine < B.CheckLine; + if (A.CheckLine != B.CheckLine) + return A.CheckLine < B.CheckLine; + assert(A.CheckDiagIndex != B.CheckDiagIndex && + "expected diagnostic indices to be unique within a " + " check line"); + return A.CheckDiagIndex < B.CheckDiagIndex; }); // Compute the width of the label column.