bool VerboseVerbose = false;
};
-//===----------------------------------------------------------------------===//
-// Summary of a FileCheck diagnostic.
-//===----------------------------------------------------------------------===//
-
namespace Check {
enum FileCheckKind {
};
} // namespace Check
+/// Summary of a FileCheck diagnostic.
struct FileCheckDiag {
/// What is the FileCheck directive for this diagnostic?
Check::FileCheckType CheckTy;
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;
}
void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
- SMRange MatchRange) const {
+ SMRange Range,
+ FileCheckDiag::MatchType MatchTy,
+ std::vector<FileCheckDiag> *Diags) const {
// Print what we know about substitutions.
if (!Substitutions.empty()) {
for (const auto &Substitution : Substitutions) {
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());
}
}
}
Check::FileCheckType CheckTy,
StringRef Buffer, size_t Pos, size_t Len,
std::vector<FileCheckDiag> *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;
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;
// 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;
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,
// 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;
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);
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;
}
/// 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<FileCheckDiag> *Diags) const;
void printFuzzyMatch(const SourceMgr &SM, StringRef Buffer,
std::vector<FileCheckDiag> *Diags) const;
; 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: The first CHECK-NOT directive below uses an undefined variable.
+; Because it therefore cannot match regardless of the input, the directive
+; succeeds. However, it should fail in this case. Update the example below
+; once that's fixed.
+;--------------------------------------------------
+
+; 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:>>>>>>
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[]][]]$}}
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;