[llvm] [FileCheck] Add split view diff option for FileCheck (PR #189269)

Shivam Gupta via llvm-commits llvm-commits at lists.llvm.org
Sat Apr 18 00:55:14 PDT 2026


https://github.com/xgupta updated https://github.com/llvm/llvm-project/pull/189269

>From 18b0d1a65ac6c5e1fca94d26cf7f29684c4cf001 Mon Sep 17 00:00:00 2001
From: Shivam Gupta <shivam98.tkg at gmail.com>
Date: Sat, 18 Apr 2026 13:24:00 +0530
Subject: [PATCH] [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              | 93 ++++++++++++++++---
 .../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, 122 insertions(+), 27 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 d99cbbdd9f36f..ec787db7fbf02 100644
--- a/llvm/docs/CommandGuide/FileCheck.rst
+++ b/llvm/docs/CommandGuide/FileCheck.rst
@@ -117,6 +117,7 @@ and from the command line.
 
   * ``none`` – Use the standard FileCheck diagnostic messages.
   * ``unidiff`` – Display mismatches using a unified diff format.
+  * ``split`` – Display mismatches using a side-by-side split diff view.
 
 .. option:: --dump-input <value>
 
diff --git a/llvm/include/llvm/FileCheck/FileCheck.h b/llvm/include/llvm/FileCheck/FileCheck.h
index 7896a942a3466..84d006d616ddf 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 { None, Unified };
+enum DiffFormatType { None, 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 307be0b6e2faf..9e57f3efb1013 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -2762,11 +2762,45 @@ static DiffContext getDiffContext(SourceMgr &SM, unsigned LineNo,
           getLineText(LineNo + 1)};
 }
 
+// Extracts a fixed-width "window" from string S, centered around DiffPos.
+static std::string getCenteredView(StringRef S, size_t DiffPos, size_t Width) {
+  if (S.size() <= Width)
+    return S.str() + std::string(Width - S.size(), ' ');
+
+  size_t HalfWidth = Width / 2;
+  size_t Start = (DiffPos > HalfWidth) ? DiffPos - HalfWidth : 0;
+
+  if (Start + Width > S.size())
+    Start = (S.size() > Width) ? S.size() - Width : 0;
+
+  std::string View = S.substr(Start, Width).str();
+
+  if (Start > 0 && Width > 3)
+    View.replace(0, 3, "...");
+
+  if (Start + Width < S.size() && View.size() > 3)
+    View.replace(View.size() - 3, 3, "...");
+
+  return View;
+}
+
 // Renders a diagnostic diff via llvm::errs().
-static void renderDiff(unsigned ExpectedLineNo, unsigned ActualLineNo,
+static void renderDiff(DiffFormatType Mode, unsigned ExpectedLineNo, unsigned ActualLineNo,
                        StringRef ExpectedLine, StringRef ActualLine,
                        const DiffContext &Ctx) {
   auto &OS = errs();
+  constexpr unsigned ColWidth = 45;
+  constexpr StringRef Sep = " | ";
+
+  // Find the index from where the expected and actual text diverge.
+  size_t DiffPos = 0;
+  size_t MinLen = std::min(ExpectedLine.size(), ActualLine.size());
+  while (DiffPos < MinLen && ExpectedLine[DiffPos] == ActualLine[DiffPos])
+    ++DiffPos;
+
+  auto GetView = [&](StringRef S) {
+    return getCenteredView(S, DiffPos, ColWidth - 1);
+  };
 
   // Header
   OS.changeColor(raw_ostream::CYAN);
@@ -2775,24 +2809,56 @@ 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";
   }
 }
 
-static bool printDiff(const FileCheckString &CheckStr, StringRef ActualLine,
+static bool printDiff(DiffFormatType Mode, const FileCheckString &CheckStr, StringRef ActualLine,
                       SourceMgr &SM, std::vector<FileCheckDiag> *Diags,
                       unsigned OverwriteActualLine = 0) {
   SMLoc PatternLoc = CheckStr.Pat.getLoc();
@@ -2820,7 +2886,7 @@ 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);
 
   errs() << '\n';
   return true;
@@ -2829,6 +2895,7 @@ static bool printDiff(const FileCheckString &CheckStr, StringRef ActualLine,
 // Report the mismatch on the current line and advance to the next line.
 static bool handleDiffFailure(const FileCheckString &CheckStr,
                               StringRef &CheckRegion, SourceMgr &SM,
+                              FileCheckRequest &Req,
                               std::vector<FileCheckDiag> *Diags,
                               raw_ostream &OS, bool &HeaderPrinted,
                               unsigned &TotalMismatches) {
@@ -2874,7 +2941,7 @@ static bool handleDiffFailure(const FileCheckString &CheckStr,
     TargetLineNo = SM.getLineAndColumn(CurrentLoc).first;
   }
 
-  printDiff(CheckStr, TargetLine, SM, Diags, TargetLineNo);
+  printDiff(Req.DiffFormat, CheckStr, TargetLine, SM, Diags, TargetLineNo);
   TotalMismatches++;
 
   // Advance CheckRegion past the current line to recover for the next CHECK.
@@ -2940,7 +3007,7 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
       // Handle failure
       if (MatchPos == StringRef::npos) {
         if (IsDiffFormat) {
-          handleDiffFailure(CheckStr, CheckRegion, SM, Diags, OS, HeaderPrinted,
+          handleDiffFailure(CheckStr, CheckRegion, SM, Req, Diags, OS, HeaderPrinted,
                             TotalMismatches);
         }
         ChecksFailed = true;
@@ -2957,7 +3024,7 @@ bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
             (CurrentLineEnd != StringRef::npos)
                 ? CheckRegion.drop_front(CurrentLineEnd + 1)
                 : CheckRegion;
-        handleDiffFailure(CheckStr, NextLineRegion, SM, Diags, OS,
+        handleDiffFailure(CheckStr, NextLineRegion, SM, Req, Diags, OS,
                           HeaderPrinted, TotalMismatches);
         ChecksFailed = true;
         i = j;
diff --git a/llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt b/llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt
index a04f2e84ac70b..1a011a61032ca 100644
--- a/llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt
+++ b/llvm/test/FileCheck/diff/diff-label-fuzzy-match.txt
@@ -1,6 +1,8 @@
 ; RUN: split-file %s %t
 ; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=unidiff 2>&1 \
-; RUN:   | FileCheck %s --check-prefix=DIFF
+; RUN:   | FileCheck %s --check-prefix=UNI
+; RUN: %ProtectFileCheckOutput not FileCheck %t/check.txt --input-file=%t/input.txt --diff=split 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=SPLIT
 
 ;--- input.txt
 define void @foo() {
@@ -11,8 +13,13 @@ define void @foo() {
 ; CHECK-LABEL: define void @foo()
 ; CHECK:        call void @work_test()
 
-; DIFF:      @@ -[[#]] +[[#]] @@
-; DIFF:      define void @foo() {
-; DIFF-NEXT: -call void @work_test()
-; DIFF-NEXT: +call void @work_task()
-; DIFF-NEXT:  }
+; UNI:      @@ -[[#]] +[[#]] @@
+; UNI:      define void @foo() {
+; UNI-NEXT: -call void @work_test()
+; UNI-NEXT: +call void @work_task()
+; UNI-NEXT:  }
+
+; SPLIT:      @@ -[[#]] +[[#]] @@
+; SPLIT-NEXT: define void @foo() {              | define void @foo() {
+; SPLIT-NEXT: {{.*}}call void @work_test(){{.*}}|{{.*}}call void @work_task()
+; SPLIT-NEXT: }
diff --git a/llvm/test/FileCheck/diff/diff-long-line-trunc.txt b/llvm/test/FileCheck/diff/diff-long-line-trunc.txt
new file mode 100644
index 0000000000000..f854ff3956864
--- /dev/null
+++ b/llvm/test/FileCheck/diff/diff-long-line-trunc.txt
@@ -0,0 +1,12 @@
+; RUN: split-file %s %t
+; RUN: not FileCheck %t/check.txt --input-file=%t/input.txt --diff=split 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=DIFF
+
+;--- input.txt
+THIS_IS_A_VERY_LONG_PREFIX_THAT_SHOULD_BE_TRUNCATED_BY_THE_SLIDING_WINDOW_LOGIC_ACTUAL_VALUE
+
+;--- check.txt
+; CHECK: THIS_IS_A_VERY_LONG_PREFIX_THAT_SHOULD_BE_TRUNCATED_BY_THE_SLIDING_WINDOW_LOGIC_EXPECTED_VALUE
+
+; DIFF:      @@ -1 +1 @@
+; DIFF-NEXT: {{.*}}...{{.*}}WINDOW_LOGIC_EXPECTED_VALUE{{.*}}|{{.*}}...{{.*}}WINDOW_LOGIC_ACTUAL_VALUE
diff --git a/llvm/test/FileCheck/diff/diff-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 f200e94becaa2..f315fda533931 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -118,7 +118,8 @@ static cl::opt<DiffFormatType> DiffFormat(
     cl::ValueOptional,
     cl::values(clEnumValN(None, "none", "Standard FileCheck diagnostics"),
                clEnumValN(Unified, "unidiff", "Outputs a Unified diff"),
-               clEnumValN(Unified, "", "")));
+               clEnumValN(Unified, "", ""),
+               clEnumValN(Split, "split", "Outputs a Split diff")));
 
 // The order of DumpInputValue members affects their precedence, as documented
 // for -dump-input below.



More information about the llvm-commits mailing list