Index: llvm/include/llvm/Support/FileCheck.h =================================================================== --- llvm/include/llvm/Support/FileCheck.h +++ llvm/include/llvm/Support/FileCheck.h @@ -127,7 +127,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()); @@ -157,13 +158,19 @@ /// Where is the FileCheck directive for this diagnostic? 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. enum MatchType { // TODO: More members will appear with later patches in this series. /// Indicates no match for an expected pattern. MatchNoneButExpected, + /// Indicates a possible intended match because there's no perfect match. + MatchFuzzy, 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 @@ -1,5 +1,5 @@ ;-------------------------------------------------- -; Use -strict-whitespace to check marker alignment here. +; Use -strict-whitespace to check marker and note alignment here. ; (Also check multiline marker where start/end columns vary across lines.) ; ; In the remaining checks, don't use -strict-whitespace and thus check just the @@ -10,6 +10,7 @@ ; RUN: echo 'hello world' > %t.in ; RUN: echo 'goodbye' >> %t.in ; RUN: echo 'world' >> %t.in +; RUN: echo 'unicorn' >> %t.in ; RUN: echo 'CHECK: hello' > %t.chk ; RUN: echo 'CHECK: universe' >> %t.chk @@ -19,17 +20,20 @@ ; ALIGN:Full input was: ; ALIGN-NEXT:<<<<<< -; ALIGN-NEXT: 1: hello world -; ALIGN-NEXT:check:2 X~~~~ -; ALIGN-NEXT: 2: goodbye -; ALIGN-NEXT:check:2 ~~~~~~~ -; ALIGN-NEXT: 3: world -; ALIGN-NEXT:check:2 ~~~~~ error: no match found +; ALIGN-NEXT: 1: hello world +; ALIGN-NEXT:check:2'0 X~~~~ +; ALIGN-NEXT: 2: goodbye +; ALIGN-NEXT:check:2'0 ~~~~~~~ +; ALIGN-NEXT: 3: world +; ALIGN-NEXT:check:2'0 ~~~~~ +; ALIGN-NEXT: 4: unicorn +; ALIGN-NEXT:check:2'0 ~~~~~~~ error: no match found +; ALIGN-NEXT:check:2'1 ? possible intended match ; ALIGN-NEXT:>>>>>> ; ALIGN-NOT:{{.}} ;-------------------------------------------------- -; CHECK (also: multi-line search range) +; CHECK (also: multi-line search range, fuzzy match) ;-------------------------------------------------- ; Good match and no match. @@ -49,11 +53,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 ~~~~~~~ error: no match found +; CHK-NEXT: 1: hello +; CHK-NEXT: 2: again +; CHK-NEXT: check:2'0 X~~~~ +; CHK-NEXT: 3: whirled +; CHK-NEXT: check:2'0 ~~~~~~~ error: no match found +; CHK-NEXT: check:2'1 ? possible intended match ; CHK-NEXT: >>>>>> ; CHK-NOT: {{.}} @@ -228,13 +233,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 ~~~ error: no match found +; 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 ? possible intended match +; LAB-NEXT: 4: bar +; LAB-NEXT: label:3'0 ~~~ error: no match found ; LAB-NEXT: >>>>>> ; LAB-NOT: {{.}} Index: llvm/utils/FileCheck/FileCheck.cpp =================================================================== --- llvm/utils/FileCheck/FileCheck.cpp +++ llvm/utils/FileCheck/FileCheck.cpp @@ -145,6 +145,8 @@ switch (MatchTy) { case FileCheckDiag::MatchNoneButExpected: return MarkerStyle('X', raw_ostream::RED, "error: no match found"); + case FileCheckDiag::MatchFuzzy: + return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match"); case FileCheckDiag::MatchTypeCount: llvm_unreachable_internal("unexpected match type"); } @@ -164,18 +166,28 @@ // 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. OS << " - colors "; WithColor(OS, raw_ostream::RED, true) << "error"; + OS << ", "; + WithColor(OS, raw_ostream::MAGENTA, true) << "fuzzy match"; OS << "\n\n" << "If you are not seeing color above or in input dumps, try: -color\n"; } @@ -185,6 +197,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 @@ -234,6 +248,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; @@ -245,6 +261,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()); @@ -278,6 +307,7 @@ } InputAnnotation B; B.CheckLine = A.CheckLine; + B.CheckDiagIndex = A.CheckDiagIndex; B.Label = A.Label; B.InputLine = L; B.Marker = Marker; @@ -308,16 +338,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.