[llvm] [FileCheck] Add check-not support for diff option (PR #189357)
Shivam Gupta via llvm-commits
llvm-commits at lists.llvm.org
Mon Mar 30 04:11:09 PDT 2026
https://github.com/xgupta updated https://github.com/llvm/llvm-project/pull/189357
>From 327c81350c5f4f6cb4e42c8ccec9bc63ddecc703 Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Sun, 15 Mar 2026 02:01:51 +0530
Subject: [PATCH 01/16] [FileCheck] Add a diff output option for FileCheck
This patch introduces a --diff option to FileCheck to address the
readability issues highlighted in issue #77257. Traditional FileCheck
output can be difficult to parse, especially when dealing with
multiple substitutions or large input files with many mismatches
and additional context. This feature provides a more familiar,
scannable format for developers by rendering mismatches as diffs.
Fixes #77257
Assisted by Gemini AI
---
llvm/docs/CommandGuide/FileCheck.rst | 14 +
llvm/include/llvm/FileCheck/FileCheck.h | 10 +
llvm/lib/FileCheck/FileCheck.cpp | 454 ++++++++++++++++--
llvm/lib/FileCheck/FileCheckImpl.h | 11 +-
llvm/test/FileCheck/diff/diff-complex.txt | 13 +
.../test/FileCheck/diff/diff-fuzzy-lables.txt | 14 +
llvm/test/FileCheck/diff/diff-helloworld.txt | 8 +
.../FileCheck/diff/diff-label-boundary.txt | 12 +
llvm/test/FileCheck/diff/diff-labels.txt | 11 +
llvm/test/FileCheck/diff/diff-large.txt | 12 +
llvm/test/FileCheck/diff/diff-long-line.txt | 7 +
llvm/test/FileCheck/diff/diff-mid.txt | 13 +
.../test/FileCheck/diff/diff-missing-line.txt | 11 +
llvm/test/FileCheck/diff/diff-multi-subs.txt | 9 +
llvm/test/FileCheck/diff/diff-multihunk.txt | 13 +
llvm/test/FileCheck/diff/diff-nested-vars.txt | 10 +
llvm/test/FileCheck/diff/diff-next.txt | 11 +
llvm/test/FileCheck/diff/diff-noise.txt | 26 +
.../diff/diff-numeric-expression.txt | 10 +
llvm/test/FileCheck/diff/diff-overlap.txt | 15 +
llvm/test/FileCheck/diff/diff-resync.txt | 13 +
.../FileCheck/diff/diff-substitutions.txt | 16 +
.../FileCheck/diff/diff-variable-chain.txt | 11 +
llvm/utils/FileCheck/FileCheck.cpp | 16 +-
24 files changed, 705 insertions(+), 35 deletions(-)
create mode 100644 llvm/test/FileCheck/diff/diff-complex.txt
create mode 100644 llvm/test/FileCheck/diff/diff-fuzzy-lables.txt
create mode 100644 llvm/test/FileCheck/diff/diff-helloworld.txt
create mode 100644 llvm/test/FileCheck/diff/diff-label-boundary.txt
create mode 100644 llvm/test/FileCheck/diff/diff-labels.txt
create mode 100644 llvm/test/FileCheck/diff/diff-large.txt
create mode 100644 llvm/test/FileCheck/diff/diff-long-line.txt
create mode 100644 llvm/test/FileCheck/diff/diff-mid.txt
create mode 100644 llvm/test/FileCheck/diff/diff-missing-line.txt
create mode 100644 llvm/test/FileCheck/diff/diff-multi-subs.txt
create mode 100644 llvm/test/FileCheck/diff/diff-multihunk.txt
create mode 100644 llvm/test/FileCheck/diff/diff-nested-vars.txt
create mode 100644 llvm/test/FileCheck/diff/diff-next.txt
create mode 100644 llvm/test/FileCheck/diff/diff-noise.txt
create mode 100644 llvm/test/FileCheck/diff/diff-numeric-expression.txt
create mode 100644 llvm/test/FileCheck/diff/diff-overlap.txt
create mode 100644 llvm/test/FileCheck/diff/diff-resync.txt
create mode 100644 llvm/test/FileCheck/diff/diff-substitutions.txt
create mode 100644 llvm/test/FileCheck/diff/diff-variable-chain.txt
diff --git a/llvm/docs/CommandGuide/FileCheck.rst b/llvm/docs/CommandGuide/FileCheck.rst
index 22101d3ef2ac7..c02613e440ac2 100644
--- a/llvm/docs/CommandGuide/FileCheck.rst
+++ b/llvm/docs/CommandGuide/FileCheck.rst
@@ -110,6 +110,20 @@ and from the command line.
-verify``. With this option FileCheck will verify that input does not contain
warnings not covered by any ``CHECK:`` patterns.
+.. option:: --diff <value>
+
+ Controls how mismatches between the check patterns and the input are
+ reported. The default is ``standard``, which uses the standard FileCheck
+ diagnostic output.
+
+ * ``standard`` – Use the standard FileCheck diagnostic messages.
+ * ``split`` – Display mismatches using a side-by-side split diff view.
+ * ``unidiff`` – Display mismatches using a unified diff format.
+ * ``split-no-substitutions`` – Same as ``split`` but shows the raw
+ pattern without applying variable substitutions.
+ * ``unidiff-no-substitutions`` – Same as ``unidiff`` but shows the raw
+ pattern without applying variable substitutions.
+
.. option:: --dump-input <value>
Dump input to stderr, adding annotations representing currently enabled
diff --git a/llvm/include/llvm/FileCheck/FileCheck.h b/llvm/include/llvm/FileCheck/FileCheck.h
index b44ed8ed3f839..b8db1194d7340 100644
--- a/llvm/include/llvm/FileCheck/FileCheck.h
+++ b/llvm/include/llvm/FileCheck/FileCheck.h
@@ -27,6 +27,15 @@ class MemoryBuffer;
class SourceMgr;
template <typename T> class SmallVectorImpl;
+// Diff the output on failures.
+enum DiffFormatType {
+ Standard,
+ Split,
+ Unified,
+ SplitNoSubstitution,
+ UnifiedNoSubstitution
+};
+
/// Contains info about various FileCheck options.
struct FileCheckRequest {
std::vector<StringRef> CheckPrefixes;
@@ -43,6 +52,7 @@ struct FileCheckRequest {
bool AllowDeprecatedDagOverlap = false;
bool Verbose = false;
bool VerboseVerbose = false;
+ DiffFormatType DiffMode = DiffFormatType::Standard;
};
namespace Check {
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index d50e5d9cb088b..ea2a5684ddd3d 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -20,6 +20,7 @@
#include "llvm/ADT/StringSet.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/WithColor.h"
#include <cstdint>
#include <list>
#include <set>
@@ -1261,7 +1262,8 @@ unsigned Pattern::computeMatchDistance(StringRef Buffer) const {
void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
SMRange Range,
FileCheckDiag::MatchType MatchTy,
- std::vector<FileCheckDiag> *Diags) const {
+ std::vector<FileCheckDiag> *Diags,
+ const FileCheckRequest &Req) const {
// Print what we know about substitutions.
if (!Substitutions.empty()) {
for (const auto &Substitution : Substitutions) {
@@ -1287,8 +1289,10 @@ void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
if (Diags)
Diags->emplace_back(SM, CheckTy, getLoc(), MatchTy,
SMRange(Range.Start, Range.Start), OS.str());
- else
- SM.PrintMessage(Range.Start, SourceMgr::DK_Note, OS.str());
+ else {
+ if (Req.DiffMode == DiffFormatType::Standard)
+ SM.PrintMessage(Range.Start, SourceMgr::DK_Note, OS.str());
+ }
}
}
}
@@ -1368,7 +1372,8 @@ static SMRange ProcessMatchResult(FileCheckDiag::MatchType MatchTy,
}
void Pattern::printFuzzyMatch(const SourceMgr &SM, StringRef Buffer,
- std::vector<FileCheckDiag> *Diags) const {
+ std::vector<FileCheckDiag> *Diags,
+ const FileCheckRequest &Req) const {
// Attempt to find the closest/best fuzzy match. Usually an error happens
// because some string in the output didn't exactly match. In these cases, we
// would like to show the user a best guess at what "should have" matched, to
@@ -1411,8 +1416,9 @@ void Pattern::printFuzzyMatch(const SourceMgr &SM, StringRef Buffer,
SMRange MatchRange =
ProcessMatchResult(FileCheckDiag::MatchFuzzy, SM, getLoc(),
getCheckTy(), Buffer, Best, 0, Diags);
- SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note,
- "possible intended match here");
+ if (Req.DiffMode == DiffFormatType::Standard)
+ SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note,
+ "possible intended match here");
// FIXME: If we wanted to be really friendly we would show why the match
// failed, as it can be hard to spot simple one character differences.
@@ -2057,7 +2063,7 @@ static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
Buffer, MatchResult.TheMatch->Pos,
MatchResult.TheMatch->Len, Diags);
if (Diags) {
- Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, Diags);
+ Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, Diags, Req);
Pat.printVariableDefs(SM, MatchTy, Diags);
}
if (!PrintDiag) {
@@ -2078,7 +2084,7 @@ static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
{MatchRange});
// Print additional information, which can be useful even if there are errors.
- Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, nullptr);
+ Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, nullptr, Req);
Pat.printVariableDefs(SM, MatchTy, nullptr);
// Print errors and add them to Diags. We report these errors after the match
@@ -2102,7 +2108,7 @@ static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM,
StringRef Prefix, SMLoc Loc, const Pattern &Pat,
int MatchedCount, StringRef Buffer, Error MatchError,
- bool VerboseVerbose,
+ bool VerboseVerbose, const FileCheckRequest &Req,
std::vector<FileCheckDiag> *Diags) {
// Print any pattern errors, and record them to be added to Diags later.
bool HasError = ExpectedMatch;
@@ -2148,7 +2154,7 @@ static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM,
for (StringRef ErrorMsg : ErrorMsgs)
Diags->emplace_back(SM, Pat.getCheckTy(), Loc, MatchTy, NoteRange,
ErrorMsg);
- Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, Diags);
+ Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, Diags, Req);
}
if (!PrintDiag) {
assert(!HasError && "expected to report more diagnostics for error");
@@ -2165,18 +2171,20 @@ static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM,
if (Pat.getCount() > 1)
Message +=
formatv(" ({0} out of {1})", MatchedCount, Pat.getCount()).str();
- SM.PrintMessage(Loc,
- ExpectedMatch ? SourceMgr::DK_Error : SourceMgr::DK_Remark,
- Message);
- SM.PrintMessage(SearchRange.Start, SourceMgr::DK_Note,
- "scanning from here");
+ if (Req.DiffMode == DiffFormatType::Standard) {
+ SM.PrintMessage(
+ Loc, ExpectedMatch ? SourceMgr::DK_Error : SourceMgr::DK_Remark,
+ Message);
+ SM.PrintMessage(SearchRange.Start, SourceMgr::DK_Note,
+ "scanning from here");
+ }
}
// Print additional information, which can be useful even after a pattern
// error.
- Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, nullptr);
+ Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, nullptr, Req);
if (ExpectedMatch)
- Pat.printFuzzyMatch(SM, Buffer, Diags);
+ Pat.printFuzzyMatch(SM, Buffer, Diags, Req);
return ErrorReported::reportedOrSuccess(HasError);
}
@@ -2192,7 +2200,7 @@ static Error reportMatchResult(bool ExpectedMatch, const SourceMgr &SM,
return printMatch(ExpectedMatch, SM, Prefix, Loc, Pat, MatchedCount, Buffer,
std::move(MatchResult), Req, Diags);
return printNoMatch(ExpectedMatch, SM, Prefix, Loc, Pat, MatchedCount, Buffer,
- std::move(MatchResult.TheError), Req.VerboseVerbose,
+ std::move(MatchResult.TheError), Req.VerboseVerbose, Req,
Diags);
}
@@ -2275,7 +2283,7 @@ size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer,
// If this check is a "CHECK-NEXT", verify that the previous match was on
// the previous line (i.e. that there is one newline between them).
- if (CheckNext(SM, SkippedRegion)) {
+ if (CheckNext(SM, SkippedRegion, Req)) {
ProcessMatchResult(FileCheckDiag::MatchFoundButWrongLine, SM, Loc,
Pat.getCheckTy(), MatchBuffer, MatchPos, MatchLen,
Diags, Req.Verbose);
@@ -2300,7 +2308,8 @@ size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer,
return FirstMatchPos;
}
-bool FileCheckString::CheckNext(const SourceMgr &SM, StringRef Buffer) const {
+bool FileCheckString::CheckNext(const SourceMgr &SM, StringRef Buffer,
+ const FileCheckRequest &Req) const {
if (Pat.getCheckTy() != Check::CheckNext &&
Pat.getCheckTy() != Check::CheckEmpty)
return false;
@@ -2312,8 +2321,7 @@ bool FileCheckString::CheckNext(const SourceMgr &SM, StringRef Buffer) const {
// Count the number of newlines between the previous match and this one.
const char *FirstNewLine = nullptr;
unsigned NumNewLines = CountNumNewlinesBetween(Buffer, FirstNewLine);
-
- if (NumNewLines == 0) {
+ if (NumNewLines == 0 && Req.DiffMode == DiffFormatType::Standard) {
SM.PrintMessage(Loc, SourceMgr::DK_Error,
CheckName + ": is on the same line as previous match");
SM.PrintMessage(SMLoc::getFromPointer(Buffer.end()), SourceMgr::DK_Note,
@@ -2323,7 +2331,7 @@ bool FileCheckString::CheckNext(const SourceMgr &SM, StringRef Buffer) const {
return true;
}
- if (NumNewLines != 1) {
+ if (NumNewLines != 1 && Req.DiffMode == DiffFormatType::Standard) {
SM.PrintMessage(Loc, SourceMgr::DK_Error,
CheckName +
": is not on the line after the previous match");
@@ -2724,9 +2732,361 @@ void FileCheckPatternContext::clearLocalVars() {
GlobalNumericVariableTable.erase(Var);
}
+/// Extract a fixed-width "window" from string \p S, centered around \p DiffPos.
+///
+/// This is used for side-by-side diffing of long lines (e.g., metadata
+/// or long symbol names). It ensures that the "point of divergence" is visible
+/// within the provided \p Width. If the string is truncated, ellipses (...)
+/// are inserted at the boundaries.
+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 (S.size() > Start + Width && View.size() > 3)
+ View.replace(View.size() - 3, 3, "...");
+
+ return View;
+}
+
+struct DiffContext {
+ StringRef Line;
+ StringRef LineBefore;
+ StringRef LineAfter;
+};
+
+/// Populates a \c DiffContext by fetching the line at \p LineNo
+/// as well as the lines before and after it from the \p SourceMgr. This
+/// provides the "surrounding context" seen in standard diff tools.
+static DiffContext getDiffContext(SourceMgr &SM, unsigned LineNo,
+ unsigned BufID) {
+ const MemoryBuffer *Buffer = SM.getMemoryBuffer(BufID);
+ StringRef BufText = Buffer->getBuffer();
+
+ /// Helper lambda to safely extract a single line's text from the buffer.
+ auto getLineText = [&](unsigned L) -> StringRef {
+ if (L == 0)
+ return "";
+
+ SMLoc LineLoc = SM.FindLocForLineAndColumn(BufID, L, 1);
+ if (!LineLoc.isValid())
+ return "";
+
+ const char *Ptr = LineLoc.getPointer();
+ const char *End = BufText.end();
+ const char *LineEnd = Ptr;
+
+ while (LineEnd < End && *LineEnd != '\n' && *LineEnd != '\r')
+ LineEnd++;
+
+ return StringRef(Ptr, LineEnd - Ptr).trim();
+ };
+
+ return {getLineText(LineNo), getLineText(LineNo - 1),
+ getLineText(LineNo + 1)};
+}
+
+/// Converts a raw pattern string (e.g., "val [[VAL]]") into a
+/// human-readable diagnostic string (e.g., "val 42") by replacing
+/// "[[...]]" tags with the values stored in the \c Substitutions vector.
+std::string Pattern::getSubstitutedRegex(StringRef PatternText) const {
+ std::string Result = PatternText.str();
+
+ // We iterate through substitutions. Since they are stored in the order
+ // they appear in the pattern, we can 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 to \c llvm::errs().
+///
+/// Supports two modes:
+/// 1. Split View: A side-by-side comparison (Expected | Actual) using a
+/// sliding window centered on the first difference.
+/// 2. Unified View: A standard top-to-bottom (-Expected / +Actual) format.
+static void renderDiff(DiffFormatType Mode, unsigned ExpectedLineNo,
+ unsigned ActualLineNo, StringRef ActualLine,
+ const std::string &ExpectedText, const DiffContext &Ctx,
+ unsigned OverwriteActualLine) {
+ auto &OS = llvm::errs();
+
+ constexpr unsigned ColWidth = 45;
+ constexpr StringRef Sep = " | ";
+
+ bool IsSplit = (Mode == Split || Mode == SplitNoSubstitution);
+
+ // Identify the first index where the expected and actual text diverge.
+ size_t DiffPos = 0;
+ size_t MinLen = std::min(ExpectedText.size(), ActualLine.size());
+ while (DiffPos < MinLen && ExpectedText[DiffPos] == ActualLine[DiffPos])
+ ++DiffPos;
+
+ auto GetView = [&](StringRef S) {
+ return getCenteredView(S, DiffPos, ColWidth - 1);
+ };
+
+ // Header
+ OS.changeColor(raw_ostream::CYAN);
+ if (IsSplit)
+ OS << "@@ L:" << ExpectedLineNo << " R:" << ActualLineNo << " @@\n";
+ else
+ OS << "@@ -" << ExpectedLineNo << " +" << ActualLineNo << " @@\n";
+ OS.resetColor();
+
+ // Before Context
+ if (!Ctx.LineBefore.empty()) {
+ if (IsSplit)
+ OS << " " << GetView(Ctx.LineBefore) << Sep << GetView(Ctx.LineBefore)
+ << "\n";
+ else
+ OS << " " << Ctx.LineBefore << "\n";
+ }
+
+ // Mismatch
+ if (IsSplit) {
+ OS << " ";
+
+ OS.changeColor(raw_ostream::RED);
+ OS << GetView(ExpectedText);
+ OS.resetColor();
+
+ bool IsWrongLine = (OverwriteActualLine != 0);
+
+ if (IsWrongLine) {
+ // Use a bold yellow '!' to signify that the text might match,
+ // but it was found on the wrong line.
+ OS.changeColor(raw_ostream::YELLOW, true);
+ OS << " ! ";
+ OS.resetColor();
+ } else {
+ OS << " | ";
+ }
+
+ OS.changeColor(raw_ostream::GREEN);
+ OS << GetView(ActualLine) << "\n";
+ OS.resetColor();
+
+ } else {
+ OS.changeColor(raw_ostream::RED);
+ OS << "-" << ExpectedText << "\n";
+
+ OS.changeColor(raw_ostream::GREEN);
+ OS << "+" << ActualLine << "\n";
+ OS.resetColor();
+ }
+
+ // After Context
+ if (!Ctx.LineAfter.empty()) {
+ if (IsSplit)
+ OS << " " << GetView(Ctx.LineAfter) << Sep << GetView(Ctx.LineAfter)
+ << "\n";
+ else
+ OS << " " << Ctx.LineAfter << "\n";
+ }
+}
+
+/// Prepares and prints a visual comparison between a CHECK pattern and the
+/// input.
+///
+/// This function acts as a bridge between the FileCheck engine and the diff
+/// renderer. It resolves the line numbers for both the pattern (Expected) and
+/// the input (Actual), performs variable substitution if requested, and fetches
+/// the surrounding context lines.
+static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
+ StringRef CheckRegion, SourceMgr &SM,
+ const FileCheckRequest &Req,
+ std::vector<FileCheckDiag> *Diags,
+ unsigned OverwriteActualLine = 0) {
+ StringRef ActualLine = CheckRegion.split('\n').first;
+ if (CheckRegion.empty()) {
+ ActualLine = "<EOF>";
+ }
+
+ SMLoc PatternLoc = CheckStr.Pat.getLoc();
+ unsigned ExpectedLineNo = SM.getLineAndColumn(PatternLoc).first;
+ const char *PatPtr = PatternLoc.getPointer();
+ StringRef FullExpectedLine = StringRef(PatPtr).split('\n').first.trim();
+
+ std::string ExpectedText;
+ if (Mode == DiffFormatType::SplitNoSubstitution ||
+ Mode == DiffFormatType::UnifiedNoSubstitution)
+ ExpectedText = FullExpectedLine.str(); // Raw pattern: [[VAR]]
+ else
+ ExpectedText = CheckStr.Pat.getSubstitutedRegex(
+ FullExpectedLine); // Substituted: value
+
+ // Resolve the Actual (Input) line number.
+ // Priority: 1. OverwriteActualLine (explicit override)
+ // 2. Fuzzy Match Diag (where FileCheck 'thinks' the line was)
+ // 3. Direct pointer resolution via SourceMgr.
+ SMLoc InputLoc = SMLoc::getFromPointer(CheckRegion.data());
+ unsigned ActualLineNo = OverwriteActualLine;
+
+ if (ActualLineNo == 0) {
+ ActualLineNo = SM.getLineAndColumn(InputLoc).first;
+ bool FoundFuzzy = false;
+
+ // Search backward to find most recent fuzzy match for this pattern.
+ if (Diags) {
+ for (const auto &D : llvm::reverse(*Diags)) {
+ if (D.CheckLoc == PatternLoc &&
+ D.MatchTy == FileCheckDiag::MatchFuzzy) {
+ ActualLineNo = D.InputStartLine;
+ FoundFuzzy = true;
+ break;
+ }
+ }
+ }
+ // If no diagnostic match was found, calculate the line number directly
+ // from the InputLoc pointer using the SourceManager.
+ if (!FoundFuzzy)
+ ActualLineNo = SM.getLineAndColumn(InputLoc).first;
+
+ // if we are at an empty line, usually the relevant context is the line just
+ // before it.
+ if (ActualLine.empty() && ActualLineNo > 1)
+ ActualLineNo--;
+ }
+
+ // Gather contextual diff to print (a line above and a line below).
+ unsigned BufID = SM.FindBufferContainingLoc(InputLoc);
+ DiffContext Context = getDiffContext(SM, ActualLineNo, BufID);
+
+ renderDiff(Mode, ExpectedLineNo, ActualLineNo, ActualLine, ExpectedText,
+ Context, OverwriteActualLine);
+
+ llvm::errs() << "\n";
+ return true;
+}
+
+/// Handles a mismatch by attempting to resynchronize the pattern with the
+/// input.
+///
+/// When a pattern fails to match at the current location, this function
+/// explores several recovery strategies:
+/// 1. Stray Line Detection: Check if the pattern matches perfectly on the very
+/// next line (suggesting an unexpected line was inserted).
+/// 2. Search/Resync: Search forward in the region to find a perfect match
+/// later on (suggesting a block of unexpected code).
+/// 3. Fuzzy Matching: Use existing diagnostics to find "near misses" (typos)
+/// on the current or nearby lines.
+///
+/// It then calls \c renderDiff via \c printDiff and advances \p CheckRegion
+/// to the appropriate recovery point
+static bool handleDiffFailure(const FileCheckString &CheckStr,
+ StringRef &CheckRegion, SourceMgr &SM,
+ FileCheckRequest &Req,
+ std::vector<FileCheckDiag> *Diags,
+ raw_ostream &OS, bool &HeaderPrinted,
+ unsigned &TotalMismatches) {
+ CheckRegion = CheckRegion.ltrim("\n\r");
+ if (CheckRegion.empty())
+ return false;
+
+ SMLoc CurrentLoc = SMLoc::getFromPointer(CheckRegion.data());
+ unsigned CurrentLineNo = SM.getLineAndColumn(CurrentLoc).first;
+ size_t EOL = CheckRegion.find('\n');
+ StringRef MismatchLine =
+ (EOL == StringRef::npos) ? CheckRegion : CheckRegion.substr(0, EOL);
+
+ // Try to find where this pattern actually appears next
+ std::vector<FileCheckDiag> ResyncDiags;
+ size_t ResyncMatchLen = 0;
+ size_t ResyncPos = CheckStr.Check(SM, CheckRegion, /*Search=*/true,
+ ResyncMatchLen, Req, &ResyncDiags);
+
+ StringRef TargetLine = MismatchLine;
+ unsigned TargetLineNo = CurrentLineNo;
+ bool AdvanceToResync = false;
+
+ if (ResyncPos == 0) {
+ // Perfect match right here (usually triggered by CHECK-NEXT noise)
+ TargetLine = MismatchLine;
+ } else if (ResyncPos != StringRef::npos) {
+ // Found it further down. Is it just one line away? (Stray line case)
+ size_t NextLinePos = CheckRegion.find('\n');
+ if (NextLinePos != StringRef::npos && ResyncPos == NextLinePos + 1) {
+ // This is a stray line. Diff the expected vs the stray line, then skip
+ // stray.
+ TargetLine = MismatchLine;
+ AdvanceToResync = false;
+ } else if (CheckStr.Pat.getCheckTy() != llvm::Check::CheckNext) {
+ // Regular CHECK: skip the "noise" and sync to the match
+ SMLoc MatchLoc = SMLoc::getFromPointer(CheckRegion.data() + ResyncPos);
+ TargetLineNo = SM.getLineAndColumn(MatchLoc).first;
+ TargetLine = CheckRegion.substr(ResyncPos).split('\n').first;
+ AdvanceToResync = true;
+ }
+ } else {
+ // Fallback to Fuzzy matching if no perfect match exists
+ for (const auto &D : llvm::reverse(ResyncDiags)) {
+ if (D.MatchTy == FileCheckDiag::MatchFuzzy) {
+ TargetLineNo = D.InputStartLine;
+ SMLoc FuzzyLoc = SM.FindLocForLineAndColumn(
+ SM.FindBufferContainingLoc(CurrentLoc), TargetLineNo, 1);
+ TargetLine = StringRef(FuzzyLoc.getPointer()).split('\n').first.trim();
+ break;
+ }
+ }
+ }
+
+ printDiff(Req.DiffMode, CheckStr, TargetLine, SM, Req, &ResyncDiags,
+ TargetLineNo);
+ TotalMismatches++;
+
+ // Updates \p CheckRegion to advance the search pointer past the error.
+ if (AdvanceToResync && ResyncPos != StringRef::npos)
+ CheckRegion = CheckRegion.substr(ResyncPos + ResyncMatchLen);
+ else if (EOL != StringRef::npos)
+ CheckRegion = CheckRegion.substr(EOL + 1);
+ else
+ CheckRegion = "";
+
+ return true;
+}
+
bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
std::vector<FileCheckDiag> *Diags) {
bool ChecksFailed = false;
+ unsigned TotalMismatches = 0;
+ auto &OS = llvm::errs();
unsigned i = 0, j = 0, e = CheckStrings.size();
while (true) {
@@ -2759,21 +3119,55 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
if (i != 0 && Req.EnableVarScope)
PatternContext->clearLocalVars();
+ bool HeaderPrinted = false;
for (; i != j; ++i) {
const FileCheckString &CheckStr = CheckStrings[i];
// Check each string within the scanned region, including a second check
// of any final CHECK-LABEL (to verify CHECK-NOT and CHECK-DAG)
size_t MatchLen = 0;
- size_t MatchPos =
- CheckStr.Check(SM, CheckRegion, false, MatchLen, Req, Diags);
-
- if (MatchPos == StringRef::npos) {
- ChecksFailed = true;
- i = j;
- break;
+ size_t MatchPos = StringRef::npos;
+ // Determine if the pattern requires strict adjacency (CHECK-NEXT/EMPTY).
+ bool IsStrict = CheckStr.Pat.getCheckTy() == Check::CheckNext ||
+ CheckStr.Pat.getCheckTy() == Check::CheckEmpty;
+
+ if (Req.DiffMode != DiffFormatType::Standard) {
+ // Try to match the pattern (Search only if not strict)
+ MatchPos =
+ CheckStr.Check(SM, CheckRegion, !IsStrict, MatchLen, Req, Diags);
+ } else {
+ MatchPos = CheckStr.Check(SM, CheckRegion, false, MatchLen, Req, Diags);
}
+ if (Req.DiffMode != DiffFormatType::Standard) {
+ if (MatchPos == StringRef::npos) {
+ // Case 1: No match at all. Always diff.
+ handleDiffFailure(CheckStr, CheckRegion, SM, Req, Diags, OS,
+ HeaderPrinted, TotalMismatches);
+ ChecksFailed = true;
+ i = j;
+ break;
+ } else if (IsStrict && MatchPos > 0) {
+ // Case 2: Match found, but is it "Strictly" next?
+ // Check if the skipped characters are JUST whitespace/newlines.
+ StringRef Skipped = CheckRegion.slice(0, MatchPos);
+ if (!Skipped.trim().empty()) {
+ // There is actual text (noise) between the last match and this one.
+ handleDiffFailure(CheckStr, CheckRegion, SM, Req, Diags, OS,
+ HeaderPrinted, TotalMismatches);
+ ChecksFailed = true;
+ i = j;
+ break;
+ }
+ }
+ } else {
+ // Standard Mode Logic
+ if (MatchPos == StringRef::npos) {
+ ChecksFailed = true;
+ i = j;
+ break;
+ }
+ }
CheckRegion = CheckRegion.substr(MatchPos + MatchLen);
}
diff --git a/llvm/lib/FileCheck/FileCheckImpl.h b/llvm/lib/FileCheck/FileCheckImpl.h
index 5851cfc4b5d5c..5b47b87076638 100644
--- a/llvm/lib/FileCheck/FileCheckImpl.h
+++ b/llvm/lib/FileCheck/FileCheckImpl.h
@@ -734,9 +734,13 @@ class Pattern {
/// Prints the value of successful substitutions.
void printSubstitutions(const SourceMgr &SM, StringRef Buffer,
SMRange MatchRange, FileCheckDiag::MatchType MatchTy,
- std::vector<FileCheckDiag> *Diags) const;
+ std::vector<FileCheckDiag> *Diags,
+ const FileCheckRequest &Req) const;
void printFuzzyMatch(const SourceMgr &SM, StringRef Buffer,
- std::vector<FileCheckDiag> *Diags) const;
+ std::vector<FileCheckDiag> *Diags,
+ const FileCheckRequest &Req) const;
+
+ std::string getSubstitutedRegex(StringRef PatternText) const;
bool hasVariable() const {
return !(Substitutions.empty() && VariableDefs.empty());
@@ -874,7 +878,8 @@ struct FileCheckString {
/// Verifies that there is a single line in the given \p Buffer. Errors are
/// reported against \p SM.
- bool CheckNext(const SourceMgr &SM, StringRef Buffer) const;
+ bool CheckNext(const SourceMgr &SM, StringRef Buffer,
+ const FileCheckRequest &Req) const;
/// Verifies that there is no newline in the given \p Buffer. Errors are
/// reported against \p SM.
bool CheckSame(const SourceMgr &SM, StringRef Buffer) const;
diff --git a/llvm/test/FileCheck/diff/diff-complex.txt b/llvm/test/FileCheck/diff/diff-complex.txt
new file mode 100644
index 0000000000000..e4df50dbdf936
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-complex.txt
@@ -0,0 +1,13 @@
+; RUN: printf "HEADER\nNoise1\nNoise2\nNoise3\nNoise4\nTARGET_BLOCK\nStray1\nStray2\nMATCH_ME\nNEXT_LINE\nNoise5\nNoise6\n" > %t.input
+; RUN: not FileCheck --diff=unidiff --input-file %t.input %s --check-prefix=COMPLEX 2>&1 | FileCheck %s -check-prefix=DIFF
+
+; COMPLEX-LABEL: HEADER
+; COMPLEX: TARGET_BLOCK
+; COMPLEX: MATCH_ME
+; COMPLEX-NEXT: WRONG_NEXT
+; COMPLEX: END_OF_FUNCTION
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: MATCH_ME
+; DIFF-NEXT: -WRONG_NEXT
+; DIFF-NEXT: +NEXT_LINE
diff --git a/llvm/test/FileCheck/diff/diff-fuzzy-lables.txt b/llvm/test/FileCheck/diff/diff-fuzzy-lables.txt
new file mode 100644
index 0000000000000..2ba3dae29c3a6
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-fuzzy-lables.txt
@@ -0,0 +1,14 @@
+; RUN: printf "define void @foo() {\n call void @work_task()\n}\n" > %t.input
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
+
+; CHECK-LABEL: define void @foo()
+; CHECK: call void @work_test()
+
+; Need to fixed this
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF: define void @foo() {
+; DIFF-NEXT: -call void @work_test()
+; DIFF-NEXT: +call void @work_task()
+; DIFF-NEXT: }
+
diff --git a/llvm/test/FileCheck/diff/diff-helloworld.txt b/llvm/test/FileCheck/diff/diff-helloworld.txt
new file mode 100644
index 0000000000000..074c15bdcfe86
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-helloworld.txt
@@ -0,0 +1,8 @@
+; RUN: printf "hello world\n" > %t.input
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck -check-prefix=UNI-DIFF %s
+
+; CHECK: hello {{U}}niverse
+
+; UNI-DIFF: @@ -4 +1 @@
+; UNI-DIFF-NEXT: -hello {{[{][{]U[}][}]}}niverse
+; UNI-DIFF-NEXT: +hello world
diff --git a/llvm/test/FileCheck/diff/diff-label-boundary.txt b/llvm/test/FileCheck/diff/diff-label-boundary.txt
new file mode 100644
index 0000000000000..2d3cc114f254e
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-label-boundary.txt
@@ -0,0 +1,12 @@
+; RUN: printf "FUNCTION_A\n BAD_OP\nFUNCTION_B\n GOOD_OP\n" > %t.input
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
+
+; CHECK-LABEL: FUNCTION_A
+; CHECK: CORRECT_OP
+; CHECK-LABEL: FUNCTION_B
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: FUNCTION_A
+; DIFF-NEXT: -CORRECT_OP
+; DIFF-NEXT: +BAD_OP
+; DIFF-NEXT: FUNCTION_B
diff --git a/llvm/test/FileCheck/diff/diff-labels.txt b/llvm/test/FileCheck/diff/diff-labels.txt
new file mode 100644
index 0000000000000..0ef08ce6f5f63
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-labels.txt
@@ -0,0 +1,11 @@
+; RUN: printf "FUNC_A\n mov r0, #1\nFUNC_B\n mov r1, #2\n" > %t.labels.input
+; RUN: not FileCheck --diff=unidiff --input-file %t.labels.input %s --check-prefix=LABELS 2>&1 | FileCheck %s -check-prefix=DIFF_LABELS
+
+; LABELS-LABEL: FUNC_A
+; LABELS-NEXT: mov r0, #5
+; LABELS-LABEL: FUNC_B
+
+; DIFF_LABELS: @@ -[[#]] +[[#]] @@
+; DIFF_LABELS-NEXT: FUNC_A
+; DIFF_LABELS-NEXT: -mov r0, #5
+; DIFF_LABELS-NEXT: +mov r0, #1
diff --git a/llvm/test/FileCheck/diff/diff-large.txt b/llvm/test/FileCheck/diff/diff-large.txt
new file mode 100644
index 0000000000000..9bb50aec00850
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-large.txt
@@ -0,0 +1,12 @@
+; RUN: python3 -c "for i in range(5000): print(f'DATA_{i}')" > %t.input
+; RUN: echo "CHECK: DATA_0" > %t.check
+; RUN: echo "CHECK: DATA_2500_WRONG" >> %t.check
+; RUN: echo "CHECK: DATA_4999" >> %t.check
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %t.check 2>&1 | FileCheck %s --check-prefix=DIFF
+
+; DIFF: @@ -2 +21 @@
+; DIFF-NEXT: DATA_19
+; DIFF-NEXT: -DATA_2500_WRONG
+; DIFF-NEXT: +DATA_20
+; DIFF-NEXT: DATA_21
+
diff --git a/llvm/test/FileCheck/diff/diff-long-line.txt b/llvm/test/FileCheck/diff/diff-long-line.txt
new file mode 100644
index 0000000000000..3d771c34c4e2c
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-long-line.txt
@@ -0,0 +1,7 @@
+; RUN: printf "THIS_IS_A_VERY_LONG_PREFIX_THAT_SHOULD_BE_TRUNCATED_BY_THE_SLIDING_WINDOW_LOGIC_ACTUAL_VALUE\n" > %t.input
+; RUN: not FileCheck --diff=split --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
+
+; CHECK: THIS_IS_A_VERY_LONG_PREFIX_THAT_SHOULD_BE_TRUNCATED_BY_THE_SLIDING_WINDOW_LOGIC_EXPECTED_VALUE
+
+; DIFF: @@ L:[[#]] R:[[#]] @@
+; DIFF-NEXT: {{.*}}...{{.*}}WINDOW_LOGIC_EXPECTED_VALUE{{.*}}!{{.*}}...{{.*}}WINDOW_LOGIC_ACTUAL_VALUE
diff --git a/llvm/test/FileCheck/diff/diff-mid.txt b/llvm/test/FileCheck/diff/diff-mid.txt
new file mode 100644
index 0000000000000..d83438b80ecdf
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-mid.txt
@@ -0,0 +1,13 @@
+; RUN: printf "START\nWRONG\nEND\n" > %t.mid.input
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.mid.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
+
+; CHECK: START
+; CHECK: MIDDLE
+; CHECK: END
+
+; We should fix this.
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: WRONG
+; DIFF-NEXT: -MIDDLE
+; DIFF-NEXT: +END
diff --git a/llvm/test/FileCheck/diff/diff-missing-line.txt b/llvm/test/FileCheck/diff/diff-missing-line.txt
new file mode 100644
index 0000000000000..a7b79a4a055c3
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-missing-line.txt
@@ -0,0 +1,11 @@
+; RUN: printf "ALPHA\nGAMMA\n" > %t.input
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
+
+; CHECK: ALPHA
+; CHECK: BETA
+; CHECK: GAMMA
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: ALPHA
+; DIFF-NEXT: -BETA
+; DIFF-NEXT: +GAMMA
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..8f37bb90a99b3
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-multi-subs.txt
@@ -0,0 +1,9 @@
+; RUN: printf "SET REG=rax VAL=0\nUSE rax, 1\n" > %t.input
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=split --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
+
+; CHECK: SET REG=[[REG:[a-z]+]] VAL=[[VAL:[0-9]+]]
+; CHECK: USE [[REG]], [[VAL]]
+
+; DIFF: @@ L:[[#]] R:[[#]] @@
+; 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-multihunk.txt b/llvm/test/FileCheck/diff/diff-multihunk.txt
new file mode 100644
index 0000000000000..55f4933d6c211
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-multihunk.txt
@@ -0,0 +1,13 @@
+; RUN: printf "BLOCK1_FAIL\nKEEP_THIS\nBLOCK2_FAIL\n" > %t.multi.input
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.multi.input %s 2>&1 | FileCheck %s -check-prefix=UNI-DIFF
+;
+; --- TEST DATA ---
+; CHECK: BLOCK1_PASS
+; CHECK: KEEP_THIS
+; CHECK: BLOCK2_PASS
+
+; --- VERIFY ---
+; UNI-DIFF: @@ -[[#]] +1 @@
+; UNI-DIFF-NEXT: -BLOCK1_PASS
+; UNI-DIFF-NEXT: +BLOCK1_FAIL
+; UNI-DIFF-NEXT: KEEP_THIS
diff --git a/llvm/test/FileCheck/diff/diff-nested-vars.txt b/llvm/test/FileCheck/diff/diff-nested-vars.txt
new file mode 100644
index 0000000000000..406d029bb9931
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-nested-vars.txt
@@ -0,0 +1,10 @@
+; RUN: printf "SET [[42]]\nDATA: [[42]]\n" > %t.input
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
+
+; 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-next.txt b/llvm/test/FileCheck/diff/diff-next.txt
new file mode 100644
index 0000000000000..a6022690b4404
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-next.txt
@@ -0,0 +1,11 @@
+; RUN: printf "LINE1\nSTRAY\nLINE2\n" > %t.input
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
+
+; CHECK: LINE1
+; CHECK-NEXT: LINE2
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: LINE1
+; DIFF-NEXT: -LINE2
+; DIFF-NEXT: +STRAY
+; DIFF-NEXT: LINE2
diff --git a/llvm/test/FileCheck/diff/diff-noise.txt b/llvm/test/FileCheck/diff/diff-noise.txt
new file mode 100644
index 0000000000000..4c1b8eacab98d
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-noise.txt
@@ -0,0 +1,26 @@
+; --- Test 1: High Noise Ratio ---
+; RUN: printf "START\nNoise1\nNoise2\nNoise3\nNoise4\nTARGET\nEND\n" > %t.noise.input
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.noise.input %s --check-prefix=TEST1 2>&1 | FileCheck %s -check-prefix=DIFF1
+
+; TEST1: START
+; TEST1-NEXT: TARGET
+; TEST1: END
+
+; DIFF1: @@ -[[#]] +[[#]] @@
+; DIFF1-NEXT: START
+; DIFF1-NEXT: -TARGET
+; DIFF1-NEXT: +Noise1
+; DIFF1-NEXT: Noise2
+
+; --- Test 2: CHECK-NEXT stray line ---
+; RUN: printf "HEADER\nSTRAY\nNEXT\n" > %t.next.input
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.next.input %s --check-prefix=TEST2 2>&1 | FileCheck %s -check-prefix=DIFF2
+
+; TEST2-LABEL: HEADER
+; TEST2-NEXT: NEXT
+
+; DIFF2: @@ -[[#]] +[[#]] @@
+; DIFF2-NEXT: HEADER
+; DIFF2-NEXT: -NEXT
+; DIFF2-NEXT: +STRAY
+; DIFF2-NEXT: NEXT
diff --git a/llvm/test/FileCheck/diff/diff-numeric-expression.txt b/llvm/test/FileCheck/diff/diff-numeric-expression.txt
new file mode 100644
index 0000000000000..d8a81b4e37aa6
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-numeric-expression.txt
@@ -0,0 +1,10 @@
+; RUN: printf "BASE: 10\nNEXT: 12\n" > %t.input
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
+
+; 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-overlap.txt b/llvm/test/FileCheck/diff/diff-overlap.txt
new file mode 100644
index 0000000000000..49733a0c74434
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-overlap.txt
@@ -0,0 +1,15 @@
+; RUN: printf "LINE1\nBAD_LINE\nLINE3\nLINE4\nBAD_LINE\nLINE6\n" > %t.overlap.input
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.overlap.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
+
+; CHECK: LINE1
+; CHECK: LINE2
+; CHECK: LINE3
+; CHECK: LINE4
+; CHECK: LINE5
+; CHECK: LINE6
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: LINE1
+; DIFF-NEXT: -LINE2
+; DIFF-NEXT: +BAD_LINE
+; DIFF-NEXT: LINE3
diff --git a/llvm/test/FileCheck/diff/diff-resync.txt b/llvm/test/FileCheck/diff/diff-resync.txt
new file mode 100644
index 0000000000000..e4df50dbdf936
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-resync.txt
@@ -0,0 +1,13 @@
+; RUN: printf "HEADER\nNoise1\nNoise2\nNoise3\nNoise4\nTARGET_BLOCK\nStray1\nStray2\nMATCH_ME\nNEXT_LINE\nNoise5\nNoise6\n" > %t.input
+; RUN: not FileCheck --diff=unidiff --input-file %t.input %s --check-prefix=COMPLEX 2>&1 | FileCheck %s -check-prefix=DIFF
+
+; COMPLEX-LABEL: HEADER
+; COMPLEX: TARGET_BLOCK
+; COMPLEX: MATCH_ME
+; COMPLEX-NEXT: WRONG_NEXT
+; COMPLEX: END_OF_FUNCTION
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: MATCH_ME
+; DIFF-NEXT: -WRONG_NEXT
+; DIFF-NEXT: +NEXT_LINE
diff --git a/llvm/test/FileCheck/diff/diff-substitutions.txt b/llvm/test/FileCheck/diff/diff-substitutions.txt
new file mode 100644
index 0000000000000..085780759d874
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-substitutions.txt
@@ -0,0 +1,16 @@
+; RUN: printf "START 42\nFAIL 100\n" > %t.input
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck -check-prefix=SUBST %s
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff-no-substitutions --input-file %t.input %s 2>&1 | FileCheck -check-prefix=RAW %s
+
+; 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.txt b/llvm/test/FileCheck/diff/diff-variable-chain.txt
new file mode 100644
index 0000000000000..e370046c18daa
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-variable-chain.txt
@@ -0,0 +1,11 @@
+; RUN: printf "ID: 0x100\nTYPE: apple\nUSE: 0x100 (orange)\n" > %t.input
+; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
+
+; 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 12fdbfd45279d..b1433257a8661 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -113,6 +113,16 @@ static cl::opt<bool> VerboseVerbose(
"issues, or add it to the input dump if enabled. Implies\n"
"-v.\n"));
+static cl::opt<DiffFormatType> DiffMode(
+ "diff", cl::desc("Show mismatches using a diff-style format.\n"),
+ cl::values(clEnumValN(Standard, "standard", "Outputs a Standard diff"),
+ clEnumValN(Split, "split", "Outputs a Split diff"),
+ clEnumValN(Unified, "unidiff", "Outputs a Unified diff"),
+ clEnumValN(SplitNoSubstitution, "split-no-substitutions",
+ "Outputs a Split diff with no substitutions"),
+ clEnumValN(UnifiedNoSubstitution, "unidiff-no-substitutions",
+ "Outputs a Unified diff with no substitutions")));
+
// The order of DumpInputValue members affects their precedence, as documented
// for -dump-input below.
enum DumpInputValue {
@@ -787,6 +797,7 @@ int main(int argc, char **argv) {
if (GlobalDefineError)
return 2;
+ Req.DiffMode = DiffMode;
Req.AllowEmptyInput = AllowEmptyInput;
Req.AllowUnusedPrefixes = AllowUnusedPrefixes;
Req.EnableVarScope = EnableVarScope;
@@ -858,8 +869,9 @@ int main(int argc, char **argv) {
DumpInput == DumpInputNever ? nullptr : &Diags)
? EXIT_SUCCESS
: 1;
- if (DumpInput == DumpInputAlways ||
- (ExitCode == 1 && DumpInput == DumpInputFail)) {
+ if ((DumpInput == DumpInputAlways ||
+ (ExitCode == 1 && DumpInput == DumpInputFail)) &&
+ Req.DiffMode == DiffFormatType::Standard) {
errs() << "\n"
<< "Input file: " << InputFilename << "\n"
<< "Check file: " << CheckFilename << "\n"
>From 18f6d47d96831e47f286c67360b44bfd9925881c Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Thu, 26 Mar 2026 04:23:28 +0530
Subject: [PATCH 02/16] add mismatched summary
---
llvm/lib/FileCheck/FileCheck.cpp | 6 ++++++
llvm/test/FileCheck/diff/diff-fuzzy-lables.txt | 1 -
llvm/test/FileCheck/diff/diff-large.txt | 1 -
3 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index ea2a5684ddd3d..96d84f4910c77 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -3175,6 +3175,12 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
break;
}
+ if (Req.DiffMode != DiffFormatType::Standard && TotalMismatches > 0) {
+ OS.changeColor(llvm::raw_ostream::YELLOW, true);
+ OS << "FileCheck: Found " << TotalMismatches << " unique textual mismatch"
+ << (TotalMismatches > 1 ? "es." : ".") << "\n";
+ OS.resetColor();
+ }
// Success if no checks failed.
return !ChecksFailed;
}
diff --git a/llvm/test/FileCheck/diff/diff-fuzzy-lables.txt b/llvm/test/FileCheck/diff/diff-fuzzy-lables.txt
index 2ba3dae29c3a6..d343b7d9a287c 100644
--- a/llvm/test/FileCheck/diff/diff-fuzzy-lables.txt
+++ b/llvm/test/FileCheck/diff/diff-fuzzy-lables.txt
@@ -11,4 +11,3 @@
; DIFF-NEXT: -call void @work_test()
; DIFF-NEXT: +call void @work_task()
; DIFF-NEXT: }
-
diff --git a/llvm/test/FileCheck/diff/diff-large.txt b/llvm/test/FileCheck/diff/diff-large.txt
index 9bb50aec00850..5246d2f0752ad 100644
--- a/llvm/test/FileCheck/diff/diff-large.txt
+++ b/llvm/test/FileCheck/diff/diff-large.txt
@@ -9,4 +9,3 @@
; DIFF-NEXT: -DATA_2500_WRONG
; DIFF-NEXT: +DATA_20
; DIFF-NEXT: DATA_21
-
>From f26ec9474d12b5b6ce0efdb9a2d8460423478f9f Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Thu, 26 Mar 2026 04:43:20 +0530
Subject: [PATCH 03/16] header print
---
llvm/lib/FileCheck/FileCheck.cpp | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index 96d84f4910c77..8d08d24c933b2 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -3020,8 +3020,22 @@ static bool handleDiffFailure(const FileCheckString &CheckStr,
if (CheckRegion.empty())
return false;
+ if (!HeaderPrinted) {
+ StringRef CheckFile =
+ SM.getMemoryBuffer(SM.getMainFileID())->getBufferIdentifier();
+ unsigned InputBufID =
+ SM.FindBufferContainingLoc(SMLoc::getFromPointer(CheckRegion.data()));
+ StringRef InputFile = SM.getMemoryBuffer(InputBufID)->getBufferIdentifier();
+ OS.changeColor(raw_ostream::WHITE, true);
+ OS << "--- " << CheckFile << "\n";
+ OS << "+++ " << InputFile << "\n";
+ OS.resetColor();
+ HeaderPrinted = true;
+ }
+
SMLoc CurrentLoc = SMLoc::getFromPointer(CheckRegion.data());
unsigned CurrentLineNo = SM.getLineAndColumn(CurrentLoc).first;
+
size_t EOL = CheckRegion.find('\n');
StringRef MismatchLine =
(EOL == StringRef::npos) ? CheckRegion : CheckRegion.substr(0, EOL);
@@ -3086,6 +3100,7 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
std::vector<FileCheckDiag> *Diags) {
bool ChecksFailed = false;
unsigned TotalMismatches = 0;
+ bool HeaderPrinted = false;
auto &OS = llvm::errs();
unsigned i = 0, j = 0, e = CheckStrings.size();
@@ -3119,7 +3134,6 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
if (i != 0 && Req.EnableVarScope)
PatternContext->clearLocalVars();
- bool HeaderPrinted = false;
for (; i != j; ++i) {
const FileCheckString &CheckStr = CheckStrings[i];
>From 70e58b755d8f742777f094b8ac48fbde4e0bc8f6 Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Thu, 26 Mar 2026 15:18:05 +0530
Subject: [PATCH 04/16] updated test caes for split-file
---
llvm/lib/FileCheck/FileCheck.cpp | 12 ++++----
llvm/test/FileCheck/diff/diff-complex.txt | 13 --------
.../test/FileCheck/diff/diff-fuzzy-lables.txt | 13 --------
llvm/test/FileCheck/diff/diff-helloworld.txt | 8 -----
.../FileCheck/diff/diff-label-boundary.txt | 12 ++++++--
.../FileCheck/diff/diff-label-fuzzy-match.txt | 18 +++++++++++
llvm/test/FileCheck/diff/diff-label-next.txt | 19 ++++++++++++
llvm/test/FileCheck/diff/diff-labels.txt | 11 -------
llvm/test/FileCheck/diff/diff-large.txt | 11 -------
.../FileCheck/diff/diff-long-line-trunc.txt | 12 ++++++++
llvm/test/FileCheck/diff/diff-long-line.txt | 7 -----
llvm/test/FileCheck/diff/diff-mid.txt | 13 --------
.../test/FileCheck/diff/diff-missing-line.txt | 11 -------
llvm/test/FileCheck/diff/diff-multi-subs.txt | 10 +++++--
llvm/test/FileCheck/diff/diff-multihunk.txt | 23 ++++++++------
.../FileCheck/diff/diff-nested-vars-subs.txt | 16 ++++++++++
llvm/test/FileCheck/diff/diff-nested-vars.txt | 10 -------
.../FileCheck/diff/diff-next-stray-line.txt | 18 +++++++++++
llvm/test/FileCheck/diff/diff-next.txt | 11 -------
llvm/test/FileCheck/diff/diff-noise.txt | 26 ----------------
.../test/FileCheck/diff/diff-numeric-expr.txt | 16 ++++++++++
.../diff/diff-numeric-expression.txt | 10 -------
llvm/test/FileCheck/diff/diff-overlap.txt | 15 ----------
.../FileCheck/diff/diff-regex-escaping.txt | 13 ++++++++
.../diff/diff-resync-after-noise.txt | 30 +++++++++++++++++++
.../FileCheck/diff/diff-resync-high-noise.txt | 23 ++++++++++++++
.../FileCheck/diff/diff-resync-overlap.txt | 25 ++++++++++++++++
llvm/test/FileCheck/diff/diff-resync.txt | 13 --------
.../FileCheck/diff/diff-skipped-expected.txt | 17 +++++++++++
.../diff/diff-stress-large-input.txt | 15 ++++++++++
.../FileCheck/diff/diff-substitutions.txt | 13 ++++++--
.../diff/diff-variable-chain-subs.txt | 18 +++++++++++
.../FileCheck/diff/diff-variable-chain.txt | 11 -------
33 files changed, 288 insertions(+), 205 deletions(-)
delete mode 100644 llvm/test/FileCheck/diff/diff-complex.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-fuzzy-lables.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-helloworld.txt
create mode 100644 llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt
create mode 100644 llvm/test/FileCheck/diff/diff-label-next.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-labels.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-large.txt
create mode 100644 llvm/test/FileCheck/diff/diff-long-line-trunc.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-long-line.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-mid.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-missing-line.txt
create mode 100644 llvm/test/FileCheck/diff/diff-nested-vars-subs.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-nested-vars.txt
create mode 100644 llvm/test/FileCheck/diff/diff-next-stray-line.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-next.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-noise.txt
create mode 100644 llvm/test/FileCheck/diff/diff-numeric-expr.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-numeric-expression.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-overlap.txt
create mode 100644 llvm/test/FileCheck/diff/diff-regex-escaping.txt
create mode 100644 llvm/test/FileCheck/diff/diff-resync-after-noise.txt
create mode 100644 llvm/test/FileCheck/diff/diff-resync-high-noise.txt
create mode 100644 llvm/test/FileCheck/diff/diff-resync-overlap.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-resync.txt
create mode 100644 llvm/test/FileCheck/diff/diff-skipped-expected.txt
create mode 100644 llvm/test/FileCheck/diff/diff-stress-large-input.txt
create mode 100644 llvm/test/FileCheck/diff/diff-variable-chain-subs.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-variable-chain.txt
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index 8d08d24c933b2..d282e17a21f0d 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -2936,7 +2936,7 @@ static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
unsigned OverwriteActualLine = 0) {
StringRef ActualLine = CheckRegion.split('\n').first;
if (CheckRegion.empty()) {
- ActualLine = "<EOF>";
+ ActualLine = "";
}
SMLoc PatternLoc = CheckStr.Pat.getLoc();
@@ -3016,10 +3016,6 @@ static bool handleDiffFailure(const FileCheckString &CheckStr,
std::vector<FileCheckDiag> *Diags,
raw_ostream &OS, bool &HeaderPrinted,
unsigned &TotalMismatches) {
- CheckRegion = CheckRegion.ltrim("\n\r");
- if (CheckRegion.empty())
- return false;
-
if (!HeaderPrinted) {
StringRef CheckFile =
SM.getMemoryBuffer(SM.getMainFileID())->getBufferIdentifier();
@@ -3033,8 +3029,12 @@ static bool handleDiffFailure(const FileCheckString &CheckStr,
HeaderPrinted = true;
}
+ CheckRegion = CheckRegion.ltrim("\n\r");
+
SMLoc CurrentLoc = SMLoc::getFromPointer(CheckRegion.data());
- unsigned CurrentLineNo = SM.getLineAndColumn(CurrentLoc).first;
+ unsigned CurrentLineNo = 0;
+ if (!CheckRegion.empty())
+ CurrentLineNo = SM.getLineAndColumn(CurrentLoc).first;
size_t EOL = CheckRegion.find('\n');
StringRef MismatchLine =
diff --git a/llvm/test/FileCheck/diff/diff-complex.txt b/llvm/test/FileCheck/diff/diff-complex.txt
deleted file mode 100644
index e4df50dbdf936..0000000000000
--- a/llvm/test/FileCheck/diff/diff-complex.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-; RUN: printf "HEADER\nNoise1\nNoise2\nNoise3\nNoise4\nTARGET_BLOCK\nStray1\nStray2\nMATCH_ME\nNEXT_LINE\nNoise5\nNoise6\n" > %t.input
-; RUN: not FileCheck --diff=unidiff --input-file %t.input %s --check-prefix=COMPLEX 2>&1 | FileCheck %s -check-prefix=DIFF
-
-; COMPLEX-LABEL: HEADER
-; COMPLEX: TARGET_BLOCK
-; COMPLEX: MATCH_ME
-; COMPLEX-NEXT: WRONG_NEXT
-; COMPLEX: END_OF_FUNCTION
-
-; DIFF: @@ -[[#]] +[[#]] @@
-; DIFF-NEXT: MATCH_ME
-; DIFF-NEXT: -WRONG_NEXT
-; DIFF-NEXT: +NEXT_LINE
diff --git a/llvm/test/FileCheck/diff/diff-fuzzy-lables.txt b/llvm/test/FileCheck/diff/diff-fuzzy-lables.txt
deleted file mode 100644
index d343b7d9a287c..0000000000000
--- a/llvm/test/FileCheck/diff/diff-fuzzy-lables.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-; RUN: printf "define void @foo() {\n call void @work_task()\n}\n" > %t.input
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
-
-; CHECK-LABEL: define void @foo()
-; CHECK: call void @work_test()
-
-; Need to fixed this
-
-; DIFF: @@ -[[#]] +[[#]] @@
-; DIFF: define void @foo() {
-; DIFF-NEXT: -call void @work_test()
-; DIFF-NEXT: +call void @work_task()
-; DIFF-NEXT: }
diff --git a/llvm/test/FileCheck/diff/diff-helloworld.txt b/llvm/test/FileCheck/diff/diff-helloworld.txt
deleted file mode 100644
index 074c15bdcfe86..0000000000000
--- a/llvm/test/FileCheck/diff/diff-helloworld.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-; RUN: printf "hello world\n" > %t.input
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck -check-prefix=UNI-DIFF %s
-
-; CHECK: hello {{U}}niverse
-
-; UNI-DIFF: @@ -4 +1 @@
-; UNI-DIFF-NEXT: -hello {{[{][{]U[}][}]}}niverse
-; UNI-DIFF-NEXT: +hello world
diff --git a/llvm/test/FileCheck/diff/diff-label-boundary.txt b/llvm/test/FileCheck/diff/diff-label-boundary.txt
index 2d3cc114f254e..6381e2a71f82e 100644
--- a/llvm/test/FileCheck/diff/diff-label-boundary.txt
+++ b/llvm/test/FileCheck/diff/diff-label-boundary.txt
@@ -1,6 +1,14 @@
-; RUN: printf "FUNCTION_A\n BAD_OP\nFUNCTION_B\n GOOD_OP\n" > %t.input
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
+; 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
+FUNCTION_A
+ BAD_OP
+FUNCTION_B
+ GOOD_OP
+
+;--- check.txt
; CHECK-LABEL: FUNCTION_A
; CHECK: CORRECT_OP
; CHECK-LABEL: FUNCTION_B
diff --git a/llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt b/llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt
new file mode 100644
index 0000000000000..a04f2e84ac70b
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-label-fuzzy-match.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
+define void @foo() {
+ call void @work_task()
+}
+
+;--- check.txt
+; 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: }
diff --git a/llvm/test/FileCheck/diff/diff-label-next.txt b/llvm/test/FileCheck/diff/diff-label-next.txt
new file mode 100644
index 0000000000000..f2ea934376d6e
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-label-next.txt
@@ -0,0 +1,19 @@
+; 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
+FUNC_A
+ mov r0, #1
+FUNC_B
+ mov r1, #2
+
+;--- check.txt
+; CHECK-LABEL: FUNC_A
+; CHECK-NEXT: mov r0, #5
+; CHECK-LABEL: FUNC_B
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: FUNC_A
+; DIFF-NEXT: -mov r0, #5
+; DIFF-NEXT: +mov r0, #1
diff --git a/llvm/test/FileCheck/diff/diff-labels.txt b/llvm/test/FileCheck/diff/diff-labels.txt
deleted file mode 100644
index 0ef08ce6f5f63..0000000000000
--- a/llvm/test/FileCheck/diff/diff-labels.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-; RUN: printf "FUNC_A\n mov r0, #1\nFUNC_B\n mov r1, #2\n" > %t.labels.input
-; RUN: not FileCheck --diff=unidiff --input-file %t.labels.input %s --check-prefix=LABELS 2>&1 | FileCheck %s -check-prefix=DIFF_LABELS
-
-; LABELS-LABEL: FUNC_A
-; LABELS-NEXT: mov r0, #5
-; LABELS-LABEL: FUNC_B
-
-; DIFF_LABELS: @@ -[[#]] +[[#]] @@
-; DIFF_LABELS-NEXT: FUNC_A
-; DIFF_LABELS-NEXT: -mov r0, #5
-; DIFF_LABELS-NEXT: +mov r0, #1
diff --git a/llvm/test/FileCheck/diff/diff-large.txt b/llvm/test/FileCheck/diff/diff-large.txt
deleted file mode 100644
index 5246d2f0752ad..0000000000000
--- a/llvm/test/FileCheck/diff/diff-large.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-; RUN: python3 -c "for i in range(5000): print(f'DATA_{i}')" > %t.input
-; RUN: echo "CHECK: DATA_0" > %t.check
-; RUN: echo "CHECK: DATA_2500_WRONG" >> %t.check
-; RUN: echo "CHECK: DATA_4999" >> %t.check
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %t.check 2>&1 | FileCheck %s --check-prefix=DIFF
-
-; DIFF: @@ -2 +21 @@
-; DIFF-NEXT: DATA_19
-; DIFF-NEXT: -DATA_2500_WRONG
-; DIFF-NEXT: +DATA_20
-; DIFF-NEXT: DATA_21
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..e57b23a2927c1
--- /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: @@ L:[[#]] R:[[#]] @@
+; DIFF-NEXT: {{.*}}...{{.*}}WINDOW_LOGIC_EXPECTED_VALUE{{.*}}!{{.*}}...{{.*}}WINDOW_LOGIC_ACTUAL_VALUE
diff --git a/llvm/test/FileCheck/diff/diff-long-line.txt b/llvm/test/FileCheck/diff/diff-long-line.txt
deleted file mode 100644
index 3d771c34c4e2c..0000000000000
--- a/llvm/test/FileCheck/diff/diff-long-line.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-; RUN: printf "THIS_IS_A_VERY_LONG_PREFIX_THAT_SHOULD_BE_TRUNCATED_BY_THE_SLIDING_WINDOW_LOGIC_ACTUAL_VALUE\n" > %t.input
-; RUN: not FileCheck --diff=split --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
-
-; CHECK: THIS_IS_A_VERY_LONG_PREFIX_THAT_SHOULD_BE_TRUNCATED_BY_THE_SLIDING_WINDOW_LOGIC_EXPECTED_VALUE
-
-; DIFF: @@ L:[[#]] R:[[#]] @@
-; DIFF-NEXT: {{.*}}...{{.*}}WINDOW_LOGIC_EXPECTED_VALUE{{.*}}!{{.*}}...{{.*}}WINDOW_LOGIC_ACTUAL_VALUE
diff --git a/llvm/test/FileCheck/diff/diff-mid.txt b/llvm/test/FileCheck/diff/diff-mid.txt
deleted file mode 100644
index d83438b80ecdf..0000000000000
--- a/llvm/test/FileCheck/diff/diff-mid.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-; RUN: printf "START\nWRONG\nEND\n" > %t.mid.input
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.mid.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
-
-; CHECK: START
-; CHECK: MIDDLE
-; CHECK: END
-
-; We should fix this.
-
-; DIFF: @@ -[[#]] +[[#]] @@
-; DIFF-NEXT: WRONG
-; DIFF-NEXT: -MIDDLE
-; DIFF-NEXT: +END
diff --git a/llvm/test/FileCheck/diff/diff-missing-line.txt b/llvm/test/FileCheck/diff/diff-missing-line.txt
deleted file mode 100644
index a7b79a4a055c3..0000000000000
--- a/llvm/test/FileCheck/diff/diff-missing-line.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-; RUN: printf "ALPHA\nGAMMA\n" > %t.input
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
-
-; CHECK: ALPHA
-; CHECK: BETA
-; CHECK: GAMMA
-
-; DIFF: @@ -[[#]] +[[#]] @@
-; DIFF-NEXT: ALPHA
-; DIFF-NEXT: -BETA
-; DIFF-NEXT: +GAMMA
diff --git a/llvm/test/FileCheck/diff/diff-multi-subs.txt b/llvm/test/FileCheck/diff/diff-multi-subs.txt
index 8f37bb90a99b3..22cdf37a02bd7 100644
--- a/llvm/test/FileCheck/diff/diff-multi-subs.txt
+++ b/llvm/test/FileCheck/diff/diff-multi-subs.txt
@@ -1,6 +1,12 @@
-; RUN: printf "SET REG=rax VAL=0\nUSE rax, 1\n" > %t.input
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=split --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
+; 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 --git a/llvm/test/FileCheck/diff/diff-multihunk.txt b/llvm/test/FileCheck/diff/diff-multihunk.txt
index 55f4933d6c211..aa78446b0604d 100644
--- a/llvm/test/FileCheck/diff/diff-multihunk.txt
+++ b/llvm/test/FileCheck/diff/diff-multihunk.txt
@@ -1,13 +1,18 @@
-; RUN: printf "BLOCK1_FAIL\nKEEP_THIS\nBLOCK2_FAIL\n" > %t.multi.input
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.multi.input %s 2>&1 | FileCheck %s -check-prefix=UNI-DIFF
-;
-; --- TEST DATA ---
+; 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
+BLOCK1_FAIL
+KEEP_THIS
+BLOCK2_FAIL
+
+;--- check.txt
; CHECK: BLOCK1_PASS
; CHECK: KEEP_THIS
; CHECK: BLOCK2_PASS
-; --- VERIFY ---
-; UNI-DIFF: @@ -[[#]] +1 @@
-; UNI-DIFF-NEXT: -BLOCK1_PASS
-; UNI-DIFF-NEXT: +BLOCK1_FAIL
-; UNI-DIFF-NEXT: KEEP_THIS
+; DIFF: @@ -[[#]] +1 @@
+; DIFF-NEXT: -BLOCK1_PASS
+; DIFF-NEXT: +BLOCK1_FAIL
+; DIFF-NEXT: KEEP_THIS
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-nested-vars.txt b/llvm/test/FileCheck/diff/diff-nested-vars.txt
deleted file mode 100644
index 406d029bb9931..0000000000000
--- a/llvm/test/FileCheck/diff/diff-nested-vars.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-; RUN: printf "SET [[42]]\nDATA: [[42]]\n" > %t.input
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
-
-; 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-next-stray-line.txt b/llvm/test/FileCheck/diff/diff-next-stray-line.txt
new file mode 100644
index 0000000000000..dd19a0e2c6dd2
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-next-stray-line.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
+LINE1
+STRAY
+LINE2
+
+;--- check.txt
+; CHECK: LINE1
+; CHECK-NEXT: LINE2
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: LINE1
+; DIFF-NEXT: -LINE2
+; DIFF-NEXT: +STRAY
+; DIFF-NEXT: LINE2
diff --git a/llvm/test/FileCheck/diff/diff-next.txt b/llvm/test/FileCheck/diff/diff-next.txt
deleted file mode 100644
index a6022690b4404..0000000000000
--- a/llvm/test/FileCheck/diff/diff-next.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-; RUN: printf "LINE1\nSTRAY\nLINE2\n" > %t.input
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
-
-; CHECK: LINE1
-; CHECK-NEXT: LINE2
-
-; DIFF: @@ -[[#]] +[[#]] @@
-; DIFF-NEXT: LINE1
-; DIFF-NEXT: -LINE2
-; DIFF-NEXT: +STRAY
-; DIFF-NEXT: LINE2
diff --git a/llvm/test/FileCheck/diff/diff-noise.txt b/llvm/test/FileCheck/diff/diff-noise.txt
deleted file mode 100644
index 4c1b8eacab98d..0000000000000
--- a/llvm/test/FileCheck/diff/diff-noise.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-; --- Test 1: High Noise Ratio ---
-; RUN: printf "START\nNoise1\nNoise2\nNoise3\nNoise4\nTARGET\nEND\n" > %t.noise.input
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.noise.input %s --check-prefix=TEST1 2>&1 | FileCheck %s -check-prefix=DIFF1
-
-; TEST1: START
-; TEST1-NEXT: TARGET
-; TEST1: END
-
-; DIFF1: @@ -[[#]] +[[#]] @@
-; DIFF1-NEXT: START
-; DIFF1-NEXT: -TARGET
-; DIFF1-NEXT: +Noise1
-; DIFF1-NEXT: Noise2
-
-; --- Test 2: CHECK-NEXT stray line ---
-; RUN: printf "HEADER\nSTRAY\nNEXT\n" > %t.next.input
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.next.input %s --check-prefix=TEST2 2>&1 | FileCheck %s -check-prefix=DIFF2
-
-; TEST2-LABEL: HEADER
-; TEST2-NEXT: NEXT
-
-; DIFF2: @@ -[[#]] +[[#]] @@
-; DIFF2-NEXT: HEADER
-; DIFF2-NEXT: -NEXT
-; DIFF2-NEXT: +STRAY
-; DIFF2-NEXT: NEXT
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-numeric-expression.txt b/llvm/test/FileCheck/diff/diff-numeric-expression.txt
deleted file mode 100644
index d8a81b4e37aa6..0000000000000
--- a/llvm/test/FileCheck/diff/diff-numeric-expression.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-; RUN: printf "BASE: 10\nNEXT: 12\n" > %t.input
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
-
-; 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-overlap.txt b/llvm/test/FileCheck/diff/diff-overlap.txt
deleted file mode 100644
index 49733a0c74434..0000000000000
--- a/llvm/test/FileCheck/diff/diff-overlap.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-; RUN: printf "LINE1\nBAD_LINE\nLINE3\nLINE4\nBAD_LINE\nLINE6\n" > %t.overlap.input
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.overlap.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
-
-; CHECK: LINE1
-; CHECK: LINE2
-; CHECK: LINE3
-; CHECK: LINE4
-; CHECK: LINE5
-; CHECK: LINE6
-
-; DIFF: @@ -[[#]] +[[#]] @@
-; DIFF-NEXT: LINE1
-; DIFF-NEXT: -LINE2
-; DIFF-NEXT: +BAD_LINE
-; DIFF-NEXT: LINE3
diff --git a/llvm/test/FileCheck/diff/diff-regex-escaping.txt b/llvm/test/FileCheck/diff/diff-regex-escaping.txt
new file mode 100644
index 0000000000000..8f3ebc6163f04
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-regex-escaping.txt
@@ -0,0 +1,13 @@
+; 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
+hello world
+
+;--- check.txt
+; CHECK: hello {{U}}niverse
+
+; DIFF: @@ -[[#]] +1 @@
+; DIFF-NEXT: -hello {{[{][{]U[}][}]}}niverse
+; DIFF-NEXT: +hello world
diff --git a/llvm/test/FileCheck/diff/diff-resync-after-noise.txt b/llvm/test/FileCheck/diff/diff-resync-after-noise.txt
new file mode 100644
index 0000000000000..854ee188b2944
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-resync-after-noise.txt
@@ -0,0 +1,30 @@
+; 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
+HEADER
+Noise1
+Noise2
+Noise3
+Noise4
+TARGET_BLOCK
+Stray1
+Stray2
+MATCH_ME
+NEXT_LINE
+Noise5
+Noise6
+
+;--- check.txt
+; CHECK-LABEL: HEADER
+; CHECK: TARGET_BLOCK
+; CHECK: MATCH_ME
+; CHECK-NEXT: WRONG_NEXT
+; CHECK: END_OF_FUNCTION
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: MATCH_ME
+; DIFF-NEXT: -WRONG_NEXT
+; DIFF-NEXT: +NEXT_LINE
+; DIFF-NEXT: Noise5
diff --git a/llvm/test/FileCheck/diff/diff-resync-high-noise.txt b/llvm/test/FileCheck/diff/diff-resync-high-noise.txt
new file mode 100644
index 0000000000000..d7ecfe48d3b78
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-resync-high-noise.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=DIFF
+
+;--- input.txt
+START
+Noise1
+Noise2
+Noise3
+Noise4
+TARGET
+END
+
+;--- check.txt
+; CHECK: START
+; CHECK-NEXT: TARGET
+; CHECK: END
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: START
+; DIFF-NEXT: -TARGET
+; DIFF-NEXT: +Noise1
+; DIFF-NEXT: Noise2
diff --git a/llvm/test/FileCheck/diff/diff-resync-overlap.txt b/llvm/test/FileCheck/diff/diff-resync-overlap.txt
new file mode 100644
index 0000000000000..85408544e2073
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-resync-overlap.txt
@@ -0,0 +1,25 @@
+; 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
+LINE1
+BAD_LINE
+LINE3
+LINE4
+BAD_LINE
+LINE6
+
+;--- check.txt
+; CHECK: LINE1
+; CHECK: LINE2
+; CHECK: LINE3
+; CHECK: LINE4
+; CHECK: LINE5
+; CHECK: LINE6
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: LINE1
+; DIFF-NEXT: -LINE2
+; DIFF-NEXT: +BAD_LINE
+; DIFF-NEXT: LINE3
diff --git a/llvm/test/FileCheck/diff/diff-resync.txt b/llvm/test/FileCheck/diff/diff-resync.txt
deleted file mode 100644
index e4df50dbdf936..0000000000000
--- a/llvm/test/FileCheck/diff/diff-resync.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-; RUN: printf "HEADER\nNoise1\nNoise2\nNoise3\nNoise4\nTARGET_BLOCK\nStray1\nStray2\nMATCH_ME\nNEXT_LINE\nNoise5\nNoise6\n" > %t.input
-; RUN: not FileCheck --diff=unidiff --input-file %t.input %s --check-prefix=COMPLEX 2>&1 | FileCheck %s -check-prefix=DIFF
-
-; COMPLEX-LABEL: HEADER
-; COMPLEX: TARGET_BLOCK
-; COMPLEX: MATCH_ME
-; COMPLEX-NEXT: WRONG_NEXT
-; COMPLEX: END_OF_FUNCTION
-
-; DIFF: @@ -[[#]] +[[#]] @@
-; DIFF-NEXT: MATCH_ME
-; DIFF-NEXT: -WRONG_NEXT
-; DIFF-NEXT: +NEXT_LINE
diff --git a/llvm/test/FileCheck/diff/diff-skipped-expected.txt b/llvm/test/FileCheck/diff/diff-skipped-expected.txt
new file mode 100644
index 0000000000000..f8b4d2f303bb5
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-skipped-expected.txt
@@ -0,0 +1,17 @@
+; 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
+ALPHA
+GAMMA
+
+;--- check.txt
+; CHECK: ALPHA
+; CHECK: BETA
+; CHECK: GAMMA
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: ALPHA
+; DIFF-NEXT: -BETA
+; DIFF-NEXT: +GAMMA
diff --git a/llvm/test/FileCheck/diff/diff-stress-large-input.txt b/llvm/test/FileCheck/diff/diff-stress-large-input.txt
new file mode 100644
index 0000000000000..2e411702f8aa3
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-stress-large-input.txt
@@ -0,0 +1,15 @@
+; RUN: split-file %s %t
+; RUN: python3 -c "for i in range(5000): print(f'DATA_{i}')" > %t/input.txt
+; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
+; RUN: | FileCheck %s --check-prefix=DIFF
+
+;--- check.txt
+; CHECK: DATA_0
+; CHECK: DATA_2500_WRONG
+; CHECK: DATA_4999
+
+; DIFF: @@ -2 +21 @@
+; DIFF-NEXT: DATA_19
+; DIFF-NEXT: -DATA_2500_WRONG
+; DIFF-NEXT: +DATA_20
+; DIFF-NEXT: DATA_21
diff --git a/llvm/test/FileCheck/diff/diff-substitutions.txt b/llvm/test/FileCheck/diff/diff-substitutions.txt
index 085780759d874..11c8a68484f1f 100644
--- a/llvm/test/FileCheck/diff/diff-substitutions.txt
+++ b/llvm/test/FileCheck/diff/diff-substitutions.txt
@@ -1,7 +1,14 @@
-; RUN: printf "START 42\nFAIL 100\n" > %t.input
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck -check-prefix=SUBST %s
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff-no-substitutions --input-file %t.input %s 2>&1 | FileCheck -check-prefix=RAW %s
+; 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]]
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/test/FileCheck/diff/diff-variable-chain.txt b/llvm/test/FileCheck/diff/diff-variable-chain.txt
deleted file mode 100644
index e370046c18daa..0000000000000
--- a/llvm/test/FileCheck/diff/diff-variable-chain.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-; RUN: printf "ID: 0x100\nTYPE: apple\nUSE: 0x100 (orange)\n" > %t.input
-; RUN: %ProtectFileCheckOutput not FileCheck --diff=unidiff --input-file %t.input %s 2>&1 | FileCheck %s -check-prefix=DIFF
-
-; 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)
>From 6229e7b6ce1193327e5179f99ed1e24e658ae651 Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Fri, 27 Mar 2026 10:58:26 +0530
Subject: [PATCH 05/16] removed split view and substitution functions from the
PR
---
llvm/docs/CommandGuide/FileCheck.rst | 5 -
llvm/include/llvm/FileCheck/FileCheck.h | 8 +-
llvm/lib/FileCheck/FileCheck.cpp | 202 ++++--------------
llvm/lib/FileCheck/FileCheckImpl.h | 2 -
.../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-stress-large-input.txt | 2 +-
.../FileCheck/diff/diff-substitutions.txt | 23 --
.../diff/diff-variable-chain-subs.txt | 18 --
llvm/utils/FileCheck/FileCheck.cpp | 7 +-
12 files changed, 39 insertions(+), 287 deletions(-)
delete mode 100644 llvm/test/FileCheck/diff/diff-long-line-trunc.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-multi-subs.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-nested-vars-subs.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-numeric-expr.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-substitutions.txt
delete 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 c02613e440ac2..1479c45046068 100644
--- a/llvm/docs/CommandGuide/FileCheck.rst
+++ b/llvm/docs/CommandGuide/FileCheck.rst
@@ -117,12 +117,7 @@ and from the command line.
diagnostic output.
* ``standard`` – Use the standard FileCheck diagnostic messages.
- * ``split`` – Display mismatches using a side-by-side split diff view.
* ``unidiff`` – Display mismatches using a unified diff format.
- * ``split-no-substitutions`` – Same as ``split`` but shows the raw
- pattern without applying variable substitutions.
- * ``unidiff-no-substitutions`` – Same as ``unidiff`` 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 b8db1194d7340..e5af0539eb1cf 100644
--- a/llvm/include/llvm/FileCheck/FileCheck.h
+++ b/llvm/include/llvm/FileCheck/FileCheck.h
@@ -28,13 +28,7 @@ class SourceMgr;
template <typename T> class SmallVectorImpl;
// Diff the output on failures.
-enum DiffFormatType {
- Standard,
- Split,
- Unified,
- SplitNoSubstitution,
- UnifiedNoSubstitution
-};
+enum DiffFormatType { Standard, Unified };
/// Contains info about various FileCheck options.
struct FileCheckRequest {
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index d282e17a21f0d..63f5226f47483 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -2732,42 +2732,13 @@ void FileCheckPatternContext::clearLocalVars() {
GlobalNumericVariableTable.erase(Var);
}
-/// Extract a fixed-width "window" from string \p S, centered around \p DiffPos.
-///
-/// This is used for side-by-side diffing of long lines (e.g., metadata
-/// or long symbol names). It ensures that the "point of divergence" is visible
-/// within the provided \p Width. If the string is truncated, ellipses (...)
-/// are inserted at the boundaries.
-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 (S.size() > Start + Width && View.size() > 3)
- View.replace(View.size() - 3, 3, "...");
-
- return View;
-}
-
struct DiffContext {
StringRef Line;
StringRef LineBefore;
StringRef LineAfter;
};
-/// Populates a \c DiffContext by fetching the line at \p LineNo
-/// as well as the lines before and after it from the \p SourceMgr. This
-/// provides the "surrounding context" seen in standard diff tools.
+// Provides the "surrounding context" seen in standard diff tools.
static DiffContext getDiffContext(SourceMgr &SM, unsigned LineNo,
unsigned BufID) {
const MemoryBuffer *Buffer = SM.getMemoryBuffer(BufID);
@@ -2796,139 +2767,42 @@ static DiffContext getDiffContext(SourceMgr &SM, unsigned LineNo,
getLineText(LineNo + 1)};
}
-/// Converts a raw pattern string (e.g., "val [[VAL]]") into a
-/// human-readable diagnostic string (e.g., "val 42") by replacing
-/// "[[...]]" tags with the values stored in the \c Substitutions vector.
-std::string Pattern::getSubstitutedRegex(StringRef PatternText) const {
- std::string Result = PatternText.str();
-
- // We iterate through substitutions. Since they are stored in the order
- // they appear in the pattern, we can 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 to \c llvm::errs().
-///
-/// Supports two modes:
-/// 1. Split View: A side-by-side comparison (Expected | Actual) using a
-/// sliding window centered on the first difference.
-/// 2. Unified View: A standard top-to-bottom (-Expected / +Actual) format.
+// Renders a diagnostic diff to llvm::errs().
+//
+// Supported mode:
+// Unified View: A standard top-to-bottom (-Expected / +Actual) format.
static void renderDiff(DiffFormatType Mode, unsigned ExpectedLineNo,
unsigned ActualLineNo, StringRef ActualLine,
const std::string &ExpectedText, const DiffContext &Ctx,
unsigned OverwriteActualLine) {
auto &OS = llvm::errs();
- constexpr unsigned ColWidth = 45;
- constexpr StringRef Sep = " | ";
-
- bool IsSplit = (Mode == Split || Mode == SplitNoSubstitution);
-
- // Identify the first index where the expected and actual text diverge.
- size_t DiffPos = 0;
- size_t MinLen = std::min(ExpectedText.size(), ActualLine.size());
- while (DiffPos < MinLen && ExpectedText[DiffPos] == ActualLine[DiffPos])
- ++DiffPos;
-
- auto GetView = [&](StringRef S) {
- return getCenteredView(S, DiffPos, ColWidth - 1);
- };
-
// Header
OS.changeColor(raw_ostream::CYAN);
- if (IsSplit)
- OS << "@@ L:" << ExpectedLineNo << " R:" << ActualLineNo << " @@\n";
- else
- OS << "@@ -" << ExpectedLineNo << " +" << ActualLineNo << " @@\n";
+ OS << "@@ -" << ExpectedLineNo << " +" << ActualLineNo << " @@\n";
OS.resetColor();
// Before Context
if (!Ctx.LineBefore.empty()) {
- if (IsSplit)
- OS << " " << GetView(Ctx.LineBefore) << Sep << GetView(Ctx.LineBefore)
- << "\n";
- else
- OS << " " << Ctx.LineBefore << "\n";
+ OS << " " << Ctx.LineBefore << "\n";
}
// Mismatch
- if (IsSplit) {
- OS << " ";
-
- OS.changeColor(raw_ostream::RED);
- OS << GetView(ExpectedText);
- OS.resetColor();
-
- bool IsWrongLine = (OverwriteActualLine != 0);
+ OS.changeColor(raw_ostream::RED);
+ OS << "-" << ExpectedText << "\n";
- if (IsWrongLine) {
- // Use a bold yellow '!' to signify that the text might match,
- // but it was found on the wrong line.
- OS.changeColor(raw_ostream::YELLOW, true);
- OS << " ! ";
- OS.resetColor();
- } else {
- OS << " | ";
- }
-
- OS.changeColor(raw_ostream::GREEN);
- OS << GetView(ActualLine) << "\n";
- OS.resetColor();
-
- } else {
- OS.changeColor(raw_ostream::RED);
- OS << "-" << ExpectedText << "\n";
-
- OS.changeColor(raw_ostream::GREEN);
- OS << "+" << ActualLine << "\n";
- OS.resetColor();
- }
+ OS.changeColor(raw_ostream::GREEN);
+ OS << "+" << ActualLine << "\n";
+ OS.resetColor();
// After Context
if (!Ctx.LineAfter.empty()) {
- if (IsSplit)
- OS << " " << GetView(Ctx.LineAfter) << Sep << GetView(Ctx.LineAfter)
- << "\n";
- else
- OS << " " << Ctx.LineAfter << "\n";
+ OS << " " << Ctx.LineAfter << "\n";
}
}
-/// Prepares and prints a visual comparison between a CHECK pattern and the
-/// input.
-///
-/// This function acts as a bridge between the FileCheck engine and the diff
-/// renderer. It resolves the line numbers for both the pattern (Expected) and
-/// the input (Actual), performs variable substitution if requested, and fetches
-/// the surrounding context lines.
+// Prepares and prints a visual comparison between a CHECK pattern and the
+// input.
static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
StringRef CheckRegion, SourceMgr &SM,
const FileCheckRequest &Req,
@@ -2945,39 +2819,35 @@ static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
StringRef FullExpectedLine = StringRef(PatPtr).split('\n').first.trim();
std::string ExpectedText;
- if (Mode == DiffFormatType::SplitNoSubstitution ||
- Mode == DiffFormatType::UnifiedNoSubstitution)
- ExpectedText = FullExpectedLine.str(); // Raw pattern: [[VAR]]
- else
- ExpectedText = CheckStr.Pat.getSubstitutedRegex(
- FullExpectedLine); // Substituted: value
+ ExpectedText = FullExpectedLine.str();
// Resolve the Actual (Input) line number.
// Priority: 1. OverwriteActualLine (explicit override)
// 2. Fuzzy Match Diag (where FileCheck 'thinks' the line was)
// 3. Direct pointer resolution via SourceMgr.
SMLoc InputLoc = SMLoc::getFromPointer(CheckRegion.data());
- unsigned ActualLineNo = OverwriteActualLine;
- if (ActualLineNo == 0) {
- ActualLineNo = SM.getLineAndColumn(InputLoc).first;
- bool FoundFuzzy = false;
-
- // Search backward to find most recent fuzzy match for this pattern.
- if (Diags) {
- for (const auto &D : llvm::reverse(*Diags)) {
- if (D.CheckLoc == PatternLoc &&
- D.MatchTy == FileCheckDiag::MatchFuzzy) {
- ActualLineNo = D.InputStartLine;
- FoundFuzzy = true;
- break;
- }
+
+unsigned ActualLineNo = OverwriteActualLine;
+
+if (ActualLineNo == 0) {
+ bool FoundFuzzy = false;
+
+ if (Diags) {
+ for (const auto &D : llvm::reverse(*Diags)) {
+ if (D.CheckLoc == PatternLoc &&
+ D.MatchTy == FileCheckDiag::MatchFuzzy) {
+ ActualLineNo = D.InputStartLine;
+ FoundFuzzy = true;
+ break;
}
}
- // If no diagnostic match was found, calculate the line number directly
- // from the InputLoc pointer using the SourceManager.
- if (!FoundFuzzy)
- ActualLineNo = SM.getLineAndColumn(InputLoc).first;
+ }
+
+ // If no diagnostic match was found, calculate the line number directly
+ // from the InputLoc pointer using the SourceManager.
+ if (ActualLineNo == 0)
+ ActualLineNo = SM.getLineAndColumn(InputLoc).first;
// if we are at an empty line, usually the relevant context is the line just
// before it.
@@ -3085,7 +2955,7 @@ static bool handleDiffFailure(const FileCheckString &CheckStr,
TargetLineNo);
TotalMismatches++;
- // Updates \p CheckRegion to advance the search pointer past the error.
+ // Updates CheckRegion to advance the search pointer past the error.
if (AdvanceToResync && ResyncPos != StringRef::npos)
CheckRegion = CheckRegion.substr(ResyncPos + ResyncMatchLen);
else if (EOL != StringRef::npos)
diff --git a/llvm/lib/FileCheck/FileCheckImpl.h b/llvm/lib/FileCheck/FileCheckImpl.h
index 5b47b87076638..58a03d280b658 100644
--- a/llvm/lib/FileCheck/FileCheckImpl.h
+++ b/llvm/lib/FileCheck/FileCheckImpl.h
@@ -740,8 +740,6 @@ 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-long-line-trunc.txt b/llvm/test/FileCheck/diff/diff-long-line-trunc.txt
deleted file mode 100644
index e57b23a2927c1..0000000000000
--- a/llvm/test/FileCheck/diff/diff-long-line-trunc.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-; 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: @@ L:[[#]] R:[[#]] @@
-; 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
deleted file mode 100644
index 22cdf37a02bd7..0000000000000
--- a/llvm/test/FileCheck/diff/diff-multi-subs.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-; 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: @@ L:[[#]] R:[[#]] @@
-; 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
deleted file mode 100644
index 7be1c1d9d96ab..0000000000000
--- a/llvm/test/FileCheck/diff/diff-nested-vars-subs.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-; 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
deleted file mode 100644
index 36cfe98bea0d0..0000000000000
--- a/llvm/test/FileCheck/diff/diff-numeric-expr.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-; 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-stress-large-input.txt b/llvm/test/FileCheck/diff/diff-stress-large-input.txt
index 2e411702f8aa3..68488d4b39a13 100644
--- a/llvm/test/FileCheck/diff/diff-stress-large-input.txt
+++ b/llvm/test/FileCheck/diff/diff-stress-large-input.txt
@@ -1,5 +1,5 @@
; RUN: split-file %s %t
-; RUN: python3 -c "for i in range(5000): print(f'DATA_{i}')" > %t/input.txt
+; RUN: %python -c "for i in range(5000): print(f'DATA_{i}')" > %t/input.txt
; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
; RUN: | FileCheck %s --check-prefix=DIFF
diff --git a/llvm/test/FileCheck/diff/diff-substitutions.txt b/llvm/test/FileCheck/diff/diff-substitutions.txt
deleted file mode 100644
index 11c8a68484f1f..0000000000000
--- a/llvm/test/FileCheck/diff/diff-substitutions.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-; 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
deleted file mode 100644
index 4e172a4f28789..0000000000000
--- a/llvm/test/FileCheck/diff/diff-variable-chain-subs.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-; 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 b1433257a8661..210e2f82ba990 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -116,12 +116,7 @@ static cl::opt<bool> VerboseVerbose(
static cl::opt<DiffFormatType> DiffMode(
"diff", cl::desc("Show mismatches using a diff-style format.\n"),
cl::values(clEnumValN(Standard, "standard", "Outputs a Standard diff"),
- clEnumValN(Split, "split", "Outputs a Split diff"),
- clEnumValN(Unified, "unidiff", "Outputs a Unified diff"),
- clEnumValN(SplitNoSubstitution, "split-no-substitutions",
- "Outputs a Split diff with no substitutions"),
- clEnumValN(UnifiedNoSubstitution, "unidiff-no-substitutions",
- "Outputs a Unified diff with no substitutions")));
+ clEnumValN(Unified, "unidiff", "Outputs a Unified diff")));
// The order of DumpInputValue members affects their precedence, as documented
// for -dump-input below.
>From b29835402896c306a288ce52159c54c0baab5fe8 Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Fri, 27 Mar 2026 12:05:17 +0530
Subject: [PATCH 06/16] simplify code [not for review]
Simplify CheckInput [Not for review]
---
llvm/lib/FileCheck/FileCheck.cpp | 93 ++++++++++++++------------------
1 file changed, 41 insertions(+), 52 deletions(-)
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index 63f5226f47483..b343a47bdadf9 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -2827,31 +2827,30 @@ static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
// 3. Direct pointer resolution via SourceMgr.
SMLoc InputLoc = SMLoc::getFromPointer(CheckRegion.data());
+ unsigned ActualLineNo = OverwriteActualLine;
-unsigned ActualLineNo = OverwriteActualLine;
+ if (ActualLineNo == 0) {
+ bool FoundFuzzy = false;
-if (ActualLineNo == 0) {
- bool FoundFuzzy = false;
-
- if (Diags) {
- for (const auto &D : llvm::reverse(*Diags)) {
- if (D.CheckLoc == PatternLoc &&
- D.MatchTy == FileCheckDiag::MatchFuzzy) {
- ActualLineNo = D.InputStartLine;
- FoundFuzzy = true;
- break;
+ if (Diags) {
+ for (const auto &D : llvm::reverse(*Diags)) {
+ if (D.CheckLoc == PatternLoc &&
+ D.MatchTy == FileCheckDiag::MatchFuzzy) {
+ ActualLineNo = D.InputStartLine;
+ FoundFuzzy = true;
+ break;
+ }
}
}
- }
- // If no diagnostic match was found, calculate the line number directly
- // from the InputLoc pointer using the SourceManager.
- if (ActualLineNo == 0)
- ActualLineNo = SM.getLineAndColumn(InputLoc).first;
+ // If no Fuzzy match was found, calculate the line number directly
+ // from the InputLoc pointer using the SourceManager.
+ if (ActualLineNo == 0)
+ ActualLineNo = SM.getLineAndColumn(InputLoc).first;
- // if we are at an empty line, usually the relevant context is the line just
- // before it.
- if (ActualLine.empty() && ActualLineNo > 1)
+ // if we are at an empty line (and not from fuzzy), usually the relevant
+ // context is the line just before it.
+ if (!FoundFuzzy && ActualLine.empty() && ActualLineNo > 1)
ActualLineNo--;
}
@@ -2971,6 +2970,7 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
bool ChecksFailed = false;
unsigned TotalMismatches = 0;
bool HeaderPrinted = false;
+ bool IsDiffMode = Req.DiffMode != DiffFormatType::Standard;
auto &OS = llvm::errs();
unsigned i = 0, j = 0, e = CheckStrings.size();
@@ -3004,49 +3004,38 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
if (i != 0 && Req.EnableVarScope)
PatternContext->clearLocalVars();
+ // Check each string within the scanned region, including a second check
+ // of any final CHECK-LABEL (to verify CHECK-NOT and CHECK-DAG)
for (; i != j; ++i) {
const FileCheckString &CheckStr = CheckStrings[i];
- // Check each string within the scanned region, including a second check
- // of any final CHECK-LABEL (to verify CHECK-NOT and CHECK-DAG)
- size_t MatchLen = 0;
- size_t MatchPos = StringRef::npos;
- // Determine if the pattern requires strict adjacency (CHECK-NEXT/EMPTY).
bool IsStrict = CheckStr.Pat.getCheckTy() == Check::CheckNext ||
CheckStr.Pat.getCheckTy() == Check::CheckEmpty;
+ bool AllowSearch = Req.DiffMode != DiffFormatType::Standard && !IsStrict;
- if (Req.DiffMode != DiffFormatType::Standard) {
- // Try to match the pattern (Search only if not strict)
- MatchPos =
- CheckStr.Check(SM, CheckRegion, !IsStrict, MatchLen, Req, Diags);
- } else {
- MatchPos = CheckStr.Check(SM, CheckRegion, false, MatchLen, Req, Diags);
- }
+ // Match the pattern and return its position within the current region.
+ // (Searches only if not strict and doing diff)
+ size_t MatchLen = 0;
+ size_t MatchPos =
+ CheckStr.Check(SM, CheckRegion, AllowSearch, MatchLen, Req, Diags);
- if (Req.DiffMode != DiffFormatType::Standard) {
- if (MatchPos == StringRef::npos) {
- // Case 1: No match at all. Always diff.
+ // Handle failure
+ if (MatchPos == StringRef::npos) {
+ if (IsDiffMode) {
handleDiffFailure(CheckStr, CheckRegion, SM, Req, Diags, OS,
HeaderPrinted, TotalMismatches);
- ChecksFailed = true;
- i = j;
- break;
- } else if (IsStrict && MatchPos > 0) {
- // Case 2: Match found, but is it "Strictly" next?
- // Check if the skipped characters are JUST whitespace/newlines.
- StringRef Skipped = CheckRegion.slice(0, MatchPos);
- if (!Skipped.trim().empty()) {
- // There is actual text (noise) between the last match and this one.
- handleDiffFailure(CheckStr, CheckRegion, SM, Req, Diags, OS,
- HeaderPrinted, TotalMismatches);
- ChecksFailed = true;
- i = j;
- break;
- }
}
- } else {
- // Standard Mode Logic
- if (MatchPos == StringRef::npos) {
+ ChecksFailed = true;
+ i = j;
+ break;
+ }
+
+ // Extra strict check (only in diff mode)
+ if (IsDiffMode && IsStrict && MatchPos > 0) {
+ StringRef Skipped = CheckRegion.slice(0, MatchPos);
+ if (!Skipped.trim().empty()) {
+ handleDiffFailure(CheckStr, CheckRegion, SM, Req, Diags, OS,
+ HeaderPrinted, TotalMismatches);
ChecksFailed = true;
i = j;
break;
>From 943cc747d746fd8979ccdd61e937c178eafc9ad6 Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Fri, 27 Mar 2026 14:10:26 +0530
Subject: [PATCH 07/16] simplify handleDiffFailure [not for review)
---
llvm/lib/FileCheck/FileCheck.cpp | 134 ++++++------------
llvm/test/FileCheck/diff/diff-empty-check.txt | 19 +++
.../FileCheck/diff/diff-label-boundary.txt | 2 +-
llvm/test/FileCheck/diff/diff-label-next.txt | 2 +-
...ync-overlap.txt => diff-multi-failres.txt} | 0
...ultihunk.txt => diff-simple-multihunk.txt} | 0
6 files changed, 61 insertions(+), 96 deletions(-)
create mode 100644 llvm/test/FileCheck/diff/diff-empty-check.txt
rename llvm/test/FileCheck/diff/{diff-resync-overlap.txt => diff-multi-failres.txt} (100%)
rename llvm/test/FileCheck/diff/{diff-multihunk.txt => diff-simple-multihunk.txt} (100%)
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index b343a47bdadf9..f054a8c58737b 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -2792,7 +2792,7 @@ static void renderDiff(DiffFormatType Mode, unsigned ExpectedLineNo,
OS << "-" << ExpectedText << "\n";
OS.changeColor(raw_ostream::GREEN);
- OS << "+" << ActualLine << "\n";
+ OS << "+" << ActualLine.ltrim() << "\n";
OS.resetColor();
// After Context
@@ -2822,27 +2822,12 @@ static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
ExpectedText = FullExpectedLine.str();
// Resolve the Actual (Input) line number.
- // Priority: 1. OverwriteActualLine (explicit override)
- // 2. Fuzzy Match Diag (where FileCheck 'thinks' the line was)
- // 3. Direct pointer resolution via SourceMgr.
+ // Priority: 1. OverwriteActualLine (Found via Fuzzy match)
+ // 2. Direct pointer resolution via SourceMgr.
SMLoc InputLoc = SMLoc::getFromPointer(CheckRegion.data());
unsigned ActualLineNo = OverwriteActualLine;
- if (ActualLineNo == 0) {
- bool FoundFuzzy = false;
-
- if (Diags) {
- for (const auto &D : llvm::reverse(*Diags)) {
- if (D.CheckLoc == PatternLoc &&
- D.MatchTy == FileCheckDiag::MatchFuzzy) {
- ActualLineNo = D.InputStartLine;
- FoundFuzzy = true;
- break;
- }
- }
- }
-
// If no Fuzzy match was found, calculate the line number directly
// from the InputLoc pointer using the SourceManager.
if (ActualLineNo == 0)
@@ -2850,47 +2835,35 @@ static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
// if we are at an empty line (and not from fuzzy), usually the relevant
// context is the line just before it.
- if (!FoundFuzzy && ActualLine.empty() && ActualLineNo > 1)
+ if (ActualLine.empty() && ActualLineNo > 1)
ActualLineNo--;
- }
- // Gather contextual diff to print (a line above and a line below).
- unsigned BufID = SM.FindBufferContainingLoc(InputLoc);
- DiffContext Context = getDiffContext(SM, ActualLineNo, BufID);
+ // Gather contextual diff to print (a line above and a line below).
+ unsigned BufID = SM.FindBufferContainingLoc(InputLoc);
+ DiffContext Context = getDiffContext(SM, ActualLineNo, BufID);
- renderDiff(Mode, ExpectedLineNo, ActualLineNo, ActualLine, ExpectedText,
- Context, OverwriteActualLine);
+ renderDiff(Mode, ExpectedLineNo, ActualLineNo, ActualLine, ExpectedText,
+ Context, OverwriteActualLine);
- llvm::errs() << "\n";
- return true;
+ llvm::errs() << "\n";
+ return true;
}
-/// Handles a mismatch by attempting to resynchronize the pattern with the
-/// input.
-///
-/// When a pattern fails to match at the current location, this function
-/// explores several recovery strategies:
-/// 1. Stray Line Detection: Check if the pattern matches perfectly on the very
-/// next line (suggesting an unexpected line was inserted).
-/// 2. Search/Resync: Search forward in the region to find a perfect match
-/// later on (suggesting a block of unexpected code).
-/// 3. Fuzzy Matching: Use existing diagnostics to find "near misses" (typos)
-/// on the current or nearby lines.
-///
-/// It then calls \c renderDiff via \c printDiff and advances \p CheckRegion
-/// to the appropriate recovery point
+// 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) {
+ // Print headers once per file.
if (!HeaderPrinted) {
StringRef CheckFile =
SM.getMemoryBuffer(SM.getMainFileID())->getBufferIdentifier();
unsigned InputBufID =
SM.FindBufferContainingLoc(SMLoc::getFromPointer(CheckRegion.data()));
StringRef InputFile = SM.getMemoryBuffer(InputBufID)->getBufferIdentifier();
+
OS.changeColor(raw_ostream::WHITE, true);
OS << "--- " << CheckFile << "\n";
OS << "+++ " << InputFile << "\n";
@@ -2898,66 +2871,42 @@ static bool handleDiffFailure(const FileCheckString &CheckStr,
HeaderPrinted = true;
}
+ // Identify the line that failed to match.
CheckRegion = CheckRegion.ltrim("\n\r");
-
+ size_t EOL = CheckRegion.find('\n');
SMLoc CurrentLoc = SMLoc::getFromPointer(CheckRegion.data());
- unsigned CurrentLineNo = 0;
- if (!CheckRegion.empty())
- CurrentLineNo = SM.getLineAndColumn(CurrentLoc).first;
- size_t EOL = CheckRegion.find('\n');
- StringRef MismatchLine =
- (EOL == StringRef::npos) ? CheckRegion : CheckRegion.substr(0, EOL);
-
- // Try to find where this pattern actually appears next
- std::vector<FileCheckDiag> ResyncDiags;
- size_t ResyncMatchLen = 0;
- size_t ResyncPos = CheckStr.Check(SM, CheckRegion, /*Search=*/true,
- ResyncMatchLen, Req, &ResyncDiags);
-
- StringRef TargetLine = MismatchLine;
- unsigned TargetLineNo = CurrentLineNo;
- bool AdvanceToResync = false;
-
- if (ResyncPos == 0) {
- // Perfect match right here (usually triggered by CHECK-NEXT noise)
- TargetLine = MismatchLine;
- } else if (ResyncPos != StringRef::npos) {
- // Found it further down. Is it just one line away? (Stray line case)
- size_t NextLinePos = CheckRegion.find('\n');
- if (NextLinePos != StringRef::npos && ResyncPos == NextLinePos + 1) {
- // This is a stray line. Diff the expected vs the stray line, then skip
- // stray.
- TargetLine = MismatchLine;
- AdvanceToResync = false;
- } else if (CheckStr.Pat.getCheckTy() != llvm::Check::CheckNext) {
- // Regular CHECK: skip the "noise" and sync to the match
- SMLoc MatchLoc = SMLoc::getFromPointer(CheckRegion.data() + ResyncPos);
- TargetLineNo = SM.getLineAndColumn(MatchLoc).first;
- TargetLine = CheckRegion.substr(ResyncPos).split('\n').first;
- AdvanceToResync = true;
- }
- } else {
- // Fallback to Fuzzy matching if no perfect match exists
- for (const auto &D : llvm::reverse(ResyncDiags)) {
- if (D.MatchTy == FileCheckDiag::MatchFuzzy) {
+ StringRef TargetLine;
+ unsigned TargetLineNo = 0;
+
+ // Check if the existing diagnostics already found a fuzzy match.
+ if (Diags) {
+ for (const auto &D : llvm::reverse(*Diags)) {
+ if (D.CheckLoc == CheckStr.Pat.getLoc() &&
+ D.MatchTy == FileCheckDiag::MatchFuzzy) {
TargetLineNo = D.InputStartLine;
- SMLoc FuzzyLoc = SM.FindLocForLineAndColumn(
- SM.FindBufferContainingLoc(CurrentLoc), TargetLineNo, 1);
- TargetLine = StringRef(FuzzyLoc.getPointer()).split('\n').first.trim();
+ // Get the actual text of that fuzzy match from the SourceMgr
+ unsigned BufID = SM.FindBufferContainingLoc(CurrentLoc);
+ SMLoc FuzzyLoc = SM.FindLocForLineAndColumn(BufID, TargetLineNo, 1);
+ TargetLine = StringRef(FuzzyLoc.getPointer()).split('\n').first;
break;
}
}
}
- printDiff(Req.DiffMode, CheckStr, TargetLine, SM, Req, &ResyncDiags,
- TargetLineNo);
+ // If no fuzzy match was found by the engine, just use the next line.
+ if (TargetLine.empty()) {
+ TargetLine =
+ (EOL == StringRef::npos) ? CheckRegion : CheckRegion.substr(0, EOL);
+ TargetLineNo = SM.getLineAndColumn(CurrentLoc).first;
+ }
+
+ // Render the diff for this specific line.
+ printDiff(Req.DiffMode, CheckStr, TargetLine, SM, Req, Diags, TargetLineNo);
TotalMismatches++;
- // Updates CheckRegion to advance the search pointer past the error.
- if (AdvanceToResync && ResyncPos != StringRef::npos)
- CheckRegion = CheckRegion.substr(ResyncPos + ResyncMatchLen);
- else if (EOL != StringRef::npos)
+ // Advance CheckRegion past the current line to recover for the next CHECK.
+ if (EOL != StringRef::npos)
CheckRegion = CheckRegion.substr(EOL + 1);
else
CheckRegion = "";
@@ -3011,13 +2960,10 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
bool IsStrict = CheckStr.Pat.getCheckTy() == Check::CheckNext ||
CheckStr.Pat.getCheckTy() == Check::CheckEmpty;
- bool AllowSearch = Req.DiffMode != DiffFormatType::Standard && !IsStrict;
- // Match the pattern and return its position within the current region.
- // (Searches only if not strict and doing diff)
size_t MatchLen = 0;
size_t MatchPos =
- CheckStr.Check(SM, CheckRegion, AllowSearch, MatchLen, Req, Diags);
+ CheckStr.Check(SM, CheckRegion, false, MatchLen, Req, Diags);
// Handle failure
if (MatchPos == StringRef::npos) {
diff --git a/llvm/test/FileCheck/diff/diff-empty-check.txt b/llvm/test/FileCheck/diff/diff-empty-check.txt
new file mode 100644
index 0000000000000..bba0b9993627d
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-empty-check.txt
@@ -0,0 +1,19 @@
+; 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
+START_TRANS
+ [LOG] Transaction initiated...
+END_TRANS
+
+;--- check.txt
+; CHECK: START_TRANS
+; CHECK-EMPTY:
+; CHECK: END_TRANS
+
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: START_TRANS
+; DIFF-NEXT: -
+; DIFF-NEXT: +[LOG] Transaction initiated...
+; DIFF-NEXT: END_TRANS
diff --git a/llvm/test/FileCheck/diff/diff-label-boundary.txt b/llvm/test/FileCheck/diff/diff-label-boundary.txt
index 6381e2a71f82e..cb9595f760149 100644
--- a/llvm/test/FileCheck/diff/diff-label-boundary.txt
+++ b/llvm/test/FileCheck/diff/diff-label-boundary.txt
@@ -1,5 +1,5 @@
; RUN: split-file %s %t
-; RUN: not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
+; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
; RUN: | FileCheck %s --check-prefix=DIFF
;--- input.txt
diff --git a/llvm/test/FileCheck/diff/diff-label-next.txt b/llvm/test/FileCheck/diff/diff-label-next.txt
index f2ea934376d6e..240cd14596da1 100644
--- a/llvm/test/FileCheck/diff/diff-label-next.txt
+++ b/llvm/test/FileCheck/diff/diff-label-next.txt
@@ -1,5 +1,5 @@
; RUN: split-file %s %t
-; RUN: not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
+; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
; RUN: | FileCheck %s --check-prefix=DIFF
;--- input.txt
diff --git a/llvm/test/FileCheck/diff/diff-resync-overlap.txt b/llvm/test/FileCheck/diff/diff-multi-failres.txt
similarity index 100%
rename from llvm/test/FileCheck/diff/diff-resync-overlap.txt
rename to llvm/test/FileCheck/diff/diff-multi-failres.txt
diff --git a/llvm/test/FileCheck/diff/diff-multihunk.txt b/llvm/test/FileCheck/diff/diff-simple-multihunk.txt
similarity index 100%
rename from llvm/test/FileCheck/diff/diff-multihunk.txt
rename to llvm/test/FileCheck/diff/diff-simple-multihunk.txt
>From d5453aa671d83e7134f9644f00113b818bc69178 Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Fri, 27 Mar 2026 15:36:49 +0530
Subject: [PATCH 08/16] sliced region [not for review]
---
llvm/lib/FileCheck/FileCheck.cpp | 17 ++++++----
llvm/test/FileCheck/diff/diff-multi-block.txt | 32 +++++++++++++++++++
2 files changed, 42 insertions(+), 7 deletions(-)
create mode 100644 llvm/test/FileCheck/diff/diff-multi-block.txt
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index f054a8c58737b..8fd8153311312 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -2773,7 +2773,7 @@ static DiffContext getDiffContext(SourceMgr &SM, unsigned LineNo,
// Unified View: A standard top-to-bottom (-Expected / +Actual) format.
static void renderDiff(DiffFormatType Mode, unsigned ExpectedLineNo,
unsigned ActualLineNo, StringRef ActualLine,
- const std::string &ExpectedText, const DiffContext &Ctx,
+ StringRef ExpectedText, const DiffContext &Ctx,
unsigned OverwriteActualLine) {
auto &OS = llvm::errs();
@@ -2816,10 +2816,7 @@ static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
SMLoc PatternLoc = CheckStr.Pat.getLoc();
unsigned ExpectedLineNo = SM.getLineAndColumn(PatternLoc).first;
const char *PatPtr = PatternLoc.getPointer();
- StringRef FullExpectedLine = StringRef(PatPtr).split('\n').first.trim();
-
- std::string ExpectedText;
- ExpectedText = FullExpectedLine.str();
+ StringRef ExpectedText = StringRef(PatPtr).split('\n').first.rtrim();
// Resolve the Actual (Input) line number.
// Priority: 1. OverwriteActualLine (Found via Fuzzy match)
@@ -2976,11 +2973,17 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
break;
}
- // Extra strict check (only in diff mode)
+ // Extra strict match (only in diff mode)
if (IsDiffMode && IsStrict && MatchPos > 0) {
StringRef Skipped = CheckRegion.slice(0, MatchPos);
if (!Skipped.trim().empty()) {
- handleDiffFailure(CheckStr, CheckRegion, SM, Req, Diags, OS,
+ // Create a temporary view that starts with next new line.
+ size_t CurrentLineEnd = CheckRegion.find_first_of("\n\r");
+ StringRef NextLineRegion =
+ (CurrentLineEnd != StringRef::npos)
+ ? CheckRegion.drop_front(CurrentLineEnd + 1).ltrim(" \t\n\r")
+ : CheckRegion.ltrim(" \t\n\r");
+ handleDiffFailure(CheckStr, NextLineRegion, SM, Req, Diags, OS,
HeaderPrinted, TotalMismatches);
ChecksFailed = true;
i = j;
diff --git a/llvm/test/FileCheck/diff/diff-multi-block.txt b/llvm/test/FileCheck/diff/diff-multi-block.txt
new file mode 100644
index 0000000000000..2a53fb6fdd9c4
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-multi-block.txt
@@ -0,0 +1,32 @@
+; 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
+define void @func_a() {
+ %result = add i32 %1, %2
+ ret void
+}
+define void @func_b() {
+ %val = load i32, ptr %p
+ store i32 %val, ptr %q
+ ret void
+}
+
+;--- check.txt
+; CHECK-LABEL: define void @func_a()
+; CHECK-NEXT: %result = mul i32 %1, %2
+; CHECK-LABEL: define void @func_b()
+; CHECK-NEXT: store i32 %val, ptr %q
+
+; DIFF: --- {{.*}}check.txt
+; DIFF-NEXT: +++ {{.*}}input.txt
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: define void @func_a() {
+; DIFF-NEXT: -%result = mul i32 %1, %2
+; DIFF-NEXT: +%result = add i32 %1, %2
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: define void @func_b() {
+; DIFF-NEXT: -store i32 %val, ptr %q
+; DIFF-NEXT: +%val = load i32, ptr %p
+; DIFF: FileCheck: Found 2 unique textual mismatches.
>From 0acc07000a13f66e62e7cf34f29ae8af215ad4ac Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Fri, 27 Mar 2026 16:35:30 +0530
Subject: [PATCH 09/16] fixup! clang-format
---
llvm/lib/FileCheck/FileCheck.cpp | 30 +++++++++++++++---------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index 8fd8153311312..a354b9d383b5f 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -2825,25 +2825,25 @@ static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
unsigned ActualLineNo = OverwriteActualLine;
- // If no Fuzzy match was found, calculate the line number directly
- // from the InputLoc pointer using the SourceManager.
- if (ActualLineNo == 0)
- ActualLineNo = SM.getLineAndColumn(InputLoc).first;
+ // If no Fuzzy match was found, calculate the line number directly
+ // from the InputLoc pointer using the SourceManager.
+ if (ActualLineNo == 0)
+ ActualLineNo = SM.getLineAndColumn(InputLoc).first;
- // if we are at an empty line (and not from fuzzy), usually the relevant
- // context is the line just before it.
- if (ActualLine.empty() && ActualLineNo > 1)
- ActualLineNo--;
+ // if we are at an empty line (and not from fuzzy), usually the relevant
+ // context is the line just before it.
+ if (ActualLine.empty() && ActualLineNo > 1)
+ ActualLineNo--;
- // Gather contextual diff to print (a line above and a line below).
- unsigned BufID = SM.FindBufferContainingLoc(InputLoc);
- DiffContext Context = getDiffContext(SM, ActualLineNo, BufID);
+ // Gather contextual diff to print (a line above and a line below).
+ unsigned BufID = SM.FindBufferContainingLoc(InputLoc);
+ DiffContext Context = getDiffContext(SM, ActualLineNo, BufID);
- renderDiff(Mode, ExpectedLineNo, ActualLineNo, ActualLine, ExpectedText,
- Context, OverwriteActualLine);
+ renderDiff(Mode, ExpectedLineNo, ActualLineNo, ActualLine, ExpectedText,
+ Context, OverwriteActualLine);
- llvm::errs() << "\n";
- return true;
+ llvm::errs() << "\n";
+ return true;
}
// Report the mismatch on the current line and advance to the next line.
>From 1ff91975f562fb1cbe1c2ba4f124a246053ef0e7 Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Fri, 27 Mar 2026 16:55:38 +0530
Subject: [PATCH 10/16] added multi-mismatch test case
---
.../FileCheck/diff/diff-multi-mismatch.txt | 36 +++++++++++++++++++
.../FileCheck/diff/diff-simple-multihunk.txt | 18 ----------
2 files changed, 36 insertions(+), 18 deletions(-)
create mode 100644 llvm/test/FileCheck/diff/diff-multi-mismatch.txt
delete mode 100644 llvm/test/FileCheck/diff/diff-simple-multihunk.txt
diff --git a/llvm/test/FileCheck/diff/diff-multi-mismatch.txt b/llvm/test/FileCheck/diff/diff-multi-mismatch.txt
new file mode 100644
index 0000000000000..ace3d7cd093bd
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-multi-mismatch.txt
@@ -0,0 +1,36 @@
+; 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
+BLOCK1_FAIL
+MATCH 1
+MATCH 2
+MATCH 3
+MATCH 4
+MATCH 5
+MATCH 6
+MATCH 7
+BLOCK2_FAIL
+
+;--- check.txt
+; CHECK: BLOCK1_PASS
+; CHECK: MATCH 1
+; CHECK: MATCH 2
+; CHECK: MATCH 3
+; CHECK: MATCH 4
+; CHECK: MATCH 5
+; CHECK: MATCH 6
+; CHECK: MATCH 7
+; CHECK: BLOCK2_PASS
+
+; First Hunk
+; DIFF: @@ -[[#]] +[[#]] @@
+; DIFF-NEXT: -BLOCK1_PASS
+; DIFF-NEXT: +BLOCK1_FAIL
+; DIFF-NEXT: MATCH 1
+
+; FIXME: We should continue to report failure even after first.
+; Second Hunk
+; DIFF-NOT: -BLOCK2_PASS
+; DIFF-NOT: +BLOCK2_FAIL
diff --git a/llvm/test/FileCheck/diff/diff-simple-multihunk.txt b/llvm/test/FileCheck/diff/diff-simple-multihunk.txt
deleted file mode 100644
index aa78446b0604d..0000000000000
--- a/llvm/test/FileCheck/diff/diff-simple-multihunk.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-; 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
-BLOCK1_FAIL
-KEEP_THIS
-BLOCK2_FAIL
-
-;--- check.txt
-; CHECK: BLOCK1_PASS
-; CHECK: KEEP_THIS
-; CHECK: BLOCK2_PASS
-
-; DIFF: @@ -[[#]] +1 @@
-; DIFF-NEXT: -BLOCK1_PASS
-; DIFF-NEXT: +BLOCK1_FAIL
-; DIFF-NEXT: KEEP_THIS
>From eea756688670905b53e050b4535d6ce7424752dc Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Fri, 27 Mar 2026 19:45:25 +0530
Subject: [PATCH 11/16] minor edits after rereviewing
---
llvm/lib/FileCheck/FileCheck.cpp | 42 +++++++++++++-------------------
1 file changed, 17 insertions(+), 25 deletions(-)
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index a354b9d383b5f..3cb1f71053e3c 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -2771,10 +2771,9 @@ static DiffContext getDiffContext(SourceMgr &SM, unsigned LineNo,
//
// Supported mode:
// Unified View: A standard top-to-bottom (-Expected / +Actual) format.
-static void renderDiff(DiffFormatType Mode, unsigned ExpectedLineNo,
- unsigned ActualLineNo, StringRef ActualLine,
- StringRef ExpectedText, const DiffContext &Ctx,
- unsigned OverwriteActualLine) {
+static void renderDiff(unsigned ExpectedLineNo, unsigned ActualLineNo,
+ StringRef ExpectedLine, StringRef ActualLine,
+ const DiffContext &Ctx) {
auto &OS = llvm::errs();
// Header
@@ -2789,7 +2788,7 @@ static void renderDiff(DiffFormatType Mode, unsigned ExpectedLineNo,
// Mismatch
OS.changeColor(raw_ostream::RED);
- OS << "-" << ExpectedText << "\n";
+ OS << "-" << ExpectedLine << "\n";
OS.changeColor(raw_ostream::GREEN);
OS << "+" << ActualLine.ltrim() << "\n";
@@ -2803,25 +2802,18 @@ static void renderDiff(DiffFormatType Mode, unsigned ExpectedLineNo,
// Prepares and prints a visual comparison between a CHECK pattern and the
// input.
-static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
- StringRef CheckRegion, SourceMgr &SM,
- const FileCheckRequest &Req,
- std::vector<FileCheckDiag> *Diags,
+static bool printDiff(const FileCheckString &CheckStr, StringRef ActualLine,
+ SourceMgr &SM, std::vector<FileCheckDiag> *Diags,
unsigned OverwriteActualLine = 0) {
- StringRef ActualLine = CheckRegion.split('\n').first;
- if (CheckRegion.empty()) {
- ActualLine = "";
- }
-
SMLoc PatternLoc = CheckStr.Pat.getLoc();
unsigned ExpectedLineNo = SM.getLineAndColumn(PatternLoc).first;
const char *PatPtr = PatternLoc.getPointer();
- StringRef ExpectedText = StringRef(PatPtr).split('\n').first.rtrim();
+ StringRef ExpectedLine = StringRef(PatPtr).split('\n').first.rtrim();
// Resolve the Actual (Input) line number.
// Priority: 1. OverwriteActualLine (Found via Fuzzy match)
// 2. Direct pointer resolution via SourceMgr.
- SMLoc InputLoc = SMLoc::getFromPointer(CheckRegion.data());
+ SMLoc InputLoc = SMLoc::getFromPointer(ActualLine.data());
unsigned ActualLineNo = OverwriteActualLine;
@@ -2839,8 +2831,7 @@ static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
unsigned BufID = SM.FindBufferContainingLoc(InputLoc);
DiffContext Context = getDiffContext(SM, ActualLineNo, BufID);
- renderDiff(Mode, ExpectedLineNo, ActualLineNo, ActualLine, ExpectedText,
- Context, OverwriteActualLine);
+ renderDiff(ExpectedLineNo, ActualLineNo, ExpectedLine, ActualLine, Context);
llvm::errs() << "\n";
return true;
@@ -2849,7 +2840,6 @@ static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
// 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) {
@@ -2899,7 +2889,7 @@ static bool handleDiffFailure(const FileCheckString &CheckStr,
}
// Render the diff for this specific line.
- printDiff(Req.DiffMode, CheckStr, TargetLine, SM, Req, Diags, TargetLineNo);
+ printDiff(CheckStr, TargetLine, SM, Diags, TargetLineNo);
TotalMismatches++;
// Advance CheckRegion past the current line to recover for the next CHECK.
@@ -2965,15 +2955,16 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
// Handle failure
if (MatchPos == StringRef::npos) {
if (IsDiffMode) {
- handleDiffFailure(CheckStr, CheckRegion, SM, Req, Diags, OS,
- HeaderPrinted, TotalMismatches);
+ handleDiffFailure(CheckStr, CheckRegion, SM, Diags, OS, HeaderPrinted,
+ TotalMismatches);
}
ChecksFailed = true;
i = j;
break;
}
-
- // Extra strict match (only in diff mode)
+ // For strict checks in Diff Mode, any skipped non-whitespace text is
+ // treated as a stray line. Even if a match is found later, we report
+ // the gap as a mismatch.
if (IsDiffMode && IsStrict && MatchPos > 0) {
StringRef Skipped = CheckRegion.slice(0, MatchPos);
if (!Skipped.trim().empty()) {
@@ -2983,13 +2974,14 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
(CurrentLineEnd != StringRef::npos)
? CheckRegion.drop_front(CurrentLineEnd + 1).ltrim(" \t\n\r")
: CheckRegion.ltrim(" \t\n\r");
- handleDiffFailure(CheckStr, NextLineRegion, SM, Req, Diags, OS,
+ handleDiffFailure(CheckStr, NextLineRegion, SM, Diags, OS,
HeaderPrinted, TotalMismatches);
ChecksFailed = true;
i = j;
break;
}
}
+
CheckRegion = CheckRegion.substr(MatchPos + MatchLen);
}
>From 2c375f87ad80351149db960513e5a20d35173102 Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Sat, 28 Mar 2026 01:04:34 +0530
Subject: [PATCH 12/16] fixup! add split-file dep for check-llvm-filecheck
---
llvm/test/CMakeLists.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/llvm/test/CMakeLists.txt b/llvm/test/CMakeLists.txt
index 34213cd18e38a..63d64ffc7b7fc 100644
--- a/llvm/test/CMakeLists.txt
+++ b/llvm/test/CMakeLists.txt
@@ -66,6 +66,7 @@ set(LLVM_TEST_DEPENDS_COMMON
count
llvm-config
not
+ split-file
)
set(LLVM_TEST_DEPENDS
>From 1f18bc4144c426aa056e9426f511c81a148edbf1 Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Sun, 29 Mar 2026 23:30:22 +0530
Subject: [PATCH 13/16] [FileCheck] Add split view diff option for FileCheck
---
llvm/docs/CommandGuide/FileCheck.rst | 1 +
llvm/include/llvm/FileCheck/FileCheck.h | 2 +-
llvm/lib/FileCheck/FileCheck.cpp | 106 +++++++++++++++---
.../FileCheck/diff/diff-label-fuzzy-match.txt | 19 +++-
.../FileCheck/diff/diff-long-line-trunc.txt | 12 ++
.../diff/diff-resync-after-noise.txt | 19 +++-
llvm/utils/FileCheck/FileCheck.cpp | 3 +-
7 files changed, 131 insertions(+), 31 deletions(-)
create mode 100644 llvm/test/FileCheck/diff/diff-long-line-trunc.txt
diff --git a/llvm/docs/CommandGuide/FileCheck.rst b/llvm/docs/CommandGuide/FileCheck.rst
index 1479c45046068..96d8de91946c5 100644
--- a/llvm/docs/CommandGuide/FileCheck.rst
+++ b/llvm/docs/CommandGuide/FileCheck.rst
@@ -118,6 +118,7 @@ and from the command line.
* ``standard`` – 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.
.. option:: --dump-input <value>
diff --git a/llvm/include/llvm/FileCheck/FileCheck.h b/llvm/include/llvm/FileCheck/FileCheck.h
index e5af0539eb1cf..6d7167eda3f32 100644
--- a/llvm/include/llvm/FileCheck/FileCheck.h
+++ b/llvm/include/llvm/FileCheck/FileCheck.h
@@ -28,7 +28,7 @@ class SourceMgr;
template <typename T> class SmallVectorImpl;
// Diff the output on failures.
-enum DiffFormatType { Standard, Unified };
+enum DiffFormatType { Standard, Unified, Split };
/// Contains info about various FileCheck options.
struct FileCheckRequest {
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index 3cb1f71053e3c..7d27d039a7fee 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -2767,14 +2767,51 @@ static DiffContext getDiffContext(SourceMgr &SM, unsigned LineNo,
getLineText(LineNo + 1)};
}
+// Extract a fixed-width "window" from string S, centered around DiffPos.
+// It ensures that the "point of divergence" is visible
+// within the provided Width. If the string is truncated, ellipses (...)
+// are inserted at the boundaries.
+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;
+}
+
// Renders a diagnostic diff to llvm::errs().
//
// Supported mode:
// Unified View: A standard top-to-bottom (-Expected / +Actual) format.
-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 = llvm::errs();
+ constexpr unsigned ColWidth = 45;
+ constexpr StringRef Sep = " | ";
+
+ // Identify the first index 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);
@@ -2783,27 +2820,60 @@ static void renderDiff(unsigned ExpectedLineNo, unsigned ActualLineNo,
// Before Context
if (!Ctx.LineBefore.empty()) {
- OS << " " << Ctx.LineBefore << "\n";
+ if (Mode == Split)
+ OS << " " << GetView(Ctx.LineBefore) << Sep << GetView(Ctx.LineBefore)
+ << "\n";
+ else
+ OS << " " << Ctx.LineBefore << "\n";
}
// Mismatch
- OS.changeColor(raw_ostream::RED);
- OS << "-" << ExpectedLine << "\n";
+ if (Mode == Split) {
+ 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";
}
}
// Prepares and prints a visual comparison between a CHECK pattern and the
// input.
-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;
@@ -2831,7 +2901,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, ExpectedLine, ActualLine,
+ Context);
llvm::errs() << "\n";
return true;
@@ -2840,6 +2911,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) {
@@ -2889,7 +2961,7 @@ static bool handleDiffFailure(const FileCheckString &CheckStr,
}
// Render the diff for this specific line.
- printDiff(CheckStr, TargetLine, SM, Diags, TargetLineNo);
+ printDiff(Req.DiffMode, CheckStr, TargetLine, SM, Diags, TargetLineNo);
TotalMismatches++;
// Advance CheckRegion past the current line to recover for the next CHECK.
@@ -2955,8 +3027,8 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
// Handle failure
if (MatchPos == StringRef::npos) {
if (IsDiffMode) {
- handleDiffFailure(CheckStr, CheckRegion, SM, Diags, OS, HeaderPrinted,
- TotalMismatches);
+ handleDiffFailure(CheckStr, CheckRegion, SM, Req, Diags, OS,
+ HeaderPrinted, TotalMismatches);
}
ChecksFailed = true;
i = j;
@@ -2974,7 +3046,7 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
(CurrentLineEnd != StringRef::npos)
? CheckRegion.drop_front(CurrentLineEnd + 1).ltrim(" \t\n\r")
: CheckRegion.ltrim(" \t\n\r");
- handleDiffFailure(CheckStr, NextLineRegion, SM, Diags, OS,
+ handleDiffFailure(CheckStr, NextLineRegion, SM, Req, Diags, OS,
HeaderPrinted, TotalMismatches);
ChecksFailed = true;
i = j;
diff --git a/llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt b/llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt
index a04f2e84ac70b..5f0eb0a204a8d 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-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/utils/FileCheck/FileCheck.cpp b/llvm/utils/FileCheck/FileCheck.cpp
index 210e2f82ba990..61add5fdcaafb 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -116,7 +116,8 @@ static cl::opt<bool> VerboseVerbose(
static cl::opt<DiffFormatType> DiffMode(
"diff", cl::desc("Show mismatches using a diff-style format.\n"),
cl::values(clEnumValN(Standard, "standard", "Outputs a Standard diff"),
- clEnumValN(Unified, "unidiff", "Outputs a Unified diff")));
+ clEnumValN(Unified, "unidiff", "Outputs a Unified diff"),
+ clEnumValN(Split, "split", "Outputs a Split diff")));
// The order of DumpInputValue members affects their precedence, as documented
// for -dump-input below.
>From aa1bc7cc15c7e99296454ec5d89704c45fa8671a Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Mon, 30 Mar 2026 00:02:04 +0530
Subject: [PATCH 14/16] [FileCheck] Add variable substitution for diff option
---
llvm/docs/CommandGuide/FileCheck.rst | 4 ++
llvm/include/llvm/FileCheck/FileCheck.h | 8 ++-
llvm/lib/FileCheck/FileCheck.cpp | 53 +++++++++++++++++--
llvm/lib/FileCheck/FileCheckImpl.h | 1 +
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 ++++++
.../FileCheck/diff/diff-substitutions.txt | 23 ++++++++
.../diff/diff-variable-chain-subs.txt | 18 +++++++
llvm/utils/FileCheck/FileCheck.cpp | 6 ++-
10 files changed, 154 insertions(+), 6 deletions(-)
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 96d8de91946c5..058ae2ddf831b 100644
--- a/llvm/docs/CommandGuide/FileCheck.rst
+++ b/llvm/docs/CommandGuide/FileCheck.rst
@@ -119,6 +119,10 @@ and from the command line.
* ``standard`` – 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 6d7167eda3f32..cb238da21f0dc 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 { Standard, Unified, Split };
+enum DiffFormatType {
+ Standard,
+ 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 7d27d039a7fee..660ea205baa5c 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -2792,6 +2792,42 @@ static std::string getCenteredView(StringRef S, size_t DiffPos, size_t Width) {
return View;
}
+std::string Pattern::getSubstitutedRegex(StringRef PatternText) const {
+ std::string Result = PatternText.str();
+
+ // We iterate through substitutions. Since they are stored in the order
+ // they appear in the pattern, we can 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 to llvm::errs().
//
// Supported mode:
@@ -2803,6 +2839,8 @@ static void renderDiff(DiffFormatType Mode, unsigned ExpectedLineNo,
constexpr unsigned ColWidth = 45;
constexpr StringRef Sep = " | ";
+ bool IsSplit = (Mode == Split || Mode == SplitNoSubstitution);
+
// Identify the first index where the expected and actual text diverge.
size_t DiffPos = 0;
size_t MinLen = std::min(ExpectedLine.size(), ActualLine.size());
@@ -2820,7 +2858,7 @@ static void renderDiff(DiffFormatType Mode, unsigned ExpectedLineNo,
// Before Context
if (!Ctx.LineBefore.empty()) {
- if (Mode == Split)
+ if (IsSplit)
OS << " " << GetView(Ctx.LineBefore) << Sep << GetView(Ctx.LineBefore)
<< "\n";
else
@@ -2828,7 +2866,7 @@ static void renderDiff(DiffFormatType Mode, unsigned ExpectedLineNo,
}
// Mismatch
- if (Mode == Split) {
+ if (IsSplit) {
OS << " ";
OS.changeColor(raw_ostream::RED);
@@ -2861,7 +2899,7 @@ static void renderDiff(DiffFormatType Mode, unsigned ExpectedLineNo,
// After Context
if (!Ctx.LineAfter.empty()) {
- if (Mode == Split)
+ if (IsSplit)
OS << " " << GetView(Ctx.LineAfter) << Sep << GetView(Ctx.LineAfter)
<< "\n";
else
@@ -2880,6 +2918,13 @@ static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
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.
@@ -2901,7 +2946,7 @@ static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr,
unsigned BufID = SM.FindBufferContainingLoc(InputLoc);
DiffContext Context = getDiffContext(SM, ActualLineNo, BufID);
- renderDiff(Mode, ExpectedLineNo, ActualLineNo, ExpectedLine, ActualLine,
+ renderDiff(Mode, ExpectedLineNo, ActualLineNo, ExpectedText, ActualLine,
Context);
llvm::errs() << "\n";
diff --git a/llvm/lib/FileCheck/FileCheckImpl.h b/llvm/lib/FileCheck/FileCheckImpl.h
index 58a03d280b658..855d451377b8e 100644
--- a/llvm/lib/FileCheck/FileCheckImpl.h
+++ b/llvm/lib/FileCheck/FileCheckImpl.h
@@ -739,6 +739,7 @@ class Pattern {
void printFuzzyMatch(const SourceMgr &SM, StringRef Buffer,
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-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-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 61add5fdcaafb..a14222cd33578 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -117,7 +117,11 @@ static cl::opt<DiffFormatType> DiffMode(
"diff", cl::desc("Show mismatches using a diff-style format.\n"),
cl::values(clEnumValN(Standard, "standard", "Outputs a Standard diff"),
clEnumValN(Unified, "unidiff", "Outputs a Unified diff"),
- clEnumValN(Split, "split", "Outputs a Split diff")));
+ 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 e69bd4a4f9a5407be2608d989034302e104ac033 Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Mon, 30 Mar 2026 16:07:51 +0530
Subject: [PATCH 15/16] [FileCheck] Add check-not support for diff option
---
llvm/lib/FileCheck/FileCheck.cpp | 87 +++++++++++++++------
llvm/test/FileCheck/diff/diff-check-not.txt | 21 +++++
2 files changed, 86 insertions(+), 22 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 660ea205baa5c..611c655763ff1 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -2033,6 +2033,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,
@@ -2062,6 +2067,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.DiffMode != DiffFormatType::Standard) {
+ 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.DiffMode, 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);
@@ -2909,11 +2936,29 @@ static void renderDiff(DiffFormatType Mode, unsigned ExpectedLineNo,
// Prepares and prints a visual comparison between a CHECK pattern and the
// input.
-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();
@@ -2923,7 +2968,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)
@@ -2960,21 +3005,6 @@ static bool handleDiffFailure(const FileCheckString &CheckStr,
std::vector<FileCheckDiag> *Diags,
raw_ostream &OS, bool &HeaderPrinted,
unsigned &TotalMismatches) {
- // Print headers once per file.
- if (!HeaderPrinted) {
- StringRef CheckFile =
- SM.getMemoryBuffer(SM.getMainFileID())->getBufferIdentifier();
- unsigned InputBufID =
- SM.FindBufferContainingLoc(SMLoc::getFromPointer(CheckRegion.data()));
- StringRef InputFile = SM.getMemoryBuffer(InputBufID)->getBufferIdentifier();
-
- OS.changeColor(raw_ostream::WHITE, true);
- OS << "--- " << CheckFile << "\n";
- OS << "+++ " << InputFile << "\n";
- OS.resetColor();
- HeaderPrinted = true;
- }
-
// Identify the line that failed to match.
CheckRegion = CheckRegion.ltrim("\n\r");
size_t EOL = CheckRegion.find('\n');
@@ -3006,7 +3036,8 @@ static bool handleDiffFailure(const FileCheckString &CheckStr,
}
// Render the diff for this specific line.
- printDiff(Req.DiffMode, CheckStr, TargetLine, SM, Diags, TargetLineNo);
+ printDiff(Req.DiffMode, CheckStr.Pat, TargetLine, SM, Diags, HeaderPrinted,
+ TargetLineNo);
TotalMismatches++;
// Advance CheckRegion past the current line to recover for the next CHECK.
@@ -3028,6 +3059,7 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
unsigned i = 0, j = 0, e = CheckStrings.size();
while (true) {
+
StringRef CheckRegion;
if (j == e) {
CheckRegion = Buffer;
@@ -3061,7 +3093,7 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
// of any final CHECK-LABEL (to verify CHECK-NOT and CHECK-DAG)
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;
@@ -3069,6 +3101,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 (IsDiffMode) {
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..eae025754401e
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-check-not.txt
@@ -0,0 +1,21 @@
+; 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: @@ -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.
>From 4f62880d9eaba92dd68ec428b635675f6e8396ca Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Mon, 30 Mar 2026 16:40:36 +0530
Subject: [PATCH 16/16] add file header to test case
---
llvm/test/FileCheck/diff/diff-check-not.txt | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/llvm/test/FileCheck/diff/diff-check-not.txt b/llvm/test/FileCheck/diff/diff-check-not.txt
index eae025754401e..7e25d0adec787 100644
--- a/llvm/test/FileCheck/diff/diff-check-not.txt
+++ b/llvm/test/FileCheck/diff/diff-check-not.txt
@@ -12,7 +12,9 @@ START
; CHECK-NOT: forbidden string
END
-; DIFF: @@ -2 +2 @@
+; 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
More information about the llvm-commits
mailing list