[llvm] d680711 - [FileCheck] Extend -dump-input with substitutions

Joel E. Denny via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 28 16:16:02 PDT 2020


Author: Joel E. Denny
Date: 2020-07-28T19:15:18-04:00
New Revision: d680711b94e94e9387076a0daf2a329e304e6751

URL: https://github.com/llvm/llvm-project/commit/d680711b94e94e9387076a0daf2a329e304e6751
DIFF: https://github.com/llvm/llvm-project/commit/d680711b94e94e9387076a0daf2a329e304e6751.diff

LOG: [FileCheck] Extend -dump-input with substitutions

Substitutions are already reported in the diagnostics appearing before
the input dump in the case of failed directives, and they're reported
in traces (produced by `-vv -dump-input=never`) in the case of
successful directives.  However, those reports are not always
convenient to view while investigating the input dump, so this patch
adds the substitution report to the input dump too.  For example:

```
$ cat check
CHECK: hello [[WHAT:[a-z]+]]
CHECK: [[VERB]] [[WHAT]]

$ FileCheck -vv -DVERB=goodbye check < input |& tail -8
<<<<<<
           1: hello world
check:1       ^~~~~~~~~~~
           2: goodbye word
check:2'0     X~~~~~~~~~~~ error: no match found
check:2'1                  with "VERB" equal to "goodbye"
check:2'2                  with "WHAT" equal to "world"
>>>>>>
```

Without this patch, the location reported for a substitution for a
directive match is the directive's full match range.  This location is
misleading as it implies the substitution itself matches that range.
This patch changes the reported location to just the match range start
to suggest the substitution is known at the start of the match.  (As
in the above example, input dumps don't mark any range for
substitutions.  The location info in that case simply identifies the
right line for the annotation.)

Reviewed By: mehdi_amini, thopre

Differential Revision: https://reviews.llvm.org/D83650

Added: 
    

Modified: 
    llvm/include/llvm/Support/FileCheck.h
    llvm/lib/Support/FileCheck.cpp
    llvm/lib/Support/FileCheckImpl.h
    llvm/test/FileCheck/dump-input-annotations.txt
    llvm/test/FileCheck/verbose.txt
    llvm/utils/FileCheck/FileCheck.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Support/FileCheck.h b/llvm/include/llvm/Support/FileCheck.h
index 2f0e641394d5..1eb609ef725e 100644
--- a/llvm/include/llvm/Support/FileCheck.h
+++ b/llvm/include/llvm/Support/FileCheck.h
@@ -39,10 +39,6 @@ struct FileCheckRequest {
   bool VerboseVerbose = false;
 };
 
-//===----------------------------------------------------------------------===//
-// Summary of a FileCheck diagnostic.
-//===----------------------------------------------------------------------===//
-
 namespace Check {
 
 enum FileCheckKind {
@@ -86,6 +82,7 @@ class FileCheckType {
 };
 } // namespace Check
 
+/// Summary of a FileCheck diagnostic.
 struct FileCheckDiag {
   /// What is the FileCheck directive for this diagnostic?
   Check::FileCheckType CheckTy;
@@ -131,8 +128,12 @@ struct FileCheckDiag {
   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;

diff  --git a/llvm/lib/Support/FileCheck.cpp b/llvm/lib/Support/FileCheck.cpp
index d0e79c675bcb..fd426a16d02e 100644
--- a/llvm/lib/Support/FileCheck.cpp
+++ b/llvm/lib/Support/FileCheck.cpp
@@ -1247,7 +1247,9 @@ unsigned Pattern::computeMatchDistance(StringRef Buffer) const {
 }
 
 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) {
@@ -1280,12 +1282,15 @@ void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
         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 @@ static SMRange ProcessMatchResult(FileCheckDiag::MatchType MatchTy,
                                   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;
@@ -1455,8 +1463,8 @@ StringRef FileCheck::CanonicalizeFile(MemoryBuffer &MB,
 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 @@ static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM,
     // 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 @@ static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM,
       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 @@ static void PrintNoMatch(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;
@@ -1945,7 +1959,7 @@ static void PrintNoMatch(bool ExpectedMatch, const SourceMgr &SM,
   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 @@ size_t FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer,
           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;
     }

diff  --git a/llvm/lib/Support/FileCheckImpl.h b/llvm/lib/Support/FileCheckImpl.h
index 6ca67ec2964c..d3f4384e0df0 100644
--- a/llvm/lib/Support/FileCheckImpl.h
+++ b/llvm/lib/Support/FileCheckImpl.h
@@ -683,7 +683,8 @@ class Pattern {
   /// 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;
 

diff  --git a/llvm/test/FileCheck/dump-input-annotations.txt b/llvm/test/FileCheck/dump-input-annotations.txt
index 785ee23d03dd..fe39f39186bf 100644
--- a/llvm/test/FileCheck/dump-input-annotations.txt
+++ b/llvm/test/FileCheck/dump-input-annotations.txt
@@ -599,3 +599,152 @@
 ;    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:>>>>>>

diff  --git a/llvm/test/FileCheck/verbose.txt b/llvm/test/FileCheck/verbose.txt
index d0532d02f9fc..2098ddc4a2d8 100644
--- a/llvm/test/FileCheck/verbose.txt
+++ b/llvm/test/FileCheck/verbose.txt
@@ -76,7 +76,7 @@ V-NEXT: {{^}}NUMVAR - 1:41{{$}}
 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[]][]]$}}

diff  --git a/llvm/utils/FileCheck/FileCheck.cpp b/llvm/utils/FileCheck/FileCheck.cpp
index fa79c5e89489..81889b2540f9 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -402,6 +402,18 @@ BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
     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;
 


        


More information about the llvm-commits mailing list