[llvm] [FileCheck] Call out var captures on unmatched patterns (PR #206303)

Joel E. Denny via llvm-commits llvm-commits at lists.llvm.org
Sat Jun 27 21:53:29 PDT 2026


https://github.com/jdenny-ornl created https://github.com/llvm/llvm-project/pull/206303

This patch is motivated by an #llvm IRC chat in 2019 with @AaronBallman , where he pointed out an example similar to following:

```
$ cat input
[[clang::optnone]] void foo() {

$ cat check
CHECK: [[clang::optnone]] void foo() {

$ FileCheck check < input |& tail -7
Input was:
<<<<<<
           1: [[clang::optnone]] void foo() {
check:1'0    {                                } search range (exclusive bounds)
check:1'1                                       error: no match found
check:1'2            ?                          possible intended match
>>>>>>
```

It is very easy to miss why that fails.  This patch adds one more note that hopefully clears up the confusion:

```
$ FileCheck check < input |& tail -8
Input was:
<<<<<<
           1: [[clang::optnone]] void foo() {
check:1'0    {                                } search range (exclusive bounds)
check:1'1                                       error: no match found
check:1'2                                       pattern attempts to capture variables: "clang"
check:1'3            ?                          possible intended match
>>>>>>
```

That is, where matching patterns print successful variable captures, this patch makes unmatching patterns print attempted variable captures.

>From 702fc6c14e5869e4ef753c3f916cb39a23abe050 Mon Sep 17 00:00:00 2001
From: "Joel E. Denny" <jdenny.ornl at gmail.com>
Date: Sat, 27 Jun 2026 23:54:53 -0400
Subject: [PATCH] [FileCheck] Call out var captures on unmatched patterns

This patch is motivated by an #llvm IRC chat in 2019 with Aaron
Ballman, where he pointed out an example similar to following:

```
$ cat input
[[clang::optnone]] void foo() {

$ cat check
CHECK: [[clang::optnone]] void foo() {

$ FileCheck check < input |& tail -7
Input was:
<<<<<<
           1: [[clang::optnone]] void foo() {
check:1'0    {                                } search range (exclusive bounds)
check:1'1                                       error: no match found
check:1'2            ?                          possible intended match
>>>>>>
```

It is very easy to miss why that fails.  This patch adds one more note
that hopefully clears up the confusion:

```
$ FileCheck check < input |& tail -8
Input was:
<<<<<<
           1: [[clang::optnone]] void foo() {
check:1'0    {                                } search range (exclusive bounds)
check:1'1                                       error: no match found
check:1'2                                       pattern attempts to capture variables: "clang"
check:1'3            ?                          possible intended match
>>>>>>
```

That is, where matching patterns print successful variable captures,
this patch makes unmatching patterns print attempted variable
captures.
---
 llvm/lib/FileCheck/FileCheck.cpp              |  22 ++++
 llvm/lib/FileCheck/FileCheckImpl.h            |   3 +
 .../matched-excluded-pattern.txt              |   4 +
 .../matched-expected-pattern.txt              |   6 +-
 .../FileCheck/unmatched-substs-captures.txt   | 123 ++++++++++++++++++
 5 files changed, 157 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/FileCheck/unmatched-substs-captures.txt

diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index ae25a078713e7..c2a91ca598322 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -1292,6 +1292,25 @@ void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
   }
 }
 
+void Pattern::printVariables(const SourceMgr &SM, StringRef Buffer,
+                             FileCheckDiagList *Diags) const {
+  if (VariableDefs.empty() && NumericVariableDefs.empty())
+    return;
+  SmallString<256> Msg;
+  raw_svector_ostream OS(Msg);
+  OS << "pattern attempts to capture variables: ";
+  llvm::ListSeparator LS;
+  for (const auto &Def : VariableDefs)
+    OS << LS << '"' << Def.first << '"';
+  for (const auto &Def : NumericVariableDefs)
+    OS << LS << '"' << Def.getKey() << '"';
+  SMLoc Start = SMLoc::getFromPointer(Buffer.data());
+  if (Diags)
+    Diags->emplace<MatchCustomNoteDiag>(OS.str());
+  else
+    SM.PrintMessage(Start, SourceMgr::DK_Note, OS.str());
+}
+
 void Pattern::printVariableDefs(const SourceMgr &SM,
                                 FileCheckDiagList *Diags) const {
   if (VariableDefs.empty() && NumericVariableDefs.empty())
@@ -2123,6 +2142,7 @@ static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM,
     for (StringRef ErrorMsg : ErrorMsgs)
       Diags->emplace<MatchCustomNoteDiag>(ErrorMsg);
     Pat.printSubstitutions(SM, Buffer, SearchRange, Diags);
+    Pat.printVariables(SM, Buffer, Diags);
   }
   if (!PrintDiag) {
     assert(!HasError && "expected to report more diagnostics for error");
@@ -2149,6 +2169,8 @@ static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM,
   // Print additional information, which can be useful even after a pattern
   // error.
   Pat.printSubstitutions(SM, Buffer, SearchRange, nullptr);
+  Pat.printVariables(SM, Buffer, nullptr);
+
   if (ExpectedMatch)
     Pat.printFuzzyMatch(SM, Buffer, Diags);
   return ErrorReported::reportedOrSuccess(HasError);
diff --git a/llvm/lib/FileCheck/FileCheckImpl.h b/llvm/lib/FileCheck/FileCheckImpl.h
index 5393650020a30..21dbccca34ec7 100644
--- a/llvm/lib/FileCheck/FileCheckImpl.h
+++ b/llvm/lib/FileCheck/FileCheckImpl.h
@@ -742,6 +742,9 @@ class Pattern {
   }
   LLVM_ABI_FOR_TEST void printVariableDefs(const SourceMgr &SM,
                                            FileCheckDiagList *Diags) const;
+  /// Prints a list of all variables that this pattern attempts to capture.
+  void printVariables(const SourceMgr &SM, StringRef Buffer,
+                      FileCheckDiagList *Diags) const;
 
   Check::FileCheckType getCheckTy() const { return CheckTy; }
 
diff --git a/llvm/test/FileCheck/match-time-error-propagation/matched-excluded-pattern.txt b/llvm/test/FileCheck/match-time-error-propagation/matched-excluded-pattern.txt
index 7c7a3e44b77d2..9773cc0b7efeb 100644
--- a/llvm/test/FileCheck/match-time-error-propagation/matched-excluded-pattern.txt
+++ b/llvm/test/FileCheck/match-time-error-propagation/matched-excluded-pattern.txt
@@ -18,6 +18,9 @@ ERR-VV-EMPTY:
     ERR-NEXT:{{ *}}^
     ERR-NEXT:<stdin>:1:1: note: with "122+1" equal to "123"
     ERR-NEXT:123 abc -1
+    ERR-NEXT:^
+    ERR-NEXT:<stdin>:1:1: note: pattern attempts to capture variables: "STR", "NUM"
+    ERR-NEXT:123 abc -1
     ERR-NEXT:^
      ERR-NOT:{{error|note|remark|<stdin>}}:
 
@@ -27,6 +30,7 @@ ERR-VV-EMPTY:
    DUMP-NEXT:not:1'1                  error: match failed for invalid pattern
    DUMP-NEXT:not:1'2                  unable to substitute variable or numeric expression: overflow error
    DUMP-NEXT:not:1'3                  with "122+1" equal to "123"
+   DUMP-NEXT:not:1'4                  pattern attempts to capture variables: "STR", "NUM"
 DUMP-VV-NEXT:         2: 
 DUMP-VV-NEXT:eof:1       ^
    DUMP-NEXT:>>>>>>
diff --git a/llvm/test/FileCheck/match-time-error-propagation/matched-expected-pattern.txt b/llvm/test/FileCheck/match-time-error-propagation/matched-expected-pattern.txt
index 2aa79662a02ca..a8109d8dcdd4c 100644
--- a/llvm/test/FileCheck/match-time-error-propagation/matched-expected-pattern.txt
+++ b/llvm/test/FileCheck/match-time-error-propagation/matched-expected-pattern.txt
@@ -12,6 +12,9 @@ RUN: echo > %t.in '123 abc -1'
  ERR-NEXT:<stdin>:1:1: note: with "122+1" equal to "123"
  ERR-NEXT:123 abc -1
  ERR-NEXT:^
+ ERR-NEXT:<stdin>:1:1: note: pattern attempts to capture variables: "STR", "NUM"
+ ERR-NEXT:123 abc -1
+ ERR-NEXT:^
  ERR-NEXT:<stdin>:1:3: note: possible intended match here
  ERR-NEXT:123 abc -1
  ERR-NEXT:  ^
@@ -23,7 +26,8 @@ DUMP-NEXT:check:1'0    {           } search range (exclusive bounds)
 DUMP-NEXT:check:1'1                  error: match failed for invalid pattern
 DUMP-NEXT:check:1'2                  unable to substitute variable or numeric expression: overflow error
 DUMP-NEXT:check:1'3                  with "122+1" equal to "123"
-DUMP-NEXT:check:1'4       ?          possible intended match
+DUMP-NEXT:check:1'4                  pattern attempts to capture variables: "STR", "NUM"
+DUMP-NEXT:check:1'5       ?          possible intended match
 DUMP-NEXT:>>>>>>
 
 ;--------------------------------------------------
diff --git a/llvm/test/FileCheck/unmatched-substs-captures.txt b/llvm/test/FileCheck/unmatched-substs-captures.txt
new file mode 100644
index 0000000000000..f963d999f13c0
--- /dev/null
+++ b/llvm/test/FileCheck/unmatched-substs-captures.txt
@@ -0,0 +1,123 @@
+; Check that all substitutions and attempted captures, whether for string or
+; numeric variables, are reported when an unexpected pattern (e.g., CHECK-NOT)
+; or expected pattern (e.g., CHECK) does not match the input.
+;
+; Because an unmatched pattern cannot actually capture, it might somehow seem
+; wrong to report attempted captures.  However, that report can be enlightening
+; when the match failed because the test author has forgotten that square
+; brackets are special in FileCheck patterns.  For example:
+;
+;   CHECK: [[clang::optnone]] void foo() {
+
+RUN: rm -rf %t
+RUN: split-file %s %t
+
+DEFINE: %{opts} =
+DEFINE: %{run} = \
+DEFINE:  %ProtectFileCheckOutput \
+DEFINE:  not FileCheck %{opts} %t/check.txt < %t/input.txt 2>&1 | \
+DEFINE:    FileCheck %s -match-full-lines -strict-whitespace -check-prefixes
+
+REDEFINE: %{opts} = -dump-input=never
+RUN: %{run} ERR
+
+REDEFINE: %{opts} = -dump-input=never -vv
+RUN: %{run} ERR,ERR-VV
+
+REDEFINE: %{opts} = -dump-input=always
+RUN: %{run} ERR,DMP
+
+REDEFINE: %{opts} = -dump-input=always -vv
+RUN: %{run} ERR,DMP,DMP-VV
+
+    ERR-NOT:{{.}}
+     ERR-VV:{{.*}}/check.txt:1:9: remark: CHECK: expected string found in input
+ERR-VV-NEXT: CHECK: {{.*}}
+ERR-VV-NEXT:        ^
+ERR-VV-NEXT:<stdin>:1:1: note: found here
+ERR-VV-NEXT:abc 16
+ERR-VV-NEXT:^~~~~~
+ERR-VV-NEXT:<stdin>:1:1: note: with "NUM_OLD:0x10" equal to "16"
+ERR-VV-NEXT:abc 16
+ERR-VV-NEXT:^
+ERR-VV-NEXT:<stdin>:1:1: note: captured var "STR_OLD"
+ERR-VV-NEXT:abc 16
+ERR-VV-NEXT:^~~
+ERR-VV-NEXT:<stdin>:1:5: note: captured var "NUM_OLD"
+ERR-VV-NEXT:abc 16
+ERR-VV-NEXT:    ^~
+ERR-VV-NEXT:{{.*}}/check.txt:3:9: remark: CHECK: expected string found in input
+ERR-VV-NEXT: CHECK: end
+ERR-VV-NEXT:        ^
+ERR-VV-NEXT:<stdin>:2:1: note: found here
+ERR-VV-NEXT:end
+ERR-VV-NEXT:^~~
+ERR-VV-NEXT:{{.*}}/check.txt:2:12: remark: CHECK-NOT: excluded string not found in input
+ERR-VV-NEXT:CHECK-NOT: {{.*}}
+ERR-VV-NEXT:           ^
+ERR-VV-NEXT:<stdin>:1:7: note: scanning from here
+ERR-VV-NEXT:abc 16
+ERR-VV-NEXT:      ^
+ERR-VV-NEXT:<stdin>:1:7: note: with "STR_OLD" equal to "abc"
+ERR-VV-NEXT:abc 16
+ERR-VV-NEXT:      ^
+ERR-VV-NEXT:<stdin>:1:7: note: with "NUM_OLD + 1" equal to "17"
+ERR-VV-NEXT:abc 16
+ERR-VV-NEXT:      ^
+ERR-VV-NEXT:<stdin>:1:7: note: with "NUM_NEW:456" equal to "456"
+ERR-VV-NEXT:abc 16
+ERR-VV-NEXT:      ^
+ERR-VV-NEXT:<stdin>:1:7: note: pattern attempts to capture variables: "STR_NEW", "clang", "NUM_NEW"
+ERR-VV-NEXT:abc 16
+ERR-VV-NEXT:      ^
+ ERR-VV-NOT:{{.}}
+        ERR:{{.*}}/check.txt:4:9: error: CHECK: expected string not found in input
+   ERR-NEXT: CHECK: {{.*}}
+   ERR-NEXT:        ^
+   ERR-NEXT:<stdin>:2:4: note: scanning from here
+   ERR-NEXT:end
+   ERR-NEXT:   ^
+   ERR-NEXT:<stdin>:2:4: note: with "STR_OLD" equal to "abc"
+   ERR-NEXT:end
+   ERR-NEXT:   ^
+   ERR-NEXT:<stdin>:2:4: note: with "NUM_OLD + 1" equal to "17"
+   ERR-NEXT:end
+   ERR-NEXT:   ^
+   ERR-NEXT:<stdin>:2:4: note: with "NUM_NEW:456" equal to "456"
+   ERR-NEXT:end
+   ERR-NEXT:   ^
+   ERR-NEXT:<stdin>:2:4: note: pattern attempts to capture variables: "STR_NEW", "clang", "NUM_NEW"
+   ERR-NEXT:end
+   ERR-NEXT:   ^
+    ERR-NOT:{{.*}}{{error|note|remark|<stdin>}}:
+
+        DMP:<<<<<<
+   DMP-NEXT:           1: abc 16 
+DMP-VV-NEXT:check:1'0     ^~~~~~
+DMP-VV-NEXT:check:1'1              with "NUM_OLD:0x10" equal to "16"
+DMP-VV-NEXT:check:1'2     ^~~      captured var "STR_OLD"
+DMP-VV-NEXT:check:1'3         ^~   captured var "NUM_OLD"
+DMP-VV-NEXT:not:2'0            { } search range (exclusive bounds)
+DMP-VV-NEXT:not:2'1                with "STR_OLD" equal to "abc"
+DMP-VV-NEXT:not:2'2                with "NUM_OLD + 1" equal to "17"
+DMP-VV-NEXT:not:2'3                with "NUM_NEW:456" equal to "456"
+DMP-VV-NEXT:not:2'4                pattern attempts to capture variables: "STR_NEW", "clang", "NUM_NEW"
+   DMP-NEXT:           2: end 
+DMP-VV-NEXT:check:3       ^~~
+   DMP-NEXT:check:4'0       { } search range (exclusive bounds)
+   DMP-NEXT:check:4'1           error: no match found in search range
+   DMP-NEXT:check:4'2           with "STR_OLD" equal to "abc"
+   DMP-NEXT:check:4'3           with "NUM_OLD + 1" equal to "17"
+   DMP-NEXT:check:4'4           with "NUM_NEW:456" equal to "456"
+   DMP-NEXT:check:4'5           pattern attempts to capture variables: "STR_NEW", "clang", "NUM_NEW"
+   DMP-NEXT:>>>>>>
+
+;--- check.txt
+    CHECK: [[STR_OLD:abc]] [[#NUM_OLD:0x10]]
+CHECK-NOT: [[STR_OLD]] [[STR_NEW:xyz]] [[clang::optnone]] [[#NUM_OLD + 1]] [[#NUM_NEW:456]]
+    CHECK: end
+    CHECK: [[STR_OLD]] [[STR_NEW:xyz]] [[clang::optnone]] [[#NUM_OLD + 1]] [[#NUM_NEW:456]]
+
+;--- input.txt
+abc 16
+end



More information about the llvm-commits mailing list