[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