Index: llvm/include/llvm/Support/FileCheck.h =================================================================== --- llvm/include/llvm/Support/FileCheck.h +++ llvm/include/llvm/Support/FileCheck.h @@ -39,10 +39,6 @@ bool VerboseVerbose = false; }; -//===----------------------------------------------------------------------===// -// Summary of a FileCheck diagnostic. -//===----------------------------------------------------------------------===// - namespace Check { enum FileCheckKind { @@ -86,6 +82,7 @@ }; } // namespace Check +/// Summary of a FileCheck diagnostic. struct FileCheckDiag { /// What is the FileCheck directive for this diagnostic? Check::FileCheckType CheckTy; @@ -131,8 +128,12 @@ unsigned InputStartCol; unsigned InputEndLine; unsigned InputEndCol; + /// A note to replace the one normally indicated by MatchTy, or the empty + /// string if none. + std::string Note; FileCheckDiag(const SourceMgr &SM, const Check::FileCheckType &CheckTy, - SMLoc CheckLoc, MatchType MatchTy, SMRange InputRange); + SMLoc CheckLoc, MatchType MatchTy, SMRange InputRange, + StringRef Note = ""); }; class FileCheckPatternContext; Index: llvm/lib/Support/FileCheck.cpp =================================================================== --- llvm/lib/Support/FileCheck.cpp +++ llvm/lib/Support/FileCheck.cpp @@ -1247,7 +1247,9 @@ } void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer, - SMRange MatchRange) const { + SMRange Range, + FileCheckDiag::MatchType MatchTy, + std::vector *Diags) const { // Print what we know about substitutions. if (!Substitutions.empty()) { for (const auto &Substitution : Substitutions) { @@ -1280,12 +1282,15 @@ OS.write_escaped(*MatchedValue) << "\""; } - if (MatchRange.isValid()) - SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note, OS.str(), - {MatchRange}); + // We report only the start of the match/search range to suggest we are + // reporting the substitutions as set at the start of the match/search. + // Indicating a non-zero-length range might instead seem to imply that the + // substitution matches or was captured from exactly that range. + if (Diags) + Diags->emplace_back(SM, CheckTy, getLoc(), MatchTy, + SMRange(Range.Start, Range.Start), OS.str()); else - SM.PrintMessage(SMLoc::getFromPointer(Buffer.data()), - SourceMgr::DK_Note, OS.str()); + SM.PrintMessage(Range.Start, SourceMgr::DK_Note, OS.str()); } } } @@ -1295,14 +1300,17 @@ Check::FileCheckType CheckTy, StringRef Buffer, size_t Pos, size_t Len, std::vector *Diags, - bool AdjustPrevDiag = false) { + bool AdjustPrevDiags = false) { SMLoc Start = SMLoc::getFromPointer(Buffer.data() + Pos); SMLoc End = SMLoc::getFromPointer(Buffer.data() + Pos + Len); SMRange Range(Start, End); if (Diags) { - if (AdjustPrevDiag) - Diags->rbegin()->MatchTy = MatchTy; - else + if (AdjustPrevDiags) { + SMLoc CheckLoc = Diags->rbegin()->CheckLoc; + for (auto I = Diags->rbegin(), E = Diags->rend(); + I != E && I->CheckLoc == CheckLoc; ++I) + I->MatchTy = MatchTy; + } else Diags->emplace_back(SM, CheckTy, Loc, MatchTy, Range); } return Range; @@ -1455,8 +1463,8 @@ FileCheckDiag::FileCheckDiag(const SourceMgr &SM, const Check::FileCheckType &CheckTy, SMLoc CheckLoc, MatchType MatchTy, - SMRange InputRange) - : CheckTy(CheckTy), CheckLoc(CheckLoc), MatchTy(MatchTy) { + SMRange InputRange, StringRef Note) + : CheckTy(CheckTy), CheckLoc(CheckLoc), MatchTy(MatchTy), Note(Note) { auto Start = SM.getLineAndColumn(InputRange.Start); auto End = SM.getLineAndColumn(InputRange.End); InputStartLine = Start.first; @@ -1863,10 +1871,13 @@ // diagnostics. PrintDiag = !Diags; } - SMRange MatchRange = ProcessMatchResult( - ExpectedMatch ? FileCheckDiag::MatchFoundAndExpected - : FileCheckDiag::MatchFoundButExcluded, - SM, Loc, Pat.getCheckTy(), Buffer, MatchPos, MatchLen, Diags); + FileCheckDiag::MatchType MatchTy = ExpectedMatch + ? FileCheckDiag::MatchFoundAndExpected + : FileCheckDiag::MatchFoundButExcluded; + SMRange MatchRange = ProcessMatchResult(MatchTy, SM, Loc, Pat.getCheckTy(), + Buffer, MatchPos, MatchLen, Diags); + if (Diags) + Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, Diags); if (!PrintDiag) return; @@ -1881,7 +1892,7 @@ Loc, ExpectedMatch ? SourceMgr::DK_Remark : SourceMgr::DK_Error, Message); SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note, "found here", {MatchRange}); - Pat.printSubstitutions(SM, Buffer, MatchRange); + Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, nullptr); } static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, @@ -1914,10 +1925,13 @@ // If the current position is at the end of a line, advance to the start of // the next line. Buffer = Buffer.substr(Buffer.find_first_not_of(" \t\n\r")); - SMRange SearchRange = ProcessMatchResult( - ExpectedMatch ? FileCheckDiag::MatchNoneButExpected - : FileCheckDiag::MatchNoneAndExcluded, - SM, Loc, Pat.getCheckTy(), Buffer, 0, Buffer.size(), Diags); + FileCheckDiag::MatchType MatchTy = ExpectedMatch + ? FileCheckDiag::MatchNoneButExpected + : FileCheckDiag::MatchNoneAndExcluded; + SMRange SearchRange = ProcessMatchResult(MatchTy, SM, Loc, Pat.getCheckTy(), + Buffer, 0, Buffer.size(), Diags); + if (Diags) + Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, Diags); if (!PrintDiag) { consumeError(std::move(MatchErrors)); return; @@ -1945,7 +1959,7 @@ SM.PrintMessage(SearchRange.Start, SourceMgr::DK_Note, "scanning from here"); // Allow the pattern to print additional information if desired. - Pat.printSubstitutions(SM, Buffer); + Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, nullptr); if (ExpectedMatch) Pat.printFuzzyMatch(SM, Buffer, Diags); @@ -2248,8 +2262,12 @@ SM.PrintMessage(OldStart, SourceMgr::DK_Note, "match discarded, overlaps earlier DAG match here", {OldRange}); - } else - Diags->rbegin()->MatchTy = FileCheckDiag::MatchFoundButDiscarded; + } else { + SMLoc CheckLoc = Diags->rbegin()->CheckLoc; + for (auto I = Diags->rbegin(), E = Diags->rend(); + I != E && I->CheckLoc == CheckLoc; ++I) + I->MatchTy = FileCheckDiag::MatchFoundButDiscarded; + } } MatchPos = MI->End; } Index: llvm/lib/Support/FileCheckImpl.h =================================================================== --- llvm/lib/Support/FileCheckImpl.h +++ llvm/lib/Support/FileCheckImpl.h @@ -683,7 +683,8 @@ /// Prints the value of successful substitutions or the name of the undefined /// string or numeric variables preventing a successful substitution. void printSubstitutions(const SourceMgr &SM, StringRef Buffer, - SMRange MatchRange = None) const; + SMRange MatchRange, FileCheckDiag::MatchType MatchTy, + std::vector *Diags) const; void printFuzzyMatch(const SourceMgr &SM, StringRef Buffer, std::vector *Diags) const; Index: llvm/test/FileCheck/dump-input-annotations.txt =================================================================== --- llvm/test/FileCheck/dump-input-annotations.txt +++ llvm/test/FileCheck/dump-input-annotations.txt @@ -599,3 +599,149 @@ ; IMPNOT-NEXT:not:imp3 !~~~~ error: no match expected ; IMPNOT-NEXT:>>>>>> ; IMPNOT-NOT:{{.}} + +;-------------------------------------------------- +; Substitutions: successful and failed positive directives. +;-------------------------------------------------- + +; RUN: echo 'def-match1 def-match2' > %t.in +; RUN: echo 'def-match1 def-nomatch' >> %t.in + +; RUN: echo 'CHECK: [[DEF_MATCH1]] [[DEF_MATCH2]]' > %t.chk +; RUN: echo 'CHECK: [[DEF_MATCH1]] [[UNDEF]] [[DEF_NOMATCH]]' >> %t.chk + +; RUN: %ProtectFileCheckOutput \ +; RUN: not FileCheck -dump-input=always -vv -input-file=%t.in %t.chk 2>&1 \ +; RUN: -DDEF_MATCH1=def-match1 -DDEF_MATCH2=def-match2 \ +; RUN: -DDEF_NOMATCH=foobar \ +; RUN: | FileCheck -match-full-lines %s -check-prefix=SUBST-POS + +; SUBST-POS:<<<<<< +; SUBST-POS-NEXT: 1: def-match1 def-match2 +; SUBST-POS-NEXT:check:1'0 ^~~~~~~~~~~~~~~~~~~~~ +; SUBST-POS-NEXT:check:1'1 with "DEF_MATCH1" equal to "def-match1" +; SUBST-POS-NEXT:check:1'2 with "DEF_MATCH2" equal to "def-match2" +; SUBST-POS-NEXT: 2: def-match1 def-nomatch +; SUBST-POS-NEXT:check:2'0 X~~~~~~~~~~~~~~~~~~~~~ error: no match found +; SUBST-POS-NEXT:check:2'1 with "DEF_MATCH1" equal to "def-match1" +; SUBST-POS-NEXT:check:2'2 uses undefined variable(s): "UNDEF" +; SUBST-POS-NEXT:check:2'3 with "DEF_NOMATCH" equal to "foobar" +; SUBST-POS-NEXT:check:2'4 ? possible intended match +; SUBST-POS-NEXT:>>>>>> + +;-------------------------------------------------- +; Substitutions: successful and failed negative directives. +; +; FIXME: Even CHECK-NOT should fail if a variable isn't defined, right? +;-------------------------------------------------- + +; RUN: echo 'def-match1 def-nomatch' > %t.in +; RUN: echo 'def-match1 def-match2' >> %t.in +; RUN: echo 'END' >> %t.in + +; RUN: echo 'CHECK-NOT: [[DEF_MATCH1]] [[UNDEF]] [[DEF_NOMATCH]]' > %t.chk +; RUN: echo 'CHECK-NOT: [[DEF_MATCH1]] [[DEF_MATCH2]]' >> %t.chk +; RUN: echo 'CHECK: END' >> %t.chk + +; RUN: %ProtectFileCheckOutput \ +; RUN: not FileCheck -dump-input=always -vv -input-file=%t.in %t.chk 2>&1 \ +; RUN: -DDEF_MATCH1=def-match1 -DDEF_MATCH2=def-match2 \ +; RUN: -DDEF_NOMATCH=foobar \ +; RUN: | FileCheck -match-full-lines %s -check-prefix=SUBST-NEG + +; SUBST-NEG:<<<<<< +; SUBST-NEG-NEXT: 1: def-match1 def-nomatch +; SUBST-NEG-NEXT:not:1'0 X~~~~~~~~~~~~~~~~~~~~~ +; SUBST-NEG-NEXT:not:1'1 with "DEF_MATCH1" equal to "def-match1" +; SUBST-NEG-NEXT:not:1'2 uses undefined variable(s): "UNDEF" +; SUBST-NEG-NEXT:not:1'3 with "DEF_NOMATCH" equal to "foobar" +; SUBST-NEG-NEXT: 2: def-match1 def-match2 +; SUBST-NEG-NEXT:not:1'0 ~~~~~~~~~~~~~~~~~~~~~ +; SUBST-NEG-NEXT:not:2'0 !~~~~~~~~~~~~~~~~~~~~ error: no match expected +; SUBST-NEG-NEXT:not:2'1 with "DEF_MATCH1" equal to "def-match1" +; SUBST-NEG-NEXT:not:2'2 with "DEF_MATCH2" equal to "def-match2" +; SUBST-NEG-NEXT: 3: END +; SUBST-NEG-NEXT:check:3 ^~~ +; SUBST-NEG-NEXT:>>>>>> + +;-------------------------------------------------- +; Substitutions: CHECK-NEXT, CHECK-SAME, CHECK-DAG fixups. +; +; When CHECK-NEXT or CHECK-SAME fails for the wrong line, or when a CHECK-DAG +; match is discarded, the associated diagnostic type must be converted from +; successful to failed or discarded. However, any substitution diagnostics must +; be traversed to find that diagnostic. +;-------------------------------------------------- + +;- - - - - - - - - - - - - - - - - - - - - - - - - +; CHECK-NEXT. +;- - - - - - - - - - - - - - - - - - - - - - - - - + +; RUN: echo 'pre var' > %t.in + +; RUN: echo 'CHECK: pre' > %t.chk +; RUN: echo 'CHECK-NEXT: [[VAR]]' >> %t.chk + +; RUN: %ProtectFileCheckOutput \ +; RUN: not FileCheck -dump-input=always -vv -input-file=%t.in %t.chk 2>&1 \ +; RUN: -DVAR=var \ +; RUN: | FileCheck -match-full-lines %s -check-prefix=SUBST_NEXT + +; SUBST_NEXT:<<<<<< +; SUBST_NEXT-NEXT: 1: pre var +; SUBST_NEXT-NEXT:check:1 ^~~ +; SUBST_NEXT-NEXT:next:2'0 !~~ error: match on wrong line +; SUBST_NEXT-NEXT:next:2'1 with "VAR" equal to "var" +; SUBST_NEXT-NEXT:>>>>>> + +;- - - - - - - - - - - - - - - - - - - - - - - - - +; CHECK-SAME. +;- - - - - - - - - - - - - - - - - - - - - - - - - + +; RUN: echo 'pre' > %t.in +; RUN: echo 'var' >> %t.in + +; RUN: echo 'CHECK: pre' > %t.chk +; RUN: echo 'CHECK-SAME: [[VAR]]' >> %t.chk + +; RUN: %ProtectFileCheckOutput \ +; RUN: not FileCheck -dump-input=always -vv -input-file=%t.in %t.chk 2>&1 \ +; RUN: -DVAR=var \ +; RUN: | FileCheck -match-full-lines %s -check-prefix=SUBST_SAME + +; SUBST_SAME:<<<<<< +; SUBST_SAME-NEXT: 1: pre +; SUBST_SAME-NEXT:check:1 ^~~ +; SUBST_SAME-NEXT: 2: var +; SUBST_SAME-NEXT:same:2'0 !~~ error: match on wrong line +; SUBST_SAME-NEXT:same:2'1 with "VAR" equal to "var" +; SUBST_SAME-NEXT:>>>>>> + +;- - - - - - - - - - - - - - - - - - - - - - - - - +; CHECK-DAG. +;- - - - - - - - - - - - - - - - - - - - - - - - - + +; RUN: echo 'var' > %t.in +; RUN: echo 'var' >> %t.in +; RUN: echo 'END' >> %t.in + +; RUN: echo 'CHECK-DAG: var' > %t.chk +; RUN: echo 'CHECK-DAG: [[VAR]]' >> %t.chk +; RUN: echo 'CHECK: END' >> %t.chk + +; RUN: %ProtectFileCheckOutput \ +; RUN: FileCheck -dump-input=always -vv -input-file=%t.in %t.chk 2>&1 \ +; RUN: -DVAR=var \ +; RUN: | FileCheck -match-full-lines %s -check-prefix=SUBST_DAG + +; SUBST_DAG:<<<<<< +; SUBST_DAG-NEXT: 1: var +; SUBST_DAG-NEXT:dag:1 ^~~ +; SUBST_DAG-NEXT:dag:2'0 !~~ discard: overlaps earlier match +; SUBST_DAG-NEXT:dag:2'1 with "VAR" equal to "var" +; SUBST_DAG-NEXT: 2: var +; SUBST_DAG-NEXT:dag:2'2 ^~~ +; SUBST_DAG-NEXT:dag:2'3 with "VAR" equal to "var" +; SUBST_DAG-NEXT: 3: END +; SUBST_DAG-NEXT:check:3 ^~~ +; SUBST_DAG-NEXT:>>>>>> Index: llvm/test/FileCheck/verbose.txt =================================================================== --- llvm/test/FileCheck/verbose.txt +++ llvm/test/FileCheck/verbose.txt @@ -76,7 +76,7 @@ V-NEXT: {{^}}^~~~~~~~~~~~~{{$}} V-NEXT: verbose.txt:[[#@LINE-18]]:1: note: with "NUMVAR - 1" equal to "41" V-NEXT: {{^}}NUMVAR - 1:41{{$}} -V-NEXT: {{^}}^~~~~~~~~~~~~{{$}} +V-NEXT: {{^}}^{{$}} VV-NEXT: verbose.txt:[[#@LINE-20]]:12: remark: {{C}}HECK-NOT: excluded string not found in input VV-NEXT: {{C}}HECK-NOT: {{[[][[]#NUMVAR [+] 1[]][]]$}} Index: llvm/utils/FileCheck/FileCheck.cpp =================================================================== --- llvm/utils/FileCheck/FileCheck.cpp +++ llvm/utils/FileCheck/FileCheck.cpp @@ -402,6 +402,18 @@ LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); A.Marker = GetMarker(DiagItr->MatchTy); + if (!DiagItr->Note.empty()) { + A.Marker.Note = DiagItr->Note; + // It's less confusing if notes that don't actually have ranges don't have + // markers. For example, a marker for 'with "VAR" equal to "5"' would + // seem to indicate where "VAR" matches, but the location we actually have + // for the marker simply points to the start of the match/search range for + // the full pattern of which the substitution is potentially just one + // component. + if (DiagItr->InputStartLine == DiagItr->InputEndLine && + DiagItr->InputStartCol == DiagItr->InputEndCol) + A.Marker.Lead = ' '; + } A.FoundAndExpectedMatch = DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected;