[llvm] [FileCheck] Add check-not support for diff option (PR #189357)
Shivam Gupta via llvm-commits
llvm-commits at lists.llvm.org
Sat Apr 18 04:51:41 PDT 2026
https://github.com/xgupta updated https://github.com/llvm/llvm-project/pull/189357
>From 41e32c75d74f0b8e12609311e7a8ef20fea38b34 Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Sat, 18 Apr 2026 16:58:24 +0530
Subject: [PATCH 1/2] [FileCheck] Add variable substitution for diff option
---
llvm/docs/CommandGuide/FileCheck.rst | 5 +
llvm/include/llvm/FileCheck/FileCheck.h | 8 +-
llvm/lib/FileCheck/FileCheck.cpp | 147 ++++++++++++++++--
llvm/lib/FileCheck/FileCheckImpl.h | 2 +
.../FileCheck/diff/diff-label-fuzzy-match.txt | 19 ++-
.../FileCheck/diff/diff-long-line-trunc.txt | 12 ++
llvm/test/FileCheck/diff/diff-multi-subs.txt | 15 ++
.../FileCheck/diff/diff-nested-vars-subs.txt | 16 ++
.../test/FileCheck/diff/diff-numeric-expr.txt | 16 ++
.../diff/diff-resync-after-noise.txt | 19 ++-
.../FileCheck/diff/diff-substitutions.txt | 23 +++
.../diff/diff-variable-chain-subs.txt | 18 +++
llvm/utils/FileCheck/FileCheck.cpp | 7 +-
13 files changed, 276 insertions(+), 31 deletions(-)
create mode 100644 llvm/test/FileCheck/diff/diff-long-line-trunc.txt
create mode 100644 llvm/test/FileCheck/diff/diff-multi-subs.txt
create mode 100644 llvm/test/FileCheck/diff/diff-nested-vars-subs.txt
create mode 100644 llvm/test/FileCheck/diff/diff-numeric-expr.txt
create mode 100644 llvm/test/FileCheck/diff/diff-substitutions.txt
create mode 100644 llvm/test/FileCheck/diff/diff-variable-chain-subs.txt
diff --git a/llvm/docs/CommandGuide/FileCheck.rst b/llvm/docs/CommandGuide/FileCheck.rst
index d99cbbdd9f36f..4e6d8eb369295 100644
--- a/llvm/docs/CommandGuide/FileCheck.rst
+++ b/llvm/docs/CommandGuide/FileCheck.rst
@@ -117,6 +117,11 @@ and from the command line.
* ``none`` – Use the standard FileCheck diagnostic messages.
* ``unidiff`` – Display mismatches using a unified diff format.
+ * ``split`` – Display mismatches using a side-by-side split diff view.
+ * ``unidiff-no-substitutions`` – Same as ``unidiff`` but shows the raw
+ pattern without applying variable substitutions.
+ * ``split-no-substitutions`` – Same as ``split`` but shows the raw
+ pattern without applying variable substitutions.
.. option:: --dump-input <value>
diff --git a/llvm/include/llvm/FileCheck/FileCheck.h b/llvm/include/llvm/FileCheck/FileCheck.h
index 7896a942a3466..446447aca7d3f 100644
--- a/llvm/include/llvm/FileCheck/FileCheck.h
+++ b/llvm/include/llvm/FileCheck/FileCheck.h
@@ -28,7 +28,13 @@ class SourceMgr;
template <typename T> class SmallVectorImpl;
// Diff the output on failures.
-enum DiffFormatType { None, Unified };
+enum DiffFormatType {
+ None,
+ Unified,
+ Split,
+ SplitNoSubstitution,
+ UnifiedNoSubstitution
+};
/// Contains info about various FileCheck options.
struct FileCheckRequest {
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index 307be0b6e2faf..243ded6362234 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -2762,11 +2762,82 @@ static DiffContext getDiffContext(SourceMgr &SM, unsigned LineNo,
getLineText(LineNo + 1)};
}
+// Extracts a fixed-width "window" from string S, centered around DiffPos.
+static std::string getCenteredView(StringRef S, size_t DiffPos, size_t Width) {
+ if (S.size() <= Width)
+ return S.str() + std::string(Width - S.size(), ' ');
+
+ size_t HalfWidth = Width / 2;
+ size_t Start = (DiffPos > HalfWidth) ? DiffPos - HalfWidth : 0;
+
+ if (Start + Width > S.size())
+ Start = (S.size() > Width) ? S.size() - Width : 0;
+
+ std::string View = S.substr(Start, Width).str();
+
+ if (Start > 0 && Width > 3)
+ View.replace(0, 3, "...");
+
+ if (Start + Width < S.size() && View.size() > 3)
+ View.replace(View.size() - 3, 3, "...");
+
+ return View;
+}
+
+std::string Pattern::getSubstitutedRegex(StringRef PatternText) const {
+ std::string Result = PatternText.str();
+
+ // Iterate through substitutions and replace tags from left to right.
+ for (const auto &Substitution : Substitutions) {
+ Expected<std::string> ValueOrErr = Substitution->getResultForDiagnostics();
+ std::string CleanValue;
+
+ if (!ValueOrErr) {
+ consumeError(ValueOrErr.takeError());
+ CleanValue = "<UNDEFINED>";
+ } else {
+ CleanValue = *ValueOrErr;
+ // Numeric substitutions arrive wrapped in quotes so we need to strip
+ // them.
+ if (CleanValue.size() >= 2 && CleanValue.front() == '"' &&
+ CleanValue.back() == '"')
+ CleanValue = CleanValue.substr(1, CleanValue.size() - 2);
+ }
+
+ // Find the first occurrence of a variable tag
+ size_t Start = Result.find("[[");
+ if (Start == std::string::npos)
+ break;
+
+ size_t End = Result.find("]]", Start);
+ if (End == std::string::npos)
+ break;
+
+ size_t TagLen = (End + 2) - Start;
+ Result.replace(Start, TagLen, CleanValue);
+ }
+ return Result;
+}
+
// Renders a diagnostic diff via llvm::errs().
-static void renderDiff(unsigned ExpectedLineNo, unsigned ActualLineNo,
- StringRef ExpectedLine, StringRef ActualLine,
- const DiffContext &Ctx) {
+static void renderDiff(DiffFormatType Mode, unsigned ExpectedLineNo,
+ unsigned ActualLineNo, StringRef ExpectedLine,
+ StringRef ActualLine, const DiffContext &Ctx) {
auto &OS = errs();
+ constexpr unsigned ColWidth = 45;
+ constexpr StringRef Sep = " | ";
+
+ bool IsSplit = (Mode == Split || Mode == SplitNoSubstitution);
+
+ // Find the index from where the expected and actual text diverge.
+ size_t DiffPos = 0;
+ size_t MinLen = std::min(ExpectedLine.size(), ActualLine.size());
+ while (DiffPos < MinLen && ExpectedLine[DiffPos] == ActualLine[DiffPos])
+ ++DiffPos;
+
+ auto GetView = [&](StringRef S) {
+ return getCenteredView(S, DiffPos, ColWidth - 1);
+ };
// Header
OS.changeColor(raw_ostream::CYAN);
@@ -2775,31 +2846,71 @@ static void renderDiff(unsigned ExpectedLineNo, unsigned ActualLineNo,
// Before Context
if (!Ctx.LineBefore.empty()) {
- OS << " " << Ctx.LineBefore << "\n";
+ if (IsSplit)
+ OS << " " << GetView(Ctx.LineBefore) << Sep << GetView(Ctx.LineBefore)
+ << "\n";
+ else
+ OS << " " << Ctx.LineBefore << "\n";
}
// Mismatch
- OS.changeColor(raw_ostream::RED);
- OS << "-" << ExpectedLine << "\n";
+ if (IsSplit) {
+ OS << " ";
- OS.changeColor(raw_ostream::GREEN);
- OS << "+" << ActualLine.ltrim() << "\n";
- OS.resetColor();
+ OS.changeColor(raw_ostream::RED);
+ OS << GetView(ExpectedLine);
+ OS.resetColor();
+
+ // Use a bold yellow '!' to signify that the text might match,
+ // but it was found on the wrong line.
+ bool IsMoved = (ExpectedLineNo != ActualLineNo);
+
+ if (IsMoved) {
+ OS.changeColor(raw_ostream::YELLOW, true);
+ OS << " ! ";
+ } else {
+ OS << " | ";
+ }
+ OS.resetColor();
+
+ OS.changeColor(raw_ostream::GREEN);
+ OS << StringRef(GetView(ActualLine)).ltrim() << "\n";
+ OS.resetColor();
+ } else {
+ OS.changeColor(raw_ostream::RED);
+ OS << "-" << ExpectedLine << "\n";
+
+ OS.changeColor(raw_ostream::GREEN);
+ OS << "+" << ActualLine.ltrim() << "\n";
+ OS.resetColor();
+ }
// After Context
if (!Ctx.LineAfter.empty()) {
- OS << " " << Ctx.LineAfter << "\n";
+ if (Mode == Split)
+ OS << " " << GetView(Ctx.LineAfter) << Sep << GetView(Ctx.LineAfter)
+ << "\n";
+ else
+ OS << " " << Ctx.LineAfter << "\n";
}
}
-static bool printDiff(const FileCheckString &CheckStr, StringRef ActualLine,
- SourceMgr &SM, std::vector<FileCheckDiag> *Diags,
+static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
+ StringRef ActualLine, SourceMgr &SM,
+ std::vector<FileCheckDiag> *Diags,
unsigned OverwriteActualLine = 0) {
SMLoc PatternLoc = CheckStr.Pat.getLoc();
unsigned ExpectedLineNo = SM.getLineAndColumn(PatternLoc).first;
const char *PatPtr = PatternLoc.getPointer();
StringRef ExpectedLine = StringRef(PatPtr).split('\n').first.rtrim();
+ std::string ExpectedText;
+ if (Mode == DiffFormatType::SplitNoSubstitution ||
+ Mode == DiffFormatType::UnifiedNoSubstitution)
+ ExpectedText = ExpectedLine.str();
+ else
+ ExpectedText = CheckStr.Pat.getSubstitutedRegex(ExpectedLine);
+
// Resolve the Actual (Input) line number.
// Priority: 1. OverwriteActualLine (Found via Fuzzy match)
// 2. Direct pointer resolution via SourceMgr.
@@ -2820,7 +2931,8 @@ static bool printDiff(const FileCheckString &CheckStr, StringRef ActualLine,
unsigned BufID = SM.FindBufferContainingLoc(InputLoc);
DiffContext Context = getDiffContext(SM, ActualLineNo, BufID);
- renderDiff(ExpectedLineNo, ActualLineNo, ExpectedLine, ActualLine, Context);
+ renderDiff(Mode, ExpectedLineNo, ActualLineNo, ExpectedText, ActualLine,
+ Context);
errs() << '\n';
return true;
@@ -2829,6 +2941,7 @@ static bool printDiff(const FileCheckString &CheckStr, StringRef ActualLine,
// Report the mismatch on the current line and advance to the next line.
static bool handleDiffFailure(const FileCheckString &CheckStr,
StringRef &CheckRegion, SourceMgr &SM,
+ FileCheckRequest &Req,
std::vector<FileCheckDiag> *Diags,
raw_ostream &OS, bool &HeaderPrinted,
unsigned &TotalMismatches) {
@@ -2874,7 +2987,7 @@ static bool handleDiffFailure(const FileCheckString &CheckStr,
TargetLineNo = SM.getLineAndColumn(CurrentLoc).first;
}
- printDiff(CheckStr, TargetLine, SM, Diags, TargetLineNo);
+ printDiff(Req.DiffFormat, CheckStr, TargetLine, SM, Diags, TargetLineNo);
TotalMismatches++;
// Advance CheckRegion past the current line to recover for the next CHECK.
@@ -2940,8 +3053,8 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
// Handle failure
if (MatchPos == StringRef::npos) {
if (IsDiffFormat) {
- handleDiffFailure(CheckStr, CheckRegion, SM, Diags, OS, HeaderPrinted,
- TotalMismatches);
+ handleDiffFailure(CheckStr, CheckRegion, SM, Req, Diags, OS,
+ HeaderPrinted, TotalMismatches);
}
ChecksFailed = true;
i = j;
@@ -2957,7 +3070,7 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
(CurrentLineEnd != StringRef::npos)
? CheckRegion.drop_front(CurrentLineEnd + 1)
: CheckRegion;
- handleDiffFailure(CheckStr, NextLineRegion, SM, Diags, OS,
+ handleDiffFailure(CheckStr, NextLineRegion, SM, Req, Diags, OS,
HeaderPrinted, TotalMismatches);
ChecksFailed = true;
i = j;
diff --git a/llvm/lib/FileCheck/FileCheckImpl.h b/llvm/lib/FileCheck/FileCheckImpl.h
index 58a03d280b658..5b47b87076638 100644
--- a/llvm/lib/FileCheck/FileCheckImpl.h
+++ b/llvm/lib/FileCheck/FileCheckImpl.h
@@ -740,6 +740,8 @@ class Pattern {
std::vector<FileCheckDiag> *Diags,
const FileCheckRequest &Req) const;
+ std::string getSubstitutedRegex(StringRef PatternText) const;
+
bool hasVariable() const {
return !(Substitutions.empty() && VariableDefs.empty());
}
diff --git a/llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt b/llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt
index a04f2e84ac70b..1a011a61032ca 100644
--- a/llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt
+++ b/llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt
@@ -1,6 +1,8 @@
; RUN: split-file %s %t
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
-; RUN: | FileCheck %s --check-prefix=DIFF
+; RUN: | FileCheck %s --check-prefix=UNI
+; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=split 2>&1 \
+; RUN: | FileCheck %s --check-prefix=SPLIT
;--- input.txt
define void @foo() {
@@ -11,8 +13,13 @@ define void @foo() {
; CHECK-LABEL: define void @foo()
; CHECK: call void @work_test()
-; DIFF: @@ -[[#]] +[[#]] @@
-; DIFF: define void @foo() {
-; DIFF-NEXT: -call void @work_test()
-; DIFF-NEXT: +call void @work_task()
-; DIFF-NEXT: }
+; UNI: @@ -[[#]] +[[#]] @@
+; UNI: define void @foo() {
+; UNI-NEXT: -call void @work_test()
+; UNI-NEXT: +call void @work_task()
+; UNI-NEXT: }
+
+; SPLIT: @@ -[[#]] +[[#]] @@
+; SPLIT-NEXT: define void @foo() { | define void @foo() {
+; SPLIT-NEXT: {{.*}}call void @work_test(){{.*}}|{{.*}}call void @work_task()
+; SPLIT-NEXT: }
diff --git a/llvm/test/FileCheck/diff/diff-long-line-trunc.txt b/llvm/test/FileCheck/diff/diff-long-line-trunc.txt
new file mode 100644
index 0000000000000..f854ff3956864
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-long-line-trunc.txt
@@ -0,0 +1,12 @@
+; RUN: split-file %s %t
+; RUN: not FileCheck %t/check.txt --input-file=%t/input.txt --diff=split 2>&1 \
+; RUN: | FileCheck %s --check-prefix=DIFF
+
+;--- input.txt
+THIS_IS_A_VERY_LONG_PREFIX_THAT_SHOULD_BE_TRUNCATED_BY_THE_SLIDING_WINDOW_LOGIC_ACTUAL_VALUE
+
+;--- check.txt
+; CHECK: THIS_IS_A_VERY_LONG_PREFIX_THAT_SHOULD_BE_TRUNCATED_BY_THE_SLIDING_WINDOW_LOGIC_EXPECTED_VALUE
+
+; DIFF: @@ -1 +1 @@
+; DIFF-NEXT: {{.*}}...{{.*}}WINDOW_LOGIC_EXPECTED_VALUE{{.*}}|{{.*}}...{{.*}}WINDOW_LOGIC_ACTUAL_VALUE
diff --git a/llvm/test/FileCheck/diff/diff-multi-subs.txt b/llvm/test/FileCheck/diff/diff-multi-subs.txt
new file mode 100644
index 0000000000000..40d72113a44a1
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-multi-subs.txt
@@ -0,0 +1,15 @@
+; RUN: split-file %s %t
+; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=split 2>&1 \
+; RUN: | FileCheck %s --check-prefix=DIFF
+
+;--- input.txt
+SET REG=rax VAL=0
+USE rax, 1
+
+;--- check.txt
+; CHECK: SET REG=[[REG:[a-z]+]] VAL=[[VAL:[0-9]+]]
+; CHECK: USE [[REG]], [[VAL]]
+
+; DIFF: @@ -2 +2 @@
+; DIFF-NEXT: {{.*}}SET REG=rax VAL=0{{.*}}| SET REG=rax VAL=0{{.*}}
+; DIFF-NEXT: {{.*}}USE rax, 0{{.*}}|{{.*}}USE rax, 1{{.*}}
diff --git a/llvm/test/FileCheck/diff/diff-nested-vars-subs.txt b/llvm/test/FileCheck/diff/diff-nested-vars-subs.txt
new file mode 100644
index 0000000000000..7be1c1d9d96ab
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-nested-vars-subs.txt
@@ -0,0 +1,16 @@
+; RUN: split-file %s %t
+; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
+; RUN: | FileCheck %s --check-prefix=DIFF
+
+;--- input.txt
+SET [[42]]
+DATA: [[42]]
+
+;--- check.txt
+; CHECK: SET [[INNER:.*]]
+; CHECK: DATA: WRONG_VALUE
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: SET {{\[\[}}42{{\]\]}}
+; DIFF-NEXT: -DATA: WRONG_VALUE
+; DIFF-NEXT: +DATA: {{\[\[}}42{{\]\]}}
diff --git a/llvm/test/FileCheck/diff/diff-numeric-expr.txt b/llvm/test/FileCheck/diff/diff-numeric-expr.txt
new file mode 100644
index 0000000000000..36cfe98bea0d0
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-numeric-expr.txt
@@ -0,0 +1,16 @@
+; RUN: split-file %s %t
+; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
+; RUN: | FileCheck %s --check-prefix=DIFF
+
+;--- input.txt
+BASE: 10
+NEXT: 12
+
+;--- check.txt
+; CHECK: BASE: [[#VAL:]]
+; CHECK: NEXT: [[#VAL+1]]
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: BASE: 10
+; DIFF-NEXT: -NEXT: 11
+; DIFF-NEXT: +NEXT: 12
diff --git a/llvm/test/FileCheck/diff/diff-resync-after-noise.txt b/llvm/test/FileCheck/diff/diff-resync-after-noise.txt
index 854ee188b2944..8fd4d73b9e83d 100644
--- a/llvm/test/FileCheck/diff/diff-resync-after-noise.txt
+++ b/llvm/test/FileCheck/diff/diff-resync-after-noise.txt
@@ -1,6 +1,8 @@
; RUN: split-file %s %t
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
-; RUN: | FileCheck %s --check-prefix=DIFF
+; RUN: | FileCheck %s --check-prefix=UNI
+; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=split 2>&1 \
+; RUN: | FileCheck %s --check-prefix=SPLIT
;--- input.txt
HEADER
@@ -23,8 +25,13 @@ Noise6
; CHECK-NEXT: WRONG_NEXT
; CHECK: END_OF_FUNCTION
-; DIFF: @@ -[[#]] +[[#]] @@
-; DIFF-NEXT: MATCH_ME
-; DIFF-NEXT: -WRONG_NEXT
-; DIFF-NEXT: +NEXT_LINE
-; DIFF-NEXT: Noise5
+; UNI: @@ -[[#]] +[[#]] @@
+; UNI-NEXT: MATCH_ME
+; UNI-NEXT: -WRONG_NEXT
+; UNI-NEXT: +NEXT_LINE
+; UNI-NEXT: Noise5
+
+; SPLIT: @@ -[[#]] +[[#]] @@
+; SPLIT-NEXT: MATCH_ME | MATCH_ME
+; SPLIT-NEXT: {{.*}}WRONG_NEXT{{.*}}!{{.*}}NEXT_LINE
+; SPLIT-NEXT: Noise5 | Noise5
diff --git a/llvm/test/FileCheck/diff/diff-substitutions.txt b/llvm/test/FileCheck/diff/diff-substitutions.txt
new file mode 100644
index 0000000000000..11c8a68484f1f
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-substitutions.txt
@@ -0,0 +1,23 @@
+; RUN: split-file %s %t
+; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
+; RUN: | FileCheck %s --check-prefix=SUBST
+; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff-no-substitutions 2>&1 \
+; RUN: | FileCheck %s --check-prefix=RAW
+
+;--- input.txt
+START 42
+FAIL 100
+
+;--- check.txt
+; CHECK: START [[VAL:[0-9]+]]
+; CHECK: FAIL [[VAL]]
+
+; SUBST: @@ -[[#]] +2 @@
+; SUBST-NEXT: START 42
+; SUBST-NEXT: -FAIL 42
+; SUBST-NEXT: +FAIL 100
+
+; RAW: @@ -[[#]] +2 @@
+; RAW-NEXT: START 42
+; RAW-NEXT: -FAIL {{\[\[VAL\]\]}}
+; RAW-NEXT: +FAIL 100
diff --git a/llvm/test/FileCheck/diff/diff-variable-chain-subs.txt b/llvm/test/FileCheck/diff/diff-variable-chain-subs.txt
new file mode 100644
index 0000000000000..4e172a4f28789
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-variable-chain-subs.txt
@@ -0,0 +1,18 @@
+; RUN: split-file %s %t
+; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
+; RUN: | FileCheck %s --check-prefix=DIFF
+
+;--- input.txt
+ID: 0x100
+TYPE: apple
+USE: 0x100 (orange)
+
+;--- check.txt
+; CHECK: ID: [[ID:0x[0-9a-f]+]]
+; CHECK: TYPE: [[TYPE:[a-z]+]]
+; CHECK: USE: [[ID]] ([[TYPE]])
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: TYPE: apple
+; DIFF-NEXT: -USE: 0x100 (apple)
+; DIFF-NEXT: +USE: 0x100 (orange)
diff --git a/llvm/utils/FileCheck/FileCheck.cpp b/llvm/utils/FileCheck/FileCheck.cpp
index f200e94becaa2..bfb1e541d57ed 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -118,7 +118,12 @@ static cl::opt<DiffFormatType> DiffFormat(
cl::ValueOptional,
cl::values(clEnumValN(None, "none", "Standard FileCheck diagnostics"),
clEnumValN(Unified, "unidiff", "Outputs a Unified diff"),
- clEnumValN(Unified, "", "")));
+ clEnumValN(Unified, "", ""),
+ clEnumValN(Split, "split", "Outputs a Split diff"),
+ clEnumValN(UnifiedNoSubstitution, "unidiff-no-substitutions",
+ "Outputs a Unified diff with no substitutions"),
+ clEnumValN(SplitNoSubstitution, "split-no-substitutions",
+ "Outputs a Split diff with no substitutions")));
// The order of DumpInputValue members affects their precedence, as documented
// for -dump-input below.
>From 7a488fb616d7987e0eb15f0e814fb181ede762e8 Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Sat, 18 Apr 2026 17:21:14 +0530
Subject: [PATCH 2/2] [FileCheck] Add check-not support for diff option
---
llvm/lib/FileCheck/FileCheck.cpp | 71 +++++++++++++++++++--
llvm/test/FileCheck/diff/diff-check-not.txt | 23 +++++++
2 files changed, 88 insertions(+), 6 deletions(-)
create mode 100644 llvm/test/FileCheck/diff/diff-check-not.txt
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index 243ded6362234..76eae4b60cc7c 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -2032,6 +2032,11 @@ bool FileCheck::readCheckFile(
return false;
}
+static bool printDiff(DiffFormatType Mode, const Pattern &Pat,
+ StringRef ActualLine, SourceMgr &SM,
+ std::vector<FileCheckDiag> *Diags, bool &HeaderPrinted,
+ unsigned OverwriteActualLine = 0);
+
/// Returns either (1) \c ErrorSuccess if there was no error or (2)
/// \c ErrorReported if an error was reported, such as an unexpected match.
static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
@@ -2061,6 +2066,28 @@ static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
SMRange MatchRange = ProcessMatchResult(MatchTy, SM, Loc, Pat.getCheckTy(),
Buffer, MatchResult.TheMatch->Pos,
MatchResult.TheMatch->Len, Diags);
+
+ if (!ExpectedMatch && Req.DiffFormat != DiffFormatType::None) {
+ size_t Pos = MatchResult.TheMatch->Pos;
+ // Extract full line containing the match
+ size_t LineStart = Buffer.rfind('\n', Pos);
+ LineStart = (LineStart == StringRef::npos) ? 0 : LineStart + 1;
+
+ size_t LineEnd = Buffer.find('\n', Pos);
+ LineEnd = (LineEnd == StringRef::npos) ? Buffer.size() : LineEnd;
+
+ StringRef ActualLine = Buffer.substr(LineStart, LineEnd - LineStart);
+
+ bool DummyHeaderPrinted = false;
+ printDiff(Req.DiffFormat, Pat, ActualLine, const_cast<SourceMgr &>(SM),
+ Diags, DummyHeaderPrinted);
+
+ handleAllErrors(std::move(MatchResult.TheError),
+ [](const ErrorInfoBase &) {});
+
+ return ErrorReported::reportedOrSuccess(true);
+ }
+
if (Diags) {
Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, Diags, Req);
Pat.printVariableDefs(SM, MatchTy, Diags);
@@ -2895,11 +2922,29 @@ static void renderDiff(DiffFormatType Mode, unsigned ExpectedLineNo,
}
}
-static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
+static bool printDiff(DiffFormatType Mode, const Pattern &Pat,
StringRef ActualLine, SourceMgr &SM,
- std::vector<FileCheckDiag> *Diags,
- unsigned OverwriteActualLine = 0) {
- SMLoc PatternLoc = CheckStr.Pat.getLoc();
+ std::vector<FileCheckDiag> *Diags, bool &HeaderPrinted,
+ unsigned OverwriteActualLine) {
+ // Print headers once per file.
+ if (!HeaderPrinted) {
+ StringRef CheckFile =
+ SM.getMemoryBuffer(SM.getMainFileID())->getBufferIdentifier();
+
+ unsigned InputBufID =
+ SM.FindBufferContainingLoc(SMLoc::getFromPointer(ActualLine.data()));
+ StringRef InputFile = SM.getMemoryBuffer(InputBufID)->getBufferIdentifier();
+
+ auto &OS = llvm::errs();
+ OS.changeColor(raw_ostream::WHITE, true);
+ OS << "--- " << CheckFile << "\n";
+ OS << "+++ " << InputFile << "\n";
+ OS.resetColor();
+
+ HeaderPrinted = true;
+ }
+
+ SMLoc PatternLoc = Pat.getLoc();
unsigned ExpectedLineNo = SM.getLineAndColumn(PatternLoc).first;
const char *PatPtr = PatternLoc.getPointer();
StringRef ExpectedLine = StringRef(PatPtr).split('\n').first.rtrim();
@@ -2909,7 +2954,7 @@ static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
Mode == DiffFormatType::UnifiedNoSubstitution)
ExpectedText = ExpectedLine.str();
else
- ExpectedText = CheckStr.Pat.getSubstitutedRegex(ExpectedLine);
+ ExpectedText = Pat.getSubstitutedRegex(ExpectedLine);
// Resolve the Actual (Input) line number.
// Priority: 1. OverwriteActualLine (Found via Fuzzy match)
@@ -2987,7 +3032,8 @@ static bool handleDiffFailure(const FileCheckString &CheckStr,
TargetLineNo = SM.getLineAndColumn(CurrentLoc).first;
}
- printDiff(Req.DiffFormat, CheckStr, TargetLine, SM, Diags, TargetLineNo);
+ printDiff(Req.DiffFormat, CheckStr.Pat, TargetLine, SM, Diags, HeaderPrinted,
+ TargetLineNo);
TotalMismatches++;
// Advance CheckRegion past the current line to recover for the next CHECK.
@@ -3043,6 +3089,8 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
for (; i != j; ++i) {
const FileCheckString &CheckStr = CheckStrings[i];
+ size_t OldDiagSize = Diags ? Diags->size() : 0;
+
bool IsStrict = CheckStr.Pat.getCheckTy() == Check::CheckNext ||
CheckStr.Pat.getCheckTy() == Check::CheckEmpty;
@@ -3050,6 +3098,17 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
size_t MatchPos =
CheckStr.Check(SM, CheckRegion, false, MatchLen, Req, Diags);
+ if (Diags && Diags->size() > OldDiagSize) {
+ // Check if the last diagnostic was a forbidden string match
+ if (Diags->back().MatchTy == FileCheckDiag::MatchFoundButExcluded) {
+ ChecksFailed = true;
+ TotalMismatches++;
+ HeaderPrinted = true;
+ i = j;
+ break;
+ }
+ }
+
// Handle failure
if (MatchPos == StringRef::npos) {
if (IsDiffFormat) {
diff --git a/llvm/test/FileCheck/diff/diff-check-not.txt b/llvm/test/FileCheck/diff/diff-check-not.txt
new file mode 100644
index 0000000000000..7e25d0adec787
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-check-not.txt
@@ -0,0 +1,23 @@
+; RUN: split-file %s %t
+; RUN: not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
+; RUN: | FileCheck %s --check-prefix=DIFF
+
+;--- input.txt
+START
+This is a forbidden string
+END
+
+;--- check.txt
+START
+; CHECK-NOT: forbidden string
+END
+
+; DIFF: --- {{.*}}check.txt
+; DIFF-NEXT: +++ {{.*}}input.txt
+; DIFF-NEXT: @@ -2 +2 @@
+; DIFF-NEXT: START
+; DIFF-NEXT: -forbidden string
+; DIFF-NEXT: +This is a forbidden string
+; DIFF-NEXT: END
+; DIFF-EMPTY:
+; DIFF-NEXT: FileCheck: Found 1 unique textual mismatch.
More information about the llvm-commits
mailing list