[llvm] [FileCheck][NFC] Complete FileCheckDiag class hierarchy (PR #195571)

Joel E. Denny via llvm-commits llvm-commits at lists.llvm.org
Wed May 6 08:15:07 PDT 2026


https://github.com/jdenny-ornl updated https://github.com/llvm/llvm-project/pull/195571

>From 0730d35e1fa8fa668624f6e2cc363b4389ef4959 Mon Sep 17 00:00:00 2001
From: "Joel E. Denny" <jdenny.ornl at gmail.com>
Date: Sun, 3 May 2026 17:56:44 -0400
Subject: [PATCH 01/10] [FileCheck][NFC] Introduce FileCheckDiagList for
 -dump-input

Problem
=======

`FileCheckDiag` and its `enum MatchType` have outgrown their original
purpose.  The `-dump-input` presentation layer (in
`llvm/utils/FileCheck/FileCheck.cpp`) and the FileCheck library's
diagnostic emission (in `llvm/lib/FileCheck/FileCheck.cpp`) are too
tightly coupled.  The interactions are subtle to understand and
maintain.  It is difficult for the former to reason about the latter's
emitted diagnostics in order to present them in the most readable
manner.

Solution
========

This patch is the first in an NFC series that removes `MatchType` from
`FileCheckDiag` and refactors `FileCheckDiag` as the base class of a
class hierarchy.  That class hierarchy is designed to enable the
FileCheck library to focus on communicating FileCheck diagnostic
information clearly and completely without participating in the
specific presentation decisions of `-dump-input`.  `-dump-input` and
any future diagnostic presentation layer that FileCheck might grow are
then freer to evolve more independently in the way they reason about
emitted diagnostics.  Moreover, the hierarchy enables various
`FileCheckDiag` objects not to carry various data not needed for the
diagnostics they describe.

This Patch
==========

The main goal of this particular patch is to introduce the
`FileCheckDiagList` class and to separate mostly mechanical changes
from future patches to make them more readable.  However, by itself,
it does offer some small improvements: it encapsulates the
`adjustPrevDiags` functionality, and it replaces the cryptic
`ProcessMatchResult` function with more readable code.

Why now?
========

FileCheck's input dumps have usability issues, including confusing
verbosity and the need to look in the test file to see directive
patterns.  There has been a recent push in issue #77257 and in PRs
linked from there to address these issues.  This patch series is a
first step in the most recent strategy discussed there.  Actually, it
is an older strategy that was started in
[D126673](https://reviews.llvm.org/D126673) but never completed.  The
current patch series updates D126673 and splits it apart to facilitate
the review.  The actual usability improvements are implemented by
patches that build on this series.
---
 llvm/include/llvm/FileCheck/FileCheck.h    | 26 +++++-
 llvm/lib/FileCheck/FileCheck.cpp           | 99 +++++++++++-----------
 llvm/lib/FileCheck/FileCheckImpl.h         | 18 ++--
 llvm/unittests/FileCheck/FileCheckTest.cpp | 28 +++---
 llvm/utils/FileCheck/FileCheck.cpp         | 61 +++++++------
 5 files changed, 124 insertions(+), 108 deletions(-)

diff --git a/llvm/include/llvm/FileCheck/FileCheck.h b/llvm/include/llvm/FileCheck/FileCheck.h
index b44ed8ed3f839..07684e6fc6cff 100644
--- a/llvm/include/llvm/FileCheck/FileCheck.h
+++ b/llvm/include/llvm/FileCheck/FileCheck.h
@@ -174,6 +174,30 @@ struct FileCheckDiag {
                          StringRef Note = "");
 };
 
+/// A \c FileCheckDiag series emitted by the FileCheck library.
+class FileCheckDiagList {
+private:
+  std::vector<std::unique_ptr<FileCheckDiag>> DiagList;
+
+public:
+  /// Emplace a new \c FileCheckDiag.
+  template <typename... ArgTys> void emplace_back(const ArgTys &...Args) {
+    DiagList.emplace_back(std::make_unique<FileCheckDiag>(Args...));
+  }
+  /// Adjust recent consecutive diagnostics of the same \c CheckLoc to have
+  /// \c MatchTy.
+  void adjustPrevDiags(FileCheckDiag::MatchType MatchTy) {
+    SMLoc CheckLoc = (*DiagList.rbegin())->CheckLoc;
+    for (auto I = DiagList.rbegin(), E = DiagList.rend();
+         I != E && (*I)->CheckLoc == CheckLoc; ++I)
+      (*I)->MatchTy = MatchTy;
+  }
+  /// The \c FileCheckDiag list.
+  const std::vector<std::unique_ptr<FileCheckDiag>> &getList() const {
+    return DiagList;
+  }
+};
+
 class FileCheckPatternContext;
 struct FileCheckString;
 
@@ -211,7 +235,7 @@ class FileCheck {
   ///
   /// \returns false if the input fails to satisfy the checks.
   LLVM_ABI bool checkInput(SourceMgr &SM, StringRef Buffer,
-                           std::vector<FileCheckDiag> *Diags = nullptr);
+                           FileCheckDiagList *Diags = nullptr);
 };
 
 } // namespace llvm
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index d50e5d9cb088b..7fd672b978e36 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -1261,7 +1261,7 @@ unsigned Pattern::computeMatchDistance(StringRef Buffer) const {
 void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
                                  SMRange Range,
                                  FileCheckDiag::MatchType MatchTy,
-                                 std::vector<FileCheckDiag> *Diags) const {
+                                 FileCheckDiagList *Diags) const {
   // Print what we know about substitutions.
   if (!Substitutions.empty()) {
     for (const auto &Substitution : Substitutions) {
@@ -1295,7 +1295,7 @@ void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
 
 void Pattern::printVariableDefs(const SourceMgr &SM,
                                 FileCheckDiag::MatchType MatchTy,
-                                std::vector<FileCheckDiag> *Diags) const {
+                                FileCheckDiagList *Diags) const {
   if (VariableDefs.empty() && NumericVariableDefs.empty())
     return;
   // Build list of variable captures.
@@ -1346,29 +1346,18 @@ void Pattern::printVariableDefs(const SourceMgr &SM,
   }
 }
 
-static SMRange ProcessMatchResult(FileCheckDiag::MatchType MatchTy,
-                                  const SourceMgr &SM, SMLoc Loc,
-                                  Check::FileCheckType CheckTy,
-                                  StringRef Buffer, size_t Pos, size_t Len,
-                                  std::vector<FileCheckDiag> *Diags,
-                                  bool AdjustPrevDiags = false) {
-  SMLoc Start = SMLoc::getFromPointer(Buffer.data() + Pos);
-  SMLoc End = SMLoc::getFromPointer(Buffer.data() + Pos + Len);
-  SMRange Range(Start, End);
-  if (Diags) {
-    if (AdjustPrevDiags) {
-      SMLoc CheckLoc = Diags->rbegin()->CheckLoc;
-      for (auto I = Diags->rbegin(), E = Diags->rend();
-           I != E && I->CheckLoc == CheckLoc; ++I)
-        I->MatchTy = MatchTy;
-    } else
-      Diags->emplace_back(SM, CheckTy, Loc, MatchTy, Range);
-  }
-  return Range;
+static SMRange buildMatchRange(StringRef Buffer, size_t Pos, size_t Len) {
+  return SMRange(SMLoc::getFromPointer(Buffer.data() + Pos),
+                 SMLoc::getFromPointer(Buffer.data() + Pos + Len));
+}
+
+static SMRange buildSearchRange(StringRef Buffer) {
+  return SMRange(SMLoc::getFromPointer(Buffer.data()),
+                 SMLoc::getFromPointer(Buffer.data() + Buffer.size()));
 }
 
 void Pattern::printFuzzyMatch(const SourceMgr &SM, StringRef Buffer,
-                              std::vector<FileCheckDiag> *Diags) const {
+                              FileCheckDiagList *Diags) 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
@@ -1408,9 +1397,10 @@ void Pattern::printFuzzyMatch(const SourceMgr &SM, StringRef Buffer,
   // reasonable and not equal to what we showed in the "scanning from here"
   // line.
   if (Best && Best != StringRef::npos && BestQuality < 50) {
-    SMRange MatchRange =
-        ProcessMatchResult(FileCheckDiag::MatchFuzzy, SM, getLoc(),
-                           getCheckTy(), Buffer, Best, 0, Diags);
+    SMRange MatchRange = buildMatchRange(Buffer, Best, 0);
+    if (Diags)
+      Diags->emplace_back(SM, getCheckTy(), getLoc(), FileCheckDiag::MatchFuzzy,
+                          MatchRange);
     SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note,
                     "possible intended match here");
 
@@ -2033,8 +2023,7 @@ static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
                         StringRef Prefix, SMLoc Loc, const Pattern &Pat,
                         int MatchedCount, StringRef Buffer,
                         Pattern::MatchResult MatchResult,
-                        const FileCheckRequest &Req,
-                        std::vector<FileCheckDiag> *Diags) {
+                        const FileCheckRequest &Req, FileCheckDiagList *Diags) {
   // Suppress some verbosity if there's no error.
   bool HasError = !ExpectedMatch || MatchResult.TheError;
   bool PrintDiag = true;
@@ -2053,10 +2042,10 @@ static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
   FileCheckDiag::MatchType MatchTy = ExpectedMatch
                                          ? FileCheckDiag::MatchFoundAndExpected
                                          : FileCheckDiag::MatchFoundButExcluded;
-  SMRange MatchRange = ProcessMatchResult(MatchTy, SM, Loc, Pat.getCheckTy(),
-                                          Buffer, MatchResult.TheMatch->Pos,
-                                          MatchResult.TheMatch->Len, Diags);
+  SMRange MatchRange = buildMatchRange(Buffer, MatchResult.TheMatch->Pos,
+                                       MatchResult.TheMatch->Len);
   if (Diags) {
+    Diags->emplace_back(SM, Pat.getCheckTy(), Loc, MatchTy, MatchRange);
     Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, Diags);
     Pat.printVariableDefs(SM, MatchTy, Diags);
   }
@@ -2102,8 +2091,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,
-                          std::vector<FileCheckDiag> *Diags) {
+                          bool VerboseVerbose, FileCheckDiagList *Diags) {
   // Print any pattern errors, and record them to be added to Diags later.
   bool HasError = ExpectedMatch;
   bool HasPatternError = false;
@@ -2141,9 +2129,9 @@ static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM,
   // errors.  The reason is that we need to attach pattern errors as notes
   // somewhere in the input, and the input search range from the "not found"
   // diagnostic is all we have to anchor them.
-  SMRange SearchRange = ProcessMatchResult(MatchTy, SM, Loc, Pat.getCheckTy(),
-                                           Buffer, 0, Buffer.size(), Diags);
+  SMRange SearchRange = buildSearchRange(Buffer);
   if (Diags) {
+    Diags->emplace_back(SM, Pat.getCheckTy(), Loc, MatchTy, SearchRange);
     SMRange NoteRange = SMRange(SearchRange.Start, SearchRange.Start);
     for (StringRef ErrorMsg : ErrorMsgs)
       Diags->emplace_back(SM, Pat.getCheckTy(), Loc, MatchTy, NoteRange,
@@ -2187,7 +2175,7 @@ static Error reportMatchResult(bool ExpectedMatch, const SourceMgr &SM,
                                int MatchedCount, StringRef Buffer,
                                Pattern::MatchResult MatchResult,
                                const FileCheckRequest &Req,
-                               std::vector<FileCheckDiag> *Diags) {
+                               FileCheckDiagList *Diags) {
   if (MatchResult.TheMatch)
     return printMatch(ExpectedMatch, SM, Prefix, Loc, Pat, MatchedCount, Buffer,
                       std::move(MatchResult), Req, Diags);
@@ -2222,7 +2210,7 @@ static unsigned CountNumNewlinesBetween(StringRef Range,
 size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer,
                               bool IsLabelScanMode, size_t &MatchLen,
                               FileCheckRequest &Req,
-                              std::vector<FileCheckDiag> *Diags) const {
+                              FileCheckDiagList *Diags) const {
   size_t LastPos = 0;
   std::vector<const DagNotPrefixInfo *> NotStrings;
 
@@ -2276,18 +2264,30 @@ 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)) {
-      ProcessMatchResult(FileCheckDiag::MatchFoundButWrongLine, SM, Loc,
-                         Pat.getCheckTy(), MatchBuffer, MatchPos, MatchLen,
-                         Diags, Req.Verbose);
+      if (Diags) {
+        if (Req.Verbose) {
+          Diags->adjustPrevDiags(FileCheckDiag::MatchFoundButWrongLine);
+        } else {
+          Diags->emplace_back(SM, Pat.getCheckTy(), Loc,
+                              FileCheckDiag::MatchFoundButWrongLine,
+                              buildMatchRange(MatchBuffer, MatchPos, MatchLen));
+        }
+      }
       return StringRef::npos;
     }
 
     // If this check is a "CHECK-SAME", verify that the previous match was on
     // the same line (i.e. that there is no newline between them).
     if (CheckSame(SM, SkippedRegion)) {
-      ProcessMatchResult(FileCheckDiag::MatchFoundButWrongLine, SM, Loc,
-                         Pat.getCheckTy(), MatchBuffer, MatchPos, MatchLen,
-                         Diags, Req.Verbose);
+      if (Diags) {
+        if (Req.Verbose) {
+          Diags->adjustPrevDiags(FileCheckDiag::MatchFoundButWrongLine);
+        } else {
+          Diags->emplace_back(SM, Pat.getCheckTy(), Loc,
+                              FileCheckDiag::MatchFoundButWrongLine,
+                              buildMatchRange(MatchBuffer, MatchPos, MatchLen));
+        }
+      }
       return StringRef::npos;
     }
 
@@ -2364,7 +2364,7 @@ bool FileCheckString::CheckSame(const SourceMgr &SM, StringRef Buffer) const {
 bool FileCheckString::CheckNot(
     const SourceMgr &SM, StringRef Buffer,
     const std::vector<const DagNotPrefixInfo *> &NotStrings,
-    const FileCheckRequest &Req, std::vector<FileCheckDiag> *Diags) const {
+    const FileCheckRequest &Req, FileCheckDiagList *Diags) const {
   bool DirectiveFail = false;
   for (auto NotInfo : NotStrings) {
     assert((NotInfo->DagNotPat.getCheckTy() == Check::CheckNot) &&
@@ -2386,7 +2386,7 @@ size_t
 FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer,
                           std::vector<const DagNotPrefixInfo *> &NotStrings,
                           const FileCheckRequest &Req,
-                          std::vector<FileCheckDiag> *Diags) const {
+                          FileCheckDiagList *Diags) const {
   if (DagNotStrings.empty())
     return 0;
 
@@ -2475,18 +2475,15 @@ FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer,
         // Due to their verbosity, we don't print verbose diagnostics here if
         // we're gathering them for a different rendering, but we always print
         // other diagnostics.
-        if (!Diags) {
+        if (Diags) {
+          Diags->adjustPrevDiags(FileCheckDiag::MatchFoundButDiscarded);
+        } else {
           SMLoc OldStart = SMLoc::getFromPointer(Buffer.data() + MI->Pos);
           SMLoc OldEnd = SMLoc::getFromPointer(Buffer.data() + MI->End);
           SMRange OldRange(OldStart, OldEnd);
           SM.PrintMessage(OldStart, SourceMgr::DK_Note,
                           "match discarded, overlaps earlier DAG match here",
                           {OldRange});
-        } else {
-          SMLoc CheckLoc = Diags->rbegin()->CheckLoc;
-          for (auto I = Diags->rbegin(), E = Diags->rend();
-               I != E && I->CheckLoc == CheckLoc; ++I)
-            I->MatchTy = FileCheckDiag::MatchFoundButDiscarded;
         }
       }
       MatchPos = MI->End;
@@ -2725,7 +2722,7 @@ void FileCheckPatternContext::clearLocalVars() {
 }
 
 bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer,
-                           std::vector<FileCheckDiag> *Diags) {
+                           FileCheckDiagList *Diags) {
   bool ChecksFailed = false;
 
   unsigned i = 0, j = 0, e = CheckStrings.size();
diff --git a/llvm/lib/FileCheck/FileCheckImpl.h b/llvm/lib/FileCheck/FileCheckImpl.h
index 5851cfc4b5d5c..547c93f2acca5 100644
--- a/llvm/lib/FileCheck/FileCheckImpl.h
+++ b/llvm/lib/FileCheck/FileCheckImpl.h
@@ -734,16 +734,16 @@ 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;
+                          FileCheckDiagList *Diags) const;
   void printFuzzyMatch(const SourceMgr &SM, StringRef Buffer,
-                       std::vector<FileCheckDiag> *Diags) const;
+                       FileCheckDiagList *Diags) const;
 
   bool hasVariable() const {
     return !(Substitutions.empty() && VariableDefs.empty());
   }
-  LLVM_ABI_FOR_TEST void
-  printVariableDefs(const SourceMgr &SM, FileCheckDiag::MatchType MatchTy,
-                    std::vector<FileCheckDiag> *Diags) const;
+  LLVM_ABI_FOR_TEST void printVariableDefs(const SourceMgr &SM,
+                                           FileCheckDiag::MatchType MatchTy,
+                                           FileCheckDiagList *Diags) const;
 
   Check::FileCheckType getCheckTy() const { return CheckTy; }
 
@@ -870,7 +870,7 @@ struct FileCheckString {
   /// Matches check string and its "not strings" and/or "dag strings".
   size_t Check(const SourceMgr &SM, StringRef Buffer, bool IsLabelScanMode,
                size_t &MatchLen, FileCheckRequest &Req,
-               std::vector<FileCheckDiag> *Diags) const;
+               FileCheckDiagList *Diags) const;
 
   /// Verifies that there is a single line in the given \p Buffer. Errors are
   /// reported against \p SM.
@@ -883,13 +883,11 @@ struct FileCheckString {
   /// \p Diags according to the verbosity level set in \p Req.
   bool CheckNot(const SourceMgr &SM, StringRef Buffer,
                 const std::vector<const DagNotPrefixInfo *> &NotStrings,
-                const FileCheckRequest &Req,
-                std::vector<FileCheckDiag> *Diags) const;
+                const FileCheckRequest &Req, FileCheckDiagList *Diags) const;
   /// Matches "dag strings" and their mixed "not strings".
   size_t CheckDag(const SourceMgr &SM, StringRef Buffer,
                   std::vector<const DagNotPrefixInfo *> &NotStrings,
-                  const FileCheckRequest &Req,
-                  std::vector<FileCheckDiag> *Diags) const;
+                  const FileCheckRequest &Req, FileCheckDiagList *Diags) const;
 };
 
 } // namespace llvm
diff --git a/llvm/unittests/FileCheck/FileCheckTest.cpp b/llvm/unittests/FileCheck/FileCheckTest.cpp
index 2c4130330e970..cff958a114c63 100644
--- a/llvm/unittests/FileCheck/FileCheckTest.cpp
+++ b/llvm/unittests/FileCheck/FileCheckTest.cpp
@@ -915,7 +915,7 @@ class PatternTester {
   }
 
   void printVariableDefs(FileCheckDiag::MatchType MatchTy,
-                         std::vector<FileCheckDiag> &Diags) {
+                         FileCheckDiagList &Diags) {
     P.printVariableDefs(SM, MatchTy, &Diags);
   }
 };
@@ -1641,20 +1641,20 @@ TEST_F(FileCheckTest, CapturedVarDiags) {
   PatternTester Tester;
   ASSERT_FALSE(Tester.parsePattern("[[STRVAR:[a-z]+]] [[#NUMVAR:@LINE]]"));
   EXPECT_THAT_EXPECTED(Tester.match("foobar 2"), Succeeded());
-  std::vector<FileCheckDiag> Diags;
+  FileCheckDiagList Diags;
   Tester.printVariableDefs(FileCheckDiag::MatchFoundAndExpected, Diags);
-  EXPECT_EQ(Diags.size(), 2ul);
-  for (const FileCheckDiag &Diag : Diags) {
-    EXPECT_EQ(Diag.CheckTy, Check::CheckPlain);
-    EXPECT_EQ(Diag.MatchTy, FileCheckDiag::MatchFoundAndExpected);
-    EXPECT_EQ(Diag.InputStartLine, 1u);
-    EXPECT_EQ(Diag.InputEndLine, 1u);
+  EXPECT_EQ(Diags.getList().size(), 2ul);
+  for (const std::unique_ptr<FileCheckDiag> &Diag : Diags.getList()) {
+    EXPECT_EQ(Diag->CheckTy, Check::CheckPlain);
+    EXPECT_EQ(Diag->MatchTy, FileCheckDiag::MatchFoundAndExpected);
+    EXPECT_EQ(Diag->InputStartLine, 1u);
+    EXPECT_EQ(Diag->InputEndLine, 1u);
   }
-  EXPECT_EQ(Diags[0].InputStartCol, 1u);
-  EXPECT_EQ(Diags[0].InputEndCol, 7u);
-  EXPECT_EQ(Diags[1].InputStartCol, 8u);
-  EXPECT_EQ(Diags[1].InputEndCol, 9u);
-  EXPECT_EQ(Diags[0].Note, "captured var \"STRVAR\"");
-  EXPECT_EQ(Diags[1].Note, "captured var \"NUMVAR\"");
+  EXPECT_EQ(Diags.getList()[0]->InputStartCol, 1u);
+  EXPECT_EQ(Diags.getList()[0]->InputEndCol, 7u);
+  EXPECT_EQ(Diags.getList()[1]->InputStartCol, 8u);
+  EXPECT_EQ(Diags.getList()[1]->InputEndCol, 9u);
+  EXPECT_EQ(Diags.getList()[0]->Note, "captured var \"STRVAR\"");
+  EXPECT_EQ(Diags.getList()[1]->Note, "captured var \"NUMVAR\"");
 }
 } // namespace
diff --git a/llvm/utils/FileCheck/FileCheck.cpp b/llvm/utils/FileCheck/FileCheck.cpp
index 12fdbfd45279d..6cf762bda68d0 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -377,9 +377,9 @@ static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) {
 }
 
 static void
-BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
+buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
                       const std::pair<unsigned, unsigned> &ImpPatBufferIDRange,
-                      const std::vector<FileCheckDiag> &Diags,
+                      const std::vector<std::unique_ptr<FileCheckDiag>> &Diags,
                       std::vector<InputAnnotation> &Annotations,
                       unsigned &LabelWidth) {
   struct CompareSMLoc {
@@ -389,25 +389,23 @@ BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
   };
   // How many diagnostics does each pattern have?
   std::map<SMLoc, unsigned, CompareSMLoc> DiagCountPerPattern;
-  for (const FileCheckDiag &Diag : Diags)
-    ++DiagCountPerPattern[Diag.CheckLoc];
+  for (const std::unique_ptr<FileCheckDiag> &Diag : Diags)
+    ++DiagCountPerPattern[Diag->CheckLoc];
   // How many diagnostics have we seen so far per pattern?
   std::map<SMLoc, unsigned, CompareSMLoc> DiagIndexPerPattern;
   // How many total diagnostics have we seen so far?
   unsigned DiagIndex = 0;
   // What's the widest label?
   LabelWidth = 0;
-  for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd;
-       ++DiagItr) {
+  for (const std::unique_ptr<FileCheckDiag> &Diag : Diags) {
     InputAnnotation A;
     A.DiagIndex = DiagIndex++;
 
     // Build label, which uniquely identifies this check result.
-    unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc);
-    auto CheckLineAndCol =
-        SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID);
+    unsigned CheckBufferID = SM.FindBufferContainingLoc(Diag->CheckLoc);
+    auto CheckLineAndCol = SM.getLineAndColumn(Diag->CheckLoc, CheckBufferID);
     llvm::raw_string_ostream Label(A.Label);
-    Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":";
+    Label << GetCheckTypeAbbreviation(Diag->CheckTy) << ":";
     if (CheckBufferID == CheckFileBufferID)
       Label << CheckLineAndCol.first;
     else if (ImpPatBufferIDRange.first <= CheckBufferID &&
@@ -416,53 +414,52 @@ BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
     else
       llvm_unreachable("expected diagnostic's check location to be either in "
                        "the check file or for an implicit pattern");
-    if (DiagCountPerPattern[DiagItr->CheckLoc] > 1)
-      Label << "'" << DiagIndexPerPattern[DiagItr->CheckLoc]++;
+    if (DiagCountPerPattern[Diag->CheckLoc] > 1)
+      Label << "'" << DiagIndexPerPattern[Diag->CheckLoc]++;
     LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size());
 
-    A.Marker = GetMarker(DiagItr->MatchTy);
-    if (!DiagItr->Note.empty()) {
-      A.Marker.Note = DiagItr->Note;
+    A.Marker = GetMarker(Diag->MatchTy);
+    if (!Diag->Note.empty()) {
+      A.Marker.Note = Diag->Note;
       // It's less confusing if notes that don't actually have ranges don't have
       // markers.  For example, a marker for 'with "VAR" equal to "5"' would
       // seem to indicate where "VAR" matches, but the location we actually have
       // for the marker simply points to the start of the match/search range for
       // the full pattern of which the substitution is potentially just one
       // component.
-      if (DiagItr->InputStartLine == DiagItr->InputEndLine &&
-          DiagItr->InputStartCol == DiagItr->InputEndCol)
+      if (Diag->InputStartLine == Diag->InputEndLine &&
+          Diag->InputStartCol == Diag->InputEndCol)
         A.Marker.Lead = ' ';
     }
-    if (DiagItr->MatchTy == FileCheckDiag::MatchFoundErrorNote) {
-      assert(!DiagItr->Note.empty() &&
+    if (Diag->MatchTy == FileCheckDiag::MatchFoundErrorNote) {
+      assert(!Diag->Note.empty() &&
              "expected custom note for MatchFoundErrorNote");
       A.Marker.Note = "error: " + A.Marker.Note;
     }
     A.FoundAndExpectedMatch =
-        DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected;
+        Diag->MatchTy == FileCheckDiag::MatchFoundAndExpected;
 
     // Compute the mark location, and break annotation into multiple
     // annotations if it spans multiple lines.
     A.IsFirstLine = true;
-    A.InputLine = DiagItr->InputStartLine;
-    A.InputStartCol = DiagItr->InputStartCol;
-    if (DiagItr->InputStartLine == DiagItr->InputEndLine) {
+    A.InputLine = Diag->InputStartLine;
+    A.InputStartCol = Diag->InputStartCol;
+    if (Diag->InputStartLine == Diag->InputEndLine) {
       // Sometimes ranges are empty in order to indicate a specific point, but
       // that would mean nothing would be marked, so adjust the range to
       // include the following character.
-      A.InputEndCol =
-          std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol);
+      A.InputEndCol = std::max(Diag->InputStartCol + 1, Diag->InputEndCol);
       Annotations.push_back(A);
     } else {
-      assert(DiagItr->InputStartLine < DiagItr->InputEndLine &&
+      assert(Diag->InputStartLine < Diag->InputEndLine &&
              "expected input range not to be inverted");
       A.InputEndCol = UINT_MAX;
       Annotations.push_back(A);
-      for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine;
+      for (unsigned L = Diag->InputStartLine + 1, E = Diag->InputEndLine;
            L <= E; ++L) {
         // If a range ends before the first column on a line, then it has no
         // characters on that line, so there's nothing to render.
-        if (DiagItr->InputEndCol == 1 && L == E)
+        if (Diag->InputEndCol == 1 && L == E)
           break;
         InputAnnotation B;
         B.DiagIndex = A.DiagIndex;
@@ -476,7 +473,7 @@ BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
         if (L != E)
           B.InputEndCol = UINT_MAX;
         else
-          B.InputEndCol = DiagItr->InputEndCol;
+          B.InputEndCol = Diag->InputEndCol;
         B.FoundAndExpectedMatch = A.FoundAndExpectedMatch;
         Annotations.push_back(B);
       }
@@ -853,7 +850,7 @@ int main(int argc, char **argv) {
                             InputFileText, InputFile.getBufferIdentifier()),
                         SMLoc());
 
-  std::vector<FileCheckDiag> Diags;
+  FileCheckDiagList Diags;
   int ExitCode = FC.checkInput(SM, InputFileText,
                                DumpInput == DumpInputNever ? nullptr : &Diags)
                      ? EXIT_SUCCESS
@@ -868,8 +865,8 @@ int main(int argc, char **argv) {
            << "\n";
     std::vector<InputAnnotation> Annotations;
     unsigned LabelWidth;
-    BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags,
-                          Annotations, LabelWidth);
+    buildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange,
+                          Diags.getList(), Annotations, LabelWidth);
     DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext,
                        InputFileText, Annotations, LabelWidth);
   }

>From 1f468c7903b13911e75c8704a129e8b6e86aa18b Mon Sep 17 00:00:00 2001
From: "Joel E. Denny" <jdenny.ornl at gmail.com>
Date: Sun, 3 May 2026 17:58:37 -0400
Subject: [PATCH 02/10] [FileCheck][NFC] Introduce MatchResultDiag and
 MatchNoteDiag

This patch depends on PR #???? and continues its effort to decouple
the `-dump-input` presentation layer (in
`llvm/utils/FileCheck/FileCheck.cpp`) and the FileCheck library's
diagnostic emission (in `llvm/lib/FileCheck/FileCheck.cpp`).

Similar to compiler errors/warnings/remarks vs. notes, the
`FileCheckDiag` series emitted by the FileCheck library contains match
results, each of which might be followed by a series of associated
notes before the next match result.  Without this patch series, that
association is not formally modeled by `FileCheckDiag` or clearly
documented, and `-dump-input` is not able to easily reason about it.

This patch improves the situation by introducing two `FileCheckDiag`
derived classes: `MatchResultDiag` and `MatchNoteDiag`.  It extends
`FileCheckDiagList` to directly associate each `MatchNoteDiag` with
its `MatchResultDiag`.  Thus:
- `FileCheckDiagList::adjustPrevDiags` no longer has to determine that
  association based on directive location info.
- Match result instances of `FileCheckDiag` no longer need to store a
  redundant custom note field, which only makes sense for notes.
- Note instances of `FileCheckDiag` no longer need to store the
  directive type and location, which are already stored by their match
  result instances.
- In both the FileCheck library and the `-dump-input` presentation
  layer, it is now clearer when a `FileCheckDiag` is a match result
  vs. a note.
---
 llvm/include/llvm/FileCheck/FileCheck.h    | 153 ++++++++++++++++-----
 llvm/lib/FileCheck/FileCheck.cpp           |  48 +++----
 llvm/unittests/FileCheck/FileCheckTest.cpp |  32 +++--
 llvm/utils/FileCheck/FileCheck.cpp         | 101 ++++++++------
 4 files changed, 221 insertions(+), 113 deletions(-)

diff --git a/llvm/include/llvm/FileCheck/FileCheck.h b/llvm/include/llvm/FileCheck/FileCheck.h
index 07684e6fc6cff..b171d1048cddb 100644
--- a/llvm/include/llvm/FileCheck/FileCheck.h
+++ b/llvm/include/llvm/FileCheck/FileCheck.h
@@ -14,6 +14,7 @@
 #define LLVM_FILECHECK_FILECHECK_H
 
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Casting.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/Regex.h"
 #include "llvm/Support/SMLoc.h"
@@ -110,24 +111,29 @@ class FileCheckType {
 };
 } // namespace Check
 
-/// Summary of a FileCheck diagnostic.
-struct FileCheckDiag {
-  /// What is the FileCheck directive for this diagnostic?
-  Check::FileCheckType CheckTy;
-  /// Where is the FileCheck directive for this diagnostic?
-  SMLoc CheckLoc;
+class MatchResultDiag;
+
+/// Abstract base class for recording a FileCheck diagnostic for a pattern
+/// (e.g., \c CHECK-NEXT directive or \c --implicit-check-not).
+///
+/// \c FileCheckDiag has two direct derived classes:
+/// - \c MatchResultDiag records a match result for a pattern.  There might be
+///   more than one for a single pattern.  For example, for \c CHECK-DAG there
+///   might be several discarded matches before either a good match or a failure
+///   to match.
+/// - \c MatchNoteDiag provides an additional note about the most recent
+///   \c MatchResultDiag emitted by a FileCheck invocation.  For example, there
+///   might be a fuzzy match after a failure to match.
+class FileCheckDiag {
+public:
+  enum FileCheckDiagKind { FCDK_MatchResultDiag, FCDK_MatchNoteDiag };
+
   /// What type of match result does this diagnostic describe?
   ///
   /// A directive's supplied pattern is said to be either expected or excluded
   /// depending on whether the pattern must have or must not have a match in
   /// order for the directive to succeed.  For example, a CHECK directive's
   /// pattern is expected, and a CHECK-NOT directive's pattern is excluded.
-  ///
-  /// There might be more than one match result for a single pattern.  For
-  /// example, there might be several discarded matches
-  /// (MatchFoundButDiscarded) before either a good match
-  /// (MatchFoundAndExpected) or a failure to match (MatchNoneButExpected),
-  /// and there might be a fuzzy match (MatchFuzzy) after the latter.
   enum MatchType {
     /// Indicates a good match for an expected pattern.
     MatchFoundAndExpected,
@@ -158,39 +164,122 @@ struct FileCheckDiag {
     /// Indicates a fuzzy match that serves as a suggestion for the next
     /// intended match for an expected pattern with too few or no good matches.
     MatchFuzzy,
-  } MatchTy;
+  };
+
+private:
+  const FileCheckDiagKind Kind;
+  MatchType MatchTy;
+  SMRange InputRange;
+
+public:
+  FileCheckDiag(FileCheckDiagKind Kind, MatchType MatchTy, SMRange InputRange)
+      : Kind(Kind), MatchTy(MatchTy), InputRange(InputRange) {}
+  /// Destructor is purely virtual to ensure this remains an abstract class.
+  virtual ~FileCheckDiag() = 0;
+  /// Of what derived class is this an instance?
+  FileCheckDiagKind getKind() const { return Kind; }
+  /// If this is a \c MatchResultDiag, return itself.  If this is a
+  /// \c MatchNoteDiag, return its associated \c MatchResultDiag.
+  virtual const MatchResultDiag &getMatchResultDiag() const = 0;
+  /// Adjust the match type.
+  void adjustMatchType(MatchType MatchTy) { this->MatchTy = MatchTy; }
+  /// Get the match type.
+  MatchType getMatchType() const { return MatchTy; }
   /// The search range if MatchTy starts with MatchNone, or the match range
   /// otherwise.
-  unsigned InputStartLine;
-  unsigned InputStartCol;
-  unsigned InputEndLine;
-  unsigned InputEndCol;
-  /// A note to replace the one normally indicated by MatchTy, or the empty
-  /// string if none.
-  std::string Note;
-  LLVM_ABI FileCheckDiag(const SourceMgr &SM,
-                         const Check::FileCheckType &CheckTy, SMLoc CheckLoc,
-                         MatchType MatchTy, SMRange InputRange,
-                         StringRef Note = "");
+  SMRange getInputRange() const { return InputRange; }
+};
+
+/// Class for recording a FileCheck diagnostic that reports a match result for a
+/// pattern.
+class MatchResultDiag : public FileCheckDiag {
+private:
+  Check::FileCheckType CheckTy;
+  SMLoc CheckLoc;
+
+public:
+  MatchResultDiag(const Check::FileCheckType &CheckTy, SMLoc CheckLoc,
+                  MatchType MatchTy, SMRange InputRange)
+      : FileCheckDiag(FCDK_MatchResultDiag, MatchTy, InputRange),
+        CheckTy(CheckTy), CheckLoc(CheckLoc) {}
+  /// Is \p FCD an instance of \c MatchResultDiag?
+  static bool classof(const FileCheckDiag *FCD) {
+    return FCD->getKind() == FCDK_MatchResultDiag;
+  }
+  /// Get itself.
+  virtual const MatchResultDiag &getMatchResultDiag() const override {
+    return *this;
+  }
+  /// What is the type of pattern for this match result?
+  Check::FileCheckType getCheckTy() const { return CheckTy; }
+  /// Where is the pattern for this match result?
+  SMLoc getCheckLoc() const { return CheckLoc; }
+};
+
+/// Class for recording a FileCheck diagnostic that provides an additional note
+/// (possibly an additional error) about the most recent \c MatchResultDiag.
+class MatchNoteDiag : public FileCheckDiag {
+private:
+  MatchResultDiag *MRD;
+  std::optional<std::string> CustomNote;
+
+public:
+  MatchNoteDiag(MatchType MatchTy, SMRange InputRange,
+                std::optional<StringRef> CustomNote = std::nullopt)
+      : FileCheckDiag(FCDK_MatchNoteDiag, MatchTy, InputRange), MRD(nullptr),
+        CustomNote(CustomNote) {}
+  /// Is \p FCD an instance of \c MatchNoteDiag?
+  static bool classof(const FileCheckDiag *FCD) {
+    return FCD->getKind() == FCDK_MatchNoteDiag;
+  }
+  /// Get the note's associated \c MatchResultDiag.
+  virtual const MatchResultDiag &getMatchResultDiag() const override {
+    return *MRD;
+  }
+  /// Set the note's associated \c MatchResultDiag.
+  void setMatchResultDiag(MatchResultDiag *MRDNew) {
+    assert(!MRD && "expected setMatchResultDiag to be called only once");
+    MRD = MRDNew;
+  }
+  /// A note to replace the one normally indicated by the match type, or the
+  /// empty string if none.
+  const std::optional<std::string> &getCustomNote() const { return CustomNote; }
 };
 
 /// A \c FileCheckDiag series emitted by the FileCheck library.
 class FileCheckDiagList {
 private:
+  MatchResultDiag *CurMatchResultDiag = nullptr;
   std::vector<std::unique_ptr<FileCheckDiag>> DiagList;
 
 public:
-  /// Emplace a new \c FileCheckDiag.
-  template <typename... ArgTys> void emplace_back(const ArgTys &...Args) {
-    DiagList.emplace_back(std::make_unique<FileCheckDiag>(Args...));
+  /// Emplace a new \c FileCheckDiag of type \c DiagTy.  If it's a
+  /// \c MatchNoteDiag, associate it with its \c MatchResultDiag.
+  ///
+  /// \c FileCheckTest.cpp calls \c Pattern::printVariableDefs directly, so it
+  /// can add a \c MatchNoteDiag without a previous \c MatchResultDiag.
+  /// Otherwise, there should always be a previous \c MatchResultDiag.
+  template <typename DiagTy, typename... ArgTys>
+  void emplace(const ArgTys &...Args) {
+    DiagList.emplace_back(std::make_unique<DiagTy>(Args...));
+    FileCheckDiag *Diag = DiagList.back().get();
+    if (MatchResultDiag *MRD = dyn_cast<MatchResultDiag>(Diag)) {
+      CurMatchResultDiag = MRD;
+      return;
+    }
+    MatchNoteDiag *Note = cast<MatchNoteDiag>(Diag);
+    if (!CurMatchResultDiag)
+      return;
+    Note->setMatchResultDiag(CurMatchResultDiag);
   }
-  /// Adjust recent consecutive diagnostics of the same \c CheckLoc to have
-  /// \c MatchTy.
+  /// Adjust the most recent \c MatchResultDiag, which must exist, and every
+  /// \c MatchResultNote after it to have the match type \c MatchTy.
   void adjustPrevDiags(FileCheckDiag::MatchType MatchTy) {
-    SMLoc CheckLoc = (*DiagList.rbegin())->CheckLoc;
+    assert(CurMatchResultDiag && "expected previous MatchResultDiag");
     for (auto I = DiagList.rbegin(), E = DiagList.rend();
-         I != E && (*I)->CheckLoc == CheckLoc; ++I)
-      (*I)->MatchTy = MatchTy;
+         I != E && &**I != CurMatchResultDiag; ++I)
+      (*I)->adjustMatchType(MatchTy);
+    CurMatchResultDiag->adjustMatchType(MatchTy);
   }
   /// The \c FileCheckDiag list.
   const std::vector<std::unique_ptr<FileCheckDiag>> &getList() const {
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index 7fd672b978e36..b493eb67fa173 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -1285,8 +1285,8 @@ void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
       // Indicating a non-zero-length range might instead seem to imply that the
       // substitution matches or was captured from exactly that range.
       if (Diags)
-        Diags->emplace_back(SM, CheckTy, getLoc(), MatchTy,
-                            SMRange(Range.Start, Range.Start), OS.str());
+        Diags->emplace<MatchNoteDiag>(
+            MatchTy, SMRange(Range.Start, Range.Start), OS.str());
       else
         SM.PrintMessage(Range.Start, SourceMgr::DK_Note, OS.str());
     }
@@ -1340,7 +1340,7 @@ void Pattern::printVariableDefs(const SourceMgr &SM,
     raw_svector_ostream OS(Msg);
     OS << "captured var \"" << VC.Name << "\"";
     if (Diags)
-      Diags->emplace_back(SM, CheckTy, getLoc(), MatchTy, VC.Range, OS.str());
+      Diags->emplace<MatchNoteDiag>(MatchTy, VC.Range, OS.str());
     else
       SM.PrintMessage(VC.Range.Start, SourceMgr::DK_Note, OS.str(), VC.Range);
   }
@@ -1399,8 +1399,7 @@ void Pattern::printFuzzyMatch(const SourceMgr &SM, StringRef Buffer,
   if (Best && Best != StringRef::npos && BestQuality < 50) {
     SMRange MatchRange = buildMatchRange(Buffer, Best, 0);
     if (Diags)
-      Diags->emplace_back(SM, getCheckTy(), getLoc(), FileCheckDiag::MatchFuzzy,
-                          MatchRange);
+      Diags->emplace<MatchNoteDiag>(FileCheckDiag::MatchFuzzy, MatchRange);
     SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note,
                     "possible intended match here");
 
@@ -1507,18 +1506,7 @@ StringRef FileCheck::CanonicalizeFile(MemoryBuffer &MB,
   return StringRef(OutputBuffer.data(), OutputBuffer.size() - 1);
 }
 
-FileCheckDiag::FileCheckDiag(const SourceMgr &SM,
-                             const Check::FileCheckType &CheckTy,
-                             SMLoc CheckLoc, MatchType MatchTy,
-                             SMRange InputRange, StringRef Note)
-    : CheckTy(CheckTy), CheckLoc(CheckLoc), MatchTy(MatchTy), Note(Note) {
-  auto Start = SM.getLineAndColumn(InputRange.Start);
-  auto End = SM.getLineAndColumn(InputRange.End);
-  InputStartLine = Start.first;
-  InputStartCol = Start.second;
-  InputEndLine = End.first;
-  InputEndCol = End.second;
-}
+FileCheckDiag::~FileCheckDiag() {}
 
 static bool IsPartOfWord(char c) {
   return (isAlnum(c) || c == '-' || c == '_');
@@ -2045,7 +2033,7 @@ static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
   SMRange MatchRange = buildMatchRange(Buffer, MatchResult.TheMatch->Pos,
                                        MatchResult.TheMatch->Len);
   if (Diags) {
-    Diags->emplace_back(SM, Pat.getCheckTy(), Loc, MatchTy, MatchRange);
+    Diags->emplace<MatchResultDiag>(Pat.getCheckTy(), Loc, MatchTy, MatchRange);
     Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, Diags);
     Pat.printVariableDefs(SM, MatchTy, Diags);
   }
@@ -2077,9 +2065,9 @@ static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
                   [&](const ErrorDiagnostic &E) {
                     E.log(errs());
                     if (Diags) {
-                      Diags->emplace_back(SM, Pat.getCheckTy(), Loc,
-                                          FileCheckDiag::MatchFoundErrorNote,
-                                          E.getRange(), E.getMessage().str());
+                      Diags->emplace<MatchNoteDiag>(
+                          FileCheckDiag::MatchFoundErrorNote, E.getRange(),
+                          E.getMessage().str());
                     }
                   });
   return ErrorReported::reportedOrSuccess(HasError);
@@ -2131,11 +2119,11 @@ static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM,
   // diagnostic is all we have to anchor them.
   SMRange SearchRange = buildSearchRange(Buffer);
   if (Diags) {
-    Diags->emplace_back(SM, Pat.getCheckTy(), Loc, MatchTy, SearchRange);
+    Diags->emplace<MatchResultDiag>(Pat.getCheckTy(), Loc, MatchTy,
+                                    SearchRange);
     SMRange NoteRange = SMRange(SearchRange.Start, SearchRange.Start);
     for (StringRef ErrorMsg : ErrorMsgs)
-      Diags->emplace_back(SM, Pat.getCheckTy(), Loc, MatchTy, NoteRange,
-                          ErrorMsg);
+      Diags->emplace<MatchNoteDiag>(MatchTy, NoteRange, ErrorMsg);
     Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, Diags);
   }
   if (!PrintDiag) {
@@ -2268,9 +2256,9 @@ size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer,
         if (Req.Verbose) {
           Diags->adjustPrevDiags(FileCheckDiag::MatchFoundButWrongLine);
         } else {
-          Diags->emplace_back(SM, Pat.getCheckTy(), Loc,
-                              FileCheckDiag::MatchFoundButWrongLine,
-                              buildMatchRange(MatchBuffer, MatchPos, MatchLen));
+          Diags->emplace<MatchResultDiag>(
+              Pat.getCheckTy(), Loc, FileCheckDiag::MatchFoundButWrongLine,
+              buildMatchRange(MatchBuffer, MatchPos, MatchLen));
         }
       }
       return StringRef::npos;
@@ -2283,9 +2271,9 @@ size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer,
         if (Req.Verbose) {
           Diags->adjustPrevDiags(FileCheckDiag::MatchFoundButWrongLine);
         } else {
-          Diags->emplace_back(SM, Pat.getCheckTy(), Loc,
-                              FileCheckDiag::MatchFoundButWrongLine,
-                              buildMatchRange(MatchBuffer, MatchPos, MatchLen));
+          Diags->emplace<MatchResultDiag>(
+              Pat.getCheckTy(), Loc, FileCheckDiag::MatchFoundButWrongLine,
+              buildMatchRange(MatchBuffer, MatchPos, MatchLen));
         }
       }
       return StringRef::npos;
diff --git a/llvm/unittests/FileCheck/FileCheckTest.cpp b/llvm/unittests/FileCheck/FileCheckTest.cpp
index cff958a114c63..9282296b4c540 100644
--- a/llvm/unittests/FileCheck/FileCheckTest.cpp
+++ b/llvm/unittests/FileCheck/FileCheckTest.cpp
@@ -918,8 +918,21 @@ class PatternTester {
                          FileCheckDiagList &Diags) {
     P.printVariableDefs(SM, MatchTy, &Diags);
   }
+
+  SourceMgr &getSourceMgr() { return SM; }
 };
 
+#define EXPECT_SM_RANGE(SM, Range, StartLineExpected, StartColExpected,        \
+                        EndLineExpected, EndColExpected)                       \
+  do {                                                                         \
+    auto StartActual = SM.getLineAndColumn(Range.Start);                       \
+    auto EndActual = SM.getLineAndColumn(Range.End);                           \
+    EXPECT_EQ(StartActual.first, StartLineExpected);                           \
+    EXPECT_EQ(StartActual.second, StartColExpected);                           \
+    EXPECT_EQ(EndActual.first, EndLineExpected);                               \
+    EXPECT_EQ(EndActual.second, EndColExpected);                               \
+  } while (0)
+
 TEST_F(FileCheckTest, ParseNumericSubstitutionBlock) {
   PatternTester Tester;
 
@@ -1639,22 +1652,21 @@ TEST_F(FileCheckTest, FileCheckContext) {
 
 TEST_F(FileCheckTest, CapturedVarDiags) {
   PatternTester Tester;
+  SourceMgr &SM = Tester.getSourceMgr();
   ASSERT_FALSE(Tester.parsePattern("[[STRVAR:[a-z]+]] [[#NUMVAR:@LINE]]"));
   EXPECT_THAT_EXPECTED(Tester.match("foobar 2"), Succeeded());
   FileCheckDiagList Diags;
   Tester.printVariableDefs(FileCheckDiag::MatchFoundAndExpected, Diags);
   EXPECT_EQ(Diags.getList().size(), 2ul);
+  SmallVector<MatchNoteDiag, 2> Notes;
   for (const std::unique_ptr<FileCheckDiag> &Diag : Diags.getList()) {
-    EXPECT_EQ(Diag->CheckTy, Check::CheckPlain);
-    EXPECT_EQ(Diag->MatchTy, FileCheckDiag::MatchFoundAndExpected);
-    EXPECT_EQ(Diag->InputStartLine, 1u);
-    EXPECT_EQ(Diag->InputEndLine, 1u);
+    EXPECT_EQ(Diag->getKind(), FileCheckDiag::FCDK_MatchNoteDiag);
+    EXPECT_EQ(Diag->getMatchType(), FileCheckDiag::MatchFoundAndExpected);
+    Notes.push_back(cast<MatchNoteDiag>(*Diag));
   }
-  EXPECT_EQ(Diags.getList()[0]->InputStartCol, 1u);
-  EXPECT_EQ(Diags.getList()[0]->InputEndCol, 7u);
-  EXPECT_EQ(Diags.getList()[1]->InputStartCol, 8u);
-  EXPECT_EQ(Diags.getList()[1]->InputEndCol, 9u);
-  EXPECT_EQ(Diags.getList()[0]->Note, "captured var \"STRVAR\"");
-  EXPECT_EQ(Diags.getList()[1]->Note, "captured var \"NUMVAR\"");
+  EXPECT_SM_RANGE(SM, Notes[0].getInputRange(), 1u, 1u, 1u, 7u);
+  EXPECT_SM_RANGE(SM, Notes[1].getInputRange(), 1u, 8u, 1u, 9u);
+  EXPECT_EQ(Notes[0].getCustomNote(), "captured var \"STRVAR\"");
+  EXPECT_EQ(Notes[1].getCustomNote(), "captured var \"NUMVAR\"");
 }
 } // namespace
diff --git a/llvm/utils/FileCheck/FileCheck.cpp b/llvm/utils/FileCheck/FileCheck.cpp
index 6cf762bda68d0..7b29238844e4f 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -390,76 +390,95 @@ buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
   // How many diagnostics does each pattern have?
   std::map<SMLoc, unsigned, CompareSMLoc> DiagCountPerPattern;
   for (const std::unique_ptr<FileCheckDiag> &Diag : Diags)
-    ++DiagCountPerPattern[Diag->CheckLoc];
+    ++DiagCountPerPattern[Diag->getMatchResultDiag().getCheckLoc()];
   // How many diagnostics have we seen so far per pattern?
   std::map<SMLoc, unsigned, CompareSMLoc> DiagIndexPerPattern;
   // How many total diagnostics have we seen so far?
   unsigned DiagIndex = 0;
   // What's the widest label?
   LabelWidth = 0;
+  // The label prefix that uniquely identifies the current MatchResultDiag and
+  // its MatchNoteDiag series.
+  std::string CurLabelPrefix;
   for (const std::unique_ptr<FileCheckDiag> &Diag : Diags) {
     InputAnnotation A;
     A.DiagIndex = DiagIndex++;
+    if (MatchResultDiag *MRD = dyn_cast<MatchResultDiag>(Diag.get())) {
+      CurLabelPrefix.clear();
+      llvm::raw_string_ostream LabelStrm(CurLabelPrefix);
+      LabelStrm << GetCheckTypeAbbreviation(MRD->getCheckTy()) << ":";
+      unsigned CheckBufferID = SM.FindBufferContainingLoc(MRD->getCheckLoc());
+      if (CheckBufferID == CheckFileBufferID)
+        LabelStrm
+            << SM.getLineAndColumn(MRD->getCheckLoc(), CheckBufferID).first;
+      else if (ImpPatBufferIDRange.first <= CheckBufferID &&
+               CheckBufferID < ImpPatBufferIDRange.second)
+        LabelStrm << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1);
+      else
+        llvm_unreachable("expected diagnostic's check location to be either in "
+                         "the check file or for an implicit pattern");
+    }
 
-    // Build label, which uniquely identifies this check result.
-    unsigned CheckBufferID = SM.FindBufferContainingLoc(Diag->CheckLoc);
-    auto CheckLineAndCol = SM.getLineAndColumn(Diag->CheckLoc, CheckBufferID);
-    llvm::raw_string_ostream Label(A.Label);
-    Label << GetCheckTypeAbbreviation(Diag->CheckTy) << ":";
-    if (CheckBufferID == CheckFileBufferID)
-      Label << CheckLineAndCol.first;
-    else if (ImpPatBufferIDRange.first <= CheckBufferID &&
-             CheckBufferID < ImpPatBufferIDRange.second)
-      Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1);
-    else
-      llvm_unreachable("expected diagnostic's check location to be either in "
-                       "the check file or for an implicit pattern");
-    if (DiagCountPerPattern[Diag->CheckLoc] > 1)
-      Label << "'" << DiagIndexPerPattern[Diag->CheckLoc]++;
+    // Build label that uniquely identifies this FileCheckDiag.
+    llvm::raw_string_ostream LabelStrm(A.Label);
+    LabelStrm << CurLabelPrefix;
+    if (DiagCountPerPattern[Diag->getMatchResultDiag().getCheckLoc()] > 1)
+      LabelStrm
+          << "'"
+          << DiagIndexPerPattern[Diag->getMatchResultDiag().getCheckLoc()]++;
     LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size());
 
-    A.Marker = GetMarker(Diag->MatchTy);
-    if (!Diag->Note.empty()) {
-      A.Marker.Note = Diag->Note;
-      // It's less confusing if notes that don't actually have ranges don't have
-      // markers.  For example, a marker for 'with "VAR" equal to "5"' would
-      // seem to indicate where "VAR" matches, but the location we actually have
-      // for the marker simply points to the start of the match/search range for
-      // the full pattern of which the substitution is potentially just one
-      // component.
-      if (Diag->InputStartLine == Diag->InputEndLine &&
-          Diag->InputStartCol == Diag->InputEndCol)
-        A.Marker.Lead = ' ';
+    A.Marker = GetMarker(Diag->getMatchType());
+    std::optional<StringRef> CustomNote = std::nullopt;
+    if (const MatchNoteDiag *NoteDiag = dyn_cast<MatchNoteDiag>(&*Diag)) {
+      CustomNote = NoteDiag->getCustomNote();
+      if (CustomNote) {
+        A.Marker.Note = *CustomNote;
+        // It's less confusing if notes that don't actually have ranges don't
+        // have markers.  For example, a marker for 'with "VAR" equal to "5"'
+        // would seem to indicate where "VAR" matches, but the location we
+        // actually have for the marker simply points to the start of the
+        // match/search range for the full pattern of which the substitution is
+        // potentially just one component.
+        SMRange InputRange = Diag->getInputRange();
+        if (InputRange.Start == InputRange.End)
+          A.Marker.Lead = ' ';
+      }
     }
-    if (Diag->MatchTy == FileCheckDiag::MatchFoundErrorNote) {
-      assert(!Diag->Note.empty() &&
-             "expected custom note for MatchFoundErrorNote");
+    if (Diag->getMatchType() == FileCheckDiag::MatchFoundErrorNote) {
+      assert(CustomNote && "expected custom note for MatchFoundErrorNote");
       A.Marker.Note = "error: " + A.Marker.Note;
     }
     A.FoundAndExpectedMatch =
-        Diag->MatchTy == FileCheckDiag::MatchFoundAndExpected;
+        Diag->getMatchType() == FileCheckDiag::MatchFoundAndExpected;
 
     // Compute the mark location, and break annotation into multiple
     // annotations if it spans multiple lines.
+    SMRange InputRange = Diag->getInputRange();
+    auto InputStartLineAndCol = SM.getLineAndColumn(InputRange.Start);
+    auto InputEndLineAndCol = SM.getLineAndColumn(InputRange.End);
+    unsigned InputStartLine = InputStartLineAndCol.first;
+    unsigned InputStartCol = InputStartLineAndCol.second;
+    unsigned InputEndLine = InputEndLineAndCol.first;
+    unsigned InputEndCol = InputEndLineAndCol.second;
     A.IsFirstLine = true;
-    A.InputLine = Diag->InputStartLine;
-    A.InputStartCol = Diag->InputStartCol;
-    if (Diag->InputStartLine == Diag->InputEndLine) {
+    A.InputLine = InputStartLine;
+    A.InputStartCol = InputStartCol;
+    if (InputStartLine == InputEndLine) {
       // Sometimes ranges are empty in order to indicate a specific point, but
       // that would mean nothing would be marked, so adjust the range to
       // include the following character.
-      A.InputEndCol = std::max(Diag->InputStartCol + 1, Diag->InputEndCol);
+      A.InputEndCol = std::max(InputStartCol + 1, InputEndCol);
       Annotations.push_back(A);
     } else {
-      assert(Diag->InputStartLine < Diag->InputEndLine &&
+      assert(InputStartLine < InputEndLine &&
              "expected input range not to be inverted");
       A.InputEndCol = UINT_MAX;
       Annotations.push_back(A);
-      for (unsigned L = Diag->InputStartLine + 1, E = Diag->InputEndLine;
-           L <= E; ++L) {
+      for (unsigned L = InputStartLine + 1, E = InputEndLine; L <= E; ++L) {
         // If a range ends before the first column on a line, then it has no
         // characters on that line, so there's nothing to render.
-        if (Diag->InputEndCol == 1 && L == E)
+        if (InputEndCol == 1 && L == E)
           break;
         InputAnnotation B;
         B.DiagIndex = A.DiagIndex;
@@ -473,7 +492,7 @@ buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
         if (L != E)
           B.InputEndCol = UINT_MAX;
         else
-          B.InputEndCol = Diag->InputEndCol;
+          B.InputEndCol = InputEndCol;
         B.FoundAndExpectedMatch = A.FoundAndExpectedMatch;
         Annotations.push_back(B);
       }

>From 25f8dbdb87750286f2a003334de9ec7b723f00d8 Mon Sep 17 00:00:00 2001
From: "Joel E. Denny" <jdenny.ornl at gmail.com>
Date: Sun, 3 May 2026 18:01:23 -0400
Subject: [PATCH 03/10] [FileCheck][NFC] Complete FileCheckDiag class hierarchy

This patch depends on PR #???? and finishes its effort to decouple the
`-dump-input` presentation layer (in
`llvm/utils/FileCheck/FileCheck.cpp`) and the FileCheck library's
diagnostic emission (in `llvm/lib/FileCheck/FileCheck.cpp`).

The focus of this patch is finally eliminating `enum MatchType` and
completing the `FileCheckDiag` class hierarchy.  That enables the
following improvements.

Replace MatchTy and InputRange
==============================

`-dump-input` needs some means to determine (1) whether some notes
should be filtered in by `-dump-input-filter=error`, and (2) where to
place some notes in the input dump.  Without this patch series, the
FileCheck library provides that information by copying the following
from a match result to any note that does not have its own versions:
(1) the `MatchType`, which determines error status, and (2) the input
range so that `-dump-input` can place the note next to the match
result.  However, this copied information is redundant as it is
already part of the associated match result, and semantically it does
not really apply to the note itself.

Furthermore, for a note that does not have a match location (e.g., the
FileCheck library does not include match locations when emitting notes
about variable substitutions), the FileCheck library is responsible
for reducing the copied location to an empty range as a special case
that `-dump-input` understands means it should not add a marker (like
`^~~`), which would be misleading in the input dump.  However, that
means a note can never have a real input range that is empty and that
thus refers to just a single position.

This interaction is too subtle, even in the case of just
`-dump-input`.  Moreover, it is easy to imagine alternative diagnostic
presentation layers where it would be unhelpful and thus more
misleading.  For example, imagine (this is just a thought experiment,
and I have no plans for this) an HTML-based presentation where notes
appear in pop-up windows when clicking on match results.  A copied
error status and location is then not relevant for notes.  Association
with a match result still is relevant.

This patch removes the `MatchTy` and `InputRange` fields from
`FieldCheckDiag`.  It replaces `MatchTy` with `Status` fields in just
the `MatchResultDiag` hierarchy.  To replace `InputRange`, it exposes
a match range as a `std::optional`, and it adds a search range to
`MatchResultDiag` (used now for `MatchNoneDiag` and, in the future,
`-dump-input` will present search ranges upon any error).  Thus, this
patch relieves the FileCheck library of the above
`-dump-input`-specific responsibilities, and `-dump-input` is now able
to cleanly examine the `FileCheckDiag` series for each required
property.

Clean up getMarker
==================

Because of the now complete `FileCheckDiag` class hierarchy, this
patch is able to clean up the `getMarker` function for `-dump-input`.
Without this patch, `getMarker` encodes the lead character, color,
message, and error status individually for every possible `MatchType`.
With this patch, `getMarker` instead encodes the logic of how the
marker properties are generally chosen at the top of the
`FileCheckDiag` hierarchy, and then it overrides those choices where
needed lower in the hierarchy.  Again, it is now easier for a
diagnostic presentation layer to reason about diagnostics.
---
 llvm/include/llvm/FileCheck/FileCheck.h    | 304 ++++++++++++++++-----
 llvm/lib/FileCheck/FileCheck.cpp           |  82 +++---
 llvm/lib/FileCheck/FileCheckImpl.h         |   4 +-
 llvm/unittests/FileCheck/FileCheckTest.cpp |  27 +-
 llvm/utils/FileCheck/FileCheck.cpp         | 158 ++++++-----
 5 files changed, 376 insertions(+), 199 deletions(-)

diff --git a/llvm/include/llvm/FileCheck/FileCheck.h b/llvm/include/llvm/FileCheck/FileCheck.h
index b171d1048cddb..ea66ca3cb0617 100644
--- a/llvm/include/llvm/FileCheck/FileCheck.h
+++ b/llvm/include/llvm/FileCheck/FileCheck.h
@@ -124,56 +124,31 @@ class MatchResultDiag;
 /// - \c MatchNoteDiag provides an additional note about the most recent
 ///   \c MatchResultDiag emitted by a FileCheck invocation.  For example, there
 ///   might be a fuzzy match after a failure to match.
+///
+/// Throughout this class hierarchy, a pattern is said to be either expected or
+/// excluded depending on whether the pattern must have or must not have a match
+/// in order for it to succeed.  For example, a \c CHECK directive's pattern is
+/// expected, and a \c CHECK-NOT directive's pattern is excluded.
 class FileCheckDiag {
 public:
-  enum FileCheckDiagKind { FCDK_MatchResultDiag, FCDK_MatchNoteDiag };
-
-  /// What type of match result does this diagnostic describe?
-  ///
-  /// A directive's supplied pattern is said to be either expected or excluded
-  /// depending on whether the pattern must have or must not have a match in
-  /// order for the directive to succeed.  For example, a CHECK directive's
-  /// pattern is expected, and a CHECK-NOT directive's pattern is excluded.
-  enum MatchType {
-    /// Indicates a good match for an expected pattern.
-    MatchFoundAndExpected,
-    /// Indicates a match for an excluded pattern.
-    MatchFoundButExcluded,
-    /// Indicates a match for an expected pattern, but the match is on the
-    /// wrong line.
-    MatchFoundButWrongLine,
-    /// Indicates a discarded match for an expected pattern.
-    MatchFoundButDiscarded,
-    /// Indicates an error while processing a match after the match was found
-    /// for an expected or excluded pattern.  The error is specified by \c Note,
-    /// to which it should be appropriate to prepend "error: " later.  The full
-    /// match itself should be recorded in a preceding diagnostic of a different
-    /// \c MatchFound match type.
-    MatchFoundErrorNote,
-    /// Indicates no match for an excluded pattern.
-    MatchNoneAndExcluded,
-    /// Indicates no match for an expected pattern, but this might follow good
-    /// matches when multiple matches are expected for the pattern, or it might
-    /// follow discarded matches for the pattern.
-    MatchNoneButExpected,
-    /// Indicates no match due to an expected or excluded pattern that has
-    /// proven to be invalid at match time.  The exact problems are usually
-    /// reported in subsequent diagnostics of the same match type but with
-    /// \c Note set.
-    MatchNoneForInvalidPattern,
-    /// Indicates a fuzzy match that serves as a suggestion for the next
-    /// intended match for an expected pattern with too few or no good matches.
-    MatchFuzzy,
+  enum FileCheckDiagKind {
+    // MatchResultDiag
+    FCDK_MatchResultDiag_First,
+    FCDK_MatchFoundDiag = FCDK_MatchResultDiag_First,
+    FCDK_MatchNoneDiag,
+    FCDK_MatchResultDiag_Last = FCDK_MatchNoneDiag,
+    // MatchNoteDiag
+    FCDK_MatchNoteDiag_First,
+    FCDK_MatchFuzzyDiag = FCDK_MatchNoteDiag_First,
+    FCDK_MatchCustomNoteDiag,
+    FCDK_MatchNoteDiag_Last = FCDK_MatchCustomNoteDiag
   };
 
 private:
   const FileCheckDiagKind Kind;
-  MatchType MatchTy;
-  SMRange InputRange;
 
 public:
-  FileCheckDiag(FileCheckDiagKind Kind, MatchType MatchTy, SMRange InputRange)
-      : Kind(Kind), MatchTy(MatchTy), InputRange(InputRange) {}
+  FileCheckDiag(FileCheckDiagKind Kind) : Kind(Kind) {}
   /// Destructor is purely virtual to ensure this remains an abstract class.
   virtual ~FileCheckDiag() = 0;
   /// Of what derived class is this an instance?
@@ -181,30 +156,41 @@ class FileCheckDiag {
   /// If this is a \c MatchResultDiag, return itself.  If this is a
   /// \c MatchNoteDiag, return its associated \c MatchResultDiag.
   virtual const MatchResultDiag &getMatchResultDiag() const = 0;
-  /// Adjust the match type.
-  void adjustMatchType(MatchType MatchTy) { this->MatchTy = MatchTy; }
-  /// Get the match type.
-  MatchType getMatchType() const { return MatchTy; }
-  /// The search range if MatchTy starts with MatchNone, or the match range
-  /// otherwise.
-  SMRange getInputRange() const { return InputRange; }
+  /// Does this diagnostic reveal a new error?
+  ///
+  /// For \c MatchResultDiag, \c !isError() is not always the same as a
+  /// successful pattern match result.  For \c MatchNoteDiag, \c !isError()
+  /// does not indicate the lack of an error but rather the lack of an
+  /// additional error beyond its associated \c MatchResultDiag.  See
+  /// documentation on derived types for details.
+  virtual bool isError() const = 0;
+  /// Return the input range for which this diagnostic indicates text that was
+  /// matched in some way (e.g., successful pattern match, discarded pattern
+  /// match, or variable capture), or return \c std::nullopt if the diagnostic
+  /// has no such input range.
+  virtual std::optional<SMRange> getMatchRange() const = 0;
 };
 
-/// Class for recording a FileCheck diagnostic that reports a match result for a
-/// pattern.
+/// Abstract base class for recording a FileCheck diagnostic that reports a
+/// match result for a pattern.
 class MatchResultDiag : public FileCheckDiag {
 private:
   Check::FileCheckType CheckTy;
   SMLoc CheckLoc;
+  SMRange SearchRange;
 
 public:
-  MatchResultDiag(const Check::FileCheckType &CheckTy, SMLoc CheckLoc,
-                  MatchType MatchTy, SMRange InputRange)
-      : FileCheckDiag(FCDK_MatchResultDiag, MatchTy, InputRange),
-        CheckTy(CheckTy), CheckLoc(CheckLoc) {}
+  MatchResultDiag(FileCheckDiagKind Kind, const Check::FileCheckType &CheckTy,
+                  SMLoc CheckLoc, SMRange SearchRange)
+      : FileCheckDiag(Kind), CheckTy(CheckTy), CheckLoc(CheckLoc),
+        SearchRange(SearchRange) {}
+  /// Destructor is purely virtual to ensure this remains an abstract class.
+  virtual ~MatchResultDiag() = 0;
   /// Is \p FCD an instance of \c MatchResultDiag?
   static bool classof(const FileCheckDiag *FCD) {
-    return FCD->getKind() == FCDK_MatchResultDiag;
+    FileCheckDiagKind FCDK = FCD->getKind();
+    return FCDK_MatchResultDiag_First <= FCDK &&
+           FCDK <= FCDK_MatchResultDiag_Last;
   }
   /// Get itself.
   virtual const MatchResultDiag &getMatchResultDiag() const override {
@@ -214,23 +200,122 @@ class MatchResultDiag : public FileCheckDiag {
   Check::FileCheckType getCheckTy() const { return CheckTy; }
   /// Where is the pattern for this match result?
   SMLoc getCheckLoc() const { return CheckLoc; }
+  /// What is the search range for the match result?
+  SMRange getSearchRange() const { return SearchRange; }
 };
 
-/// Class for recording a FileCheck diagnostic that provides an additional note
-/// (possibly an additional error) about the most recent \c MatchResultDiag.
+/// \c MatchResultDiag for a pattern that matched the input.
+class MatchFoundDiag : public MatchResultDiag {
+public:
+  enum StatusTy {
+    /// Indicates a good match for an expected pattern.
+    Success,
+    /// Indicates a match for an excluded pattern (error).
+    Excluded,
+    /// Indicates a match for an expected pattern, but the match is on the
+    /// wrong line (error).
+    WrongLine,
+    /// Indicates a discarded match for an expected pattern (not an error).
+    Discarded
+  };
+
+private:
+  StatusTy Status;
+  SMRange MatchRange;
+
+public:
+  MatchFoundDiag(const Check::FileCheckType &CheckTy, SMLoc CheckLoc,
+                 StatusTy Status, SMRange MatchRange, SMRange SearchRange)
+      : MatchResultDiag(FCDK_MatchFoundDiag, CheckTy, CheckLoc, SearchRange),
+        Status(Status), MatchRange(MatchRange) {}
+  /// Is \p FCD an instance of \c MatchFoundDiag?
+  static bool classof(const FileCheckDiag *FCD) {
+    return FCD->getKind() == FCDK_MatchFoundDiag;
+  }
+  /// Does this match produce an error?
+  ///
+  /// This is not always the same as \c getStatus()!=Success.  For example,
+  /// \c CHECK-DAG discarded matches are neither successful matches nor errors.
+  virtual bool isError() const override {
+    return Status != Success && Status != Discarded;
+  }
+  /// Was this a successful match?  If not, why not?
+  ///
+  /// See \c isError comments for the relationship between the two.
+  StatusTy getStatus() const { return Status; }
+  /// Adjust a successful status to a non-successful status.
+  ///
+  /// This is designed to be called while emitting diagnostics.  It is not
+  /// designed to be called by a diagnostic presentation layer like
+  /// `-dump-input`.
+  ///
+  /// For example, a match that was originally thought to be successful might
+  /// later be discarded, or it might be determined that it violates a matching
+  /// constraint (e.g., wrong line).
+  void markUnsuccessful(StatusTy S) {
+    assert(Status == Success && S != Success &&
+           "expected to change successful status to unsuccessful");
+    Status = S;
+  }
+  /// Return the match's input range, never \c std::nullopt.
+  virtual std::optional<SMRange> getMatchRange() const override {
+    return MatchRange;
+  }
+};
+
+/// \c MatchResultDiag for a pattern that did not match the input.
+class MatchNoneDiag : public MatchResultDiag {
+public:
+  enum StatusTy {
+    /// Indicates no match for an excluded pattern.
+    Success,
+    /// Indicates no match due to an expected or excluded pattern that has
+    /// proven to be invalid at match time (error).  The exact problems are
+    /// usually reported in subsequent \c MatchNoteDiag objects.
+    InvalidPattern,
+    /// Indicates no match for an expected pattern (error).  In some cases, it
+    /// follows good matches (because multiple matches are expected) or
+    /// discarded matches for the pattern.
+    Expected
+  };
+
+private:
+  StatusTy Status;
+
+public:
+  MatchNoneDiag(const Check::FileCheckType &CheckTy, SMLoc CheckLoc,
+                StatusTy Status, SMRange SearchRange)
+      : MatchResultDiag(FCDK_MatchNoneDiag, CheckTy, CheckLoc, SearchRange),
+        Status(Status) {}
+  /// Is \p FCD an instance of \c MatchNoneDiag?
+  static bool classof(const FileCheckDiag *FCD) {
+    return FCD->getKind() == FCDK_MatchNoneDiag;
+  }
+  /// Does the lack of match represent an error?
+  virtual bool isError() const override { return Status != Success; }
+  /// Does the lack of a match indicate a success?  If not, why not?
+  StatusTy getStatus() const { return Status; }
+  /// Return \c std::nullopt.
+  virtual std::optional<SMRange> getMatchRange() const override {
+    return std::nullopt;
+  }
+};
+
+/// Abstract base class for recording a FileCheck diagnostic that provides an
+/// additional note (possibly a new error) about the most recent
+/// \c MatchResultDiag.
 class MatchNoteDiag : public FileCheckDiag {
 private:
   MatchResultDiag *MRD;
-  std::optional<std::string> CustomNote;
 
 public:
-  MatchNoteDiag(MatchType MatchTy, SMRange InputRange,
-                std::optional<StringRef> CustomNote = std::nullopt)
-      : FileCheckDiag(FCDK_MatchNoteDiag, MatchTy, InputRange), MRD(nullptr),
-        CustomNote(CustomNote) {}
+  MatchNoteDiag(FileCheckDiagKind FCDK) : FileCheckDiag(FCDK), MRD(nullptr) {}
+  /// Destructor is purely virtual to ensure this remains an abstract class.
+  virtual ~MatchNoteDiag() = 0;
   /// Is \p FCD an instance of \c MatchNoteDiag?
   static bool classof(const FileCheckDiag *FCD) {
-    return FCD->getKind() == FCDK_MatchNoteDiag;
+    FileCheckDiagKind FCDK = FCD->getKind();
+    return FCDK_MatchNoteDiag_First <= FCDK && FCDK <= FCDK_MatchNoteDiag_Last;
   }
   /// Get the note's associated \c MatchResultDiag.
   virtual const MatchResultDiag &getMatchResultDiag() const override {
@@ -241,9 +326,82 @@ class MatchNoteDiag : public FileCheckDiag {
     assert(!MRD && "expected setMatchResultDiag to be called only once");
     MRD = MRDNew;
   }
-  /// A note to replace the one normally indicated by the match type, or the
-  /// empty string if none.
-  const std::optional<std::string> &getCustomNote() const { return CustomNote; }
+};
+
+/// \c MatchNoteDiag for a fuzzy match that serves as a suggestion for the next
+/// intended match for an expected pattern with too few or no good matches.
+class MatchFuzzyDiag : public MatchNoteDiag {
+private:
+  SMLoc MatchStart;
+
+public:
+  MatchFuzzyDiag(SMLoc MatchStart)
+      : MatchNoteDiag(FCDK_MatchFuzzyDiag), MatchStart(MatchStart) {}
+  /// Is \p FCD an instance of \c MatchFuzzyDiag?
+  static bool classof(const FileCheckDiag *FCD) {
+    return FCD->getKind() == FCDK_MatchFuzzyDiag;
+  }
+  /// Always false.  A fuzzy match is not an error even though it is performed
+  /// due to an error.
+  virtual bool isError() const override { return false; }
+  /// Return an input range (never \c std::nullopt) starting and ending at the
+  /// match start.  The actual match end is not computed.
+  virtual std::optional<SMRange> getMatchRange() const override {
+    return SMRange(MatchStart, MatchStart);
+  }
+};
+
+/// \c MatchNoteDiag with a custom note not described by any other class derived
+/// from \c MatchNoteDiag.
+class MatchCustomNoteDiag : public MatchNoteDiag {
+private:
+  std::string Note;
+  bool AddsError;
+  std::optional<SMRange> MatchRange;
+
+public:
+  /// If \p MatchRange is specified, it is a range for input text that was
+  /// matched in some way (e.g., variable capture) and that is described by
+  /// this note.  Either way, as usual, the associated \c MatchResultDiag has
+  /// any full match range for the pattern.
+  ///
+  /// If \p AddsError is true, then this note indicates a \a new error that is
+  /// distinct from any error indicated by the associated \c MatchResultDiag.
+  /// The error is described by \c Note, which must be worded appropriately for
+  /// prepending "error: " when presented later.  For example, the associated
+  /// \c MatchResultDiag might indicate a match to either an expected pattern
+  /// (success) or an excluded pattern (error), and \c Note might be "unable to
+  /// represent numeric value" to indicate the match could not be processed
+  /// afterward.
+  ///
+  /// If \p AddsError is false, then this note merely provides additional
+  /// information about the associated \c MatchResultDiag.  That information
+  /// might be something harmless (e.g., variable substitution), or it might be
+  /// one of potentially many problems summarized as an error by the
+  /// \c MatchResultDiag (e.g., one way in which the pattern was invalid).
+  ///@{
+  MatchCustomNoteDiag(SMRange MatchRange, StringRef Note,
+                      bool AddsError = false)
+      : MatchNoteDiag(FCDK_MatchCustomNoteDiag), Note(Note),
+        AddsError(AddsError), MatchRange(MatchRange) {}
+  MatchCustomNoteDiag(StringRef Note)
+      : MatchNoteDiag(FCDK_MatchCustomNoteDiag), Note(Note), AddsError(false) {}
+  ///@}
+  /// Is \p FCD an instance of \c MatchCustomNoteDiag?
+  static bool classof(const FileCheckDiag *FCD) {
+    return FCD->getKind() == FCDK_MatchCustomNoteDiag;
+  }
+  const std::string &getNote() const { return Note; }
+  /// Does this note indicate an \a additional error not indicated by the
+  /// associated \c MatchResultDiag?
+  ///
+  /// For details, see the \c MatchCustomNoteDiag::MatchCustomNoteDiag comments
+  /// for its \c AddsError parameter.
+  virtual bool isError() const override { return AddsError; }
+  /// Return the match range described by the note, or \c std::nullopt if none.
+  virtual std::optional<SMRange> getMatchRange() const override {
+    return MatchRange;
+  }
 };
 
 /// A \c FileCheckDiag series emitted by the FileCheck library.
@@ -272,14 +430,10 @@ class FileCheckDiagList {
       return;
     Note->setMatchResultDiag(CurMatchResultDiag);
   }
-  /// Adjust the most recent \c MatchResultDiag, which must exist, and every
-  /// \c MatchResultNote after it to have the match type \c MatchTy.
-  void adjustPrevDiags(FileCheckDiag::MatchType MatchTy) {
-    assert(CurMatchResultDiag && "expected previous MatchResultDiag");
-    for (auto I = DiagList.rbegin(), E = DiagList.rend();
-         I != E && &**I != CurMatchResultDiag; ++I)
-      (*I)->adjustMatchType(MatchTy);
-    CurMatchResultDiag->adjustMatchType(MatchTy);
+  /// Adjust the previous \c MatchResultDiag, which must be a \c MatchFoundDiag,
+  /// from successful status to unsuccessful status.
+  void adjustPrevMatchFoundDiag(MatchFoundDiag::StatusTy Status) {
+    cast<MatchFoundDiag>(CurMatchResultDiag)->markUnsuccessful(Status);
   }
   /// The \c FileCheckDiag list.
   const std::vector<std::unique_ptr<FileCheckDiag>> &getList() const {
diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp
index b493eb67fa173..4d3ac87b9c724 100644
--- a/llvm/lib/FileCheck/FileCheck.cpp
+++ b/llvm/lib/FileCheck/FileCheck.cpp
@@ -1260,7 +1260,6 @@ unsigned Pattern::computeMatchDistance(StringRef Buffer) const {
 
 void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
                                  SMRange Range,
-                                 FileCheckDiag::MatchType MatchTy,
                                  FileCheckDiagList *Diags) const {
   // Print what we know about substitutions.
   if (!Substitutions.empty()) {
@@ -1280,13 +1279,13 @@ void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
       OS.write_escaped(Substitution->getFromString()) << "\" equal to ";
       OS << *MatchedValue;
 
-      // We report only the start of the match/search range to suggest we are
-      // reporting the substitutions as set at the start of the match/search.
-      // Indicating a non-zero-length range might instead seem to imply that the
+      // Unlike MatchCustomNoteDiag, PrintMessage needs a location.  We report
+      // only the start of the match/search range to suggest we are reporting
+      // the substitutions as set at the start of the match/search.  Indicating
+      // a non-zero-length range might instead seem to imply that the
       // substitution matches or was captured from exactly that range.
       if (Diags)
-        Diags->emplace<MatchNoteDiag>(
-            MatchTy, SMRange(Range.Start, Range.Start), OS.str());
+        Diags->emplace<MatchCustomNoteDiag>(OS.str());
       else
         SM.PrintMessage(Range.Start, SourceMgr::DK_Note, OS.str());
     }
@@ -1294,7 +1293,6 @@ void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer,
 }
 
 void Pattern::printVariableDefs(const SourceMgr &SM,
-                                FileCheckDiag::MatchType MatchTy,
                                 FileCheckDiagList *Diags) const {
   if (VariableDefs.empty() && NumericVariableDefs.empty())
     return;
@@ -1340,7 +1338,7 @@ void Pattern::printVariableDefs(const SourceMgr &SM,
     raw_svector_ostream OS(Msg);
     OS << "captured var \"" << VC.Name << "\"";
     if (Diags)
-      Diags->emplace<MatchNoteDiag>(MatchTy, VC.Range, OS.str());
+      Diags->emplace<MatchCustomNoteDiag>(VC.Range, OS.str());
     else
       SM.PrintMessage(VC.Range.Start, SourceMgr::DK_Note, OS.str(), VC.Range);
   }
@@ -1397,10 +1395,10 @@ void Pattern::printFuzzyMatch(const SourceMgr &SM, StringRef Buffer,
   // reasonable and not equal to what we showed in the "scanning from here"
   // line.
   if (Best && Best != StringRef::npos && BestQuality < 50) {
-    SMRange MatchRange = buildMatchRange(Buffer, Best, 0);
+    SMLoc MatchStart = SMLoc::getFromPointer(Buffer.data() + Best);
     if (Diags)
-      Diags->emplace<MatchNoteDiag>(FileCheckDiag::MatchFuzzy, MatchRange);
-    SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note,
+      Diags->emplace<MatchFuzzyDiag>(MatchStart);
+    SM.PrintMessage(MatchStart, SourceMgr::DK_Note,
                     "possible intended match here");
 
     // FIXME: If we wanted to be really friendly we would show why the match
@@ -1507,6 +1505,8 @@ StringRef FileCheck::CanonicalizeFile(MemoryBuffer &MB,
 }
 
 FileCheckDiag::~FileCheckDiag() {}
+MatchResultDiag::~MatchResultDiag() {}
+MatchNoteDiag::~MatchNoteDiag() {}
 
 static bool IsPartOfWord(char c) {
   return (isAlnum(c) || c == '-' || c == '_');
@@ -2027,15 +2027,16 @@ static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
   }
 
   // Add "found" diagnostic, substitutions, and variable definitions to Diags.
-  FileCheckDiag::MatchType MatchTy = ExpectedMatch
-                                         ? FileCheckDiag::MatchFoundAndExpected
-                                         : FileCheckDiag::MatchFoundButExcluded;
+  MatchFoundDiag::StatusTy Status =
+      ExpectedMatch ? MatchFoundDiag::Success : MatchFoundDiag::Excluded;
   SMRange MatchRange = buildMatchRange(Buffer, MatchResult.TheMatch->Pos,
                                        MatchResult.TheMatch->Len);
+  SMRange SearchRange = buildSearchRange(Buffer);
   if (Diags) {
-    Diags->emplace<MatchResultDiag>(Pat.getCheckTy(), Loc, MatchTy, MatchRange);
-    Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, Diags);
-    Pat.printVariableDefs(SM, MatchTy, Diags);
+    Diags->emplace<MatchFoundDiag>(Pat.getCheckTy(), Loc, Status, MatchRange,
+                                   SearchRange);
+    Pat.printSubstitutions(SM, Buffer, MatchRange, Diags);
+    Pat.printVariableDefs(SM, Diags);
   }
   if (!PrintDiag) {
     assert(!HasError && "expected to report more diagnostics for error");
@@ -2055,8 +2056,8 @@ 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.printVariableDefs(SM, MatchTy, nullptr);
+  Pat.printSubstitutions(SM, Buffer, MatchRange, nullptr);
+  Pat.printVariableDefs(SM, nullptr);
 
   // Print errors and add them to Diags.  We report these errors after the match
   // itself because we found them after the match.  If we had found them before
@@ -2065,9 +2066,9 @@ static Error printMatch(bool ExpectedMatch, const SourceMgr &SM,
                   [&](const ErrorDiagnostic &E) {
                     E.log(errs());
                     if (Diags) {
-                      Diags->emplace<MatchNoteDiag>(
-                          FileCheckDiag::MatchFoundErrorNote, E.getRange(),
-                          E.getMessage().str());
+                      Diags->emplace<MatchCustomNoteDiag>(E.getRange(),
+                                                          E.getMessage().str(),
+                                                          /*AddsError=*/true);
                     }
                   });
   return ErrorReported::reportedOrSuccess(HasError);
@@ -2083,15 +2084,14 @@ static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM,
   // Print any pattern errors, and record them to be added to Diags later.
   bool HasError = ExpectedMatch;
   bool HasPatternError = false;
-  FileCheckDiag::MatchType MatchTy = ExpectedMatch
-                                         ? FileCheckDiag::MatchNoneButExpected
-                                         : FileCheckDiag::MatchNoneAndExcluded;
+  MatchNoneDiag::StatusTy Status =
+      ExpectedMatch ? MatchNoneDiag::Expected : MatchNoneDiag::Success;
   SmallVector<std::string, 4> ErrorMsgs;
   handleAllErrors(
       std::move(MatchError),
       [&](const ErrorDiagnostic &E) {
         HasError = HasPatternError = true;
-        MatchTy = FileCheckDiag::MatchNoneForInvalidPattern;
+        Status = MatchNoneDiag::InvalidPattern;
         E.log(errs());
         if (Diags)
           ErrorMsgs.push_back(E.getMessage().str());
@@ -2119,12 +2119,10 @@ static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM,
   // diagnostic is all we have to anchor them.
   SMRange SearchRange = buildSearchRange(Buffer);
   if (Diags) {
-    Diags->emplace<MatchResultDiag>(Pat.getCheckTy(), Loc, MatchTy,
-                                    SearchRange);
-    SMRange NoteRange = SMRange(SearchRange.Start, SearchRange.Start);
+    Diags->emplace<MatchNoneDiag>(Pat.getCheckTy(), Loc, Status, SearchRange);
     for (StringRef ErrorMsg : ErrorMsgs)
-      Diags->emplace<MatchNoteDiag>(MatchTy, NoteRange, ErrorMsg);
-    Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, Diags);
+      Diags->emplace<MatchCustomNoteDiag>(ErrorMsg);
+    Pat.printSubstitutions(SM, Buffer, SearchRange, Diags);
   }
   if (!PrintDiag) {
     assert(!HasError && "expected to report more diagnostics for error");
@@ -2150,7 +2148,7 @@ static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM,
 
   // Print additional information, which can be useful even after a pattern
   // error.
-  Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, nullptr);
+  Pat.printSubstitutions(SM, Buffer, SearchRange, nullptr);
   if (ExpectedMatch)
     Pat.printFuzzyMatch(SM, Buffer, Diags);
   return ErrorReported::reportedOrSuccess(HasError);
@@ -2254,11 +2252,12 @@ size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer,
     if (CheckNext(SM, SkippedRegion)) {
       if (Diags) {
         if (Req.Verbose) {
-          Diags->adjustPrevDiags(FileCheckDiag::MatchFoundButWrongLine);
+          Diags->adjustPrevMatchFoundDiag(MatchFoundDiag::WrongLine);
         } else {
-          Diags->emplace<MatchResultDiag>(
-              Pat.getCheckTy(), Loc, FileCheckDiag::MatchFoundButWrongLine,
-              buildMatchRange(MatchBuffer, MatchPos, MatchLen));
+          Diags->emplace<MatchFoundDiag>(
+              Pat.getCheckTy(), Loc, MatchFoundDiag::WrongLine,
+              buildMatchRange(MatchBuffer, MatchPos, MatchLen),
+              buildSearchRange(MatchBuffer));
         }
       }
       return StringRef::npos;
@@ -2269,11 +2268,12 @@ size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer,
     if (CheckSame(SM, SkippedRegion)) {
       if (Diags) {
         if (Req.Verbose) {
-          Diags->adjustPrevDiags(FileCheckDiag::MatchFoundButWrongLine);
+          Diags->adjustPrevMatchFoundDiag(MatchFoundDiag::WrongLine);
         } else {
-          Diags->emplace<MatchResultDiag>(
-              Pat.getCheckTy(), Loc, FileCheckDiag::MatchFoundButWrongLine,
-              buildMatchRange(MatchBuffer, MatchPos, MatchLen));
+          Diags->emplace<MatchFoundDiag>(
+              Pat.getCheckTy(), Loc, MatchFoundDiag::WrongLine,
+              buildMatchRange(MatchBuffer, MatchPos, MatchLen),
+              buildSearchRange(MatchBuffer));
         }
       }
       return StringRef::npos;
@@ -2464,7 +2464,7 @@ FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer,
         // we're gathering them for a different rendering, but we always print
         // other diagnostics.
         if (Diags) {
-          Diags->adjustPrevDiags(FileCheckDiag::MatchFoundButDiscarded);
+          Diags->adjustPrevMatchFoundDiag(MatchFoundDiag::Discarded);
         } else {
           SMLoc OldStart = SMLoc::getFromPointer(Buffer.data() + MI->Pos);
           SMLoc OldEnd = SMLoc::getFromPointer(Buffer.data() + MI->End);
diff --git a/llvm/lib/FileCheck/FileCheckImpl.h b/llvm/lib/FileCheck/FileCheckImpl.h
index 547c93f2acca5..5393650020a30 100644
--- a/llvm/lib/FileCheck/FileCheckImpl.h
+++ b/llvm/lib/FileCheck/FileCheckImpl.h
@@ -733,8 +733,7 @@ class Pattern {
                                       const SourceMgr &SM) const;
   /// Prints the value of successful substitutions.
   void printSubstitutions(const SourceMgr &SM, StringRef Buffer,
-                          SMRange MatchRange, FileCheckDiag::MatchType MatchTy,
-                          FileCheckDiagList *Diags) const;
+                          SMRange MatchRange, FileCheckDiagList *Diags) const;
   void printFuzzyMatch(const SourceMgr &SM, StringRef Buffer,
                        FileCheckDiagList *Diags) const;
 
@@ -742,7 +741,6 @@ class Pattern {
     return !(Substitutions.empty() && VariableDefs.empty());
   }
   LLVM_ABI_FOR_TEST void printVariableDefs(const SourceMgr &SM,
-                                           FileCheckDiag::MatchType MatchTy,
                                            FileCheckDiagList *Diags) const;
 
   Check::FileCheckType getCheckTy() const { return CheckTy; }
diff --git a/llvm/unittests/FileCheck/FileCheckTest.cpp b/llvm/unittests/FileCheck/FileCheckTest.cpp
index 9282296b4c540..909f52babbb13 100644
--- a/llvm/unittests/FileCheck/FileCheckTest.cpp
+++ b/llvm/unittests/FileCheck/FileCheckTest.cpp
@@ -914,17 +914,18 @@ class PatternTester {
     return Res.TheMatch->Pos;
   }
 
-  void printVariableDefs(FileCheckDiag::MatchType MatchTy,
-                         FileCheckDiagList &Diags) {
-    P.printVariableDefs(SM, MatchTy, &Diags);
+  void printVariableDefs(FileCheckDiagList &Diags) {
+    P.printVariableDefs(SM, &Diags);
   }
 
   SourceMgr &getSourceMgr() { return SM; }
 };
 
-#define EXPECT_SM_RANGE(SM, Range, StartLineExpected, StartColExpected,        \
+#define EXPECT_SM_RANGE(SM, RangeOpt, StartLineExpected, StartColExpected,     \
                         EndLineExpected, EndColExpected)                       \
   do {                                                                         \
+    EXPECT_TRUE(RangeOpt->isValid());                                          \
+    SMRange Range = *RangeOpt;                                                 \
     auto StartActual = SM.getLineAndColumn(Range.Start);                       \
     auto EndActual = SM.getLineAndColumn(Range.End);                           \
     EXPECT_EQ(StartActual.first, StartLineExpected);                           \
@@ -1656,17 +1657,17 @@ TEST_F(FileCheckTest, CapturedVarDiags) {
   ASSERT_FALSE(Tester.parsePattern("[[STRVAR:[a-z]+]] [[#NUMVAR:@LINE]]"));
   EXPECT_THAT_EXPECTED(Tester.match("foobar 2"), Succeeded());
   FileCheckDiagList Diags;
-  Tester.printVariableDefs(FileCheckDiag::MatchFoundAndExpected, Diags);
+  Tester.printVariableDefs(Diags);
   EXPECT_EQ(Diags.getList().size(), 2ul);
-  SmallVector<MatchNoteDiag, 2> Notes;
+  SmallVector<MatchCustomNoteDiag, 2> Notes;
   for (const std::unique_ptr<FileCheckDiag> &Diag : Diags.getList()) {
-    EXPECT_EQ(Diag->getKind(), FileCheckDiag::FCDK_MatchNoteDiag);
-    EXPECT_EQ(Diag->getMatchType(), FileCheckDiag::MatchFoundAndExpected);
-    Notes.push_back(cast<MatchNoteDiag>(*Diag));
+    EXPECT_EQ(Diag->getKind(), FileCheckDiag::FCDK_MatchCustomNoteDiag);
+    EXPECT_FALSE(Diag->isError());
+    Notes.push_back(cast<MatchCustomNoteDiag>(*Diag));
   }
-  EXPECT_SM_RANGE(SM, Notes[0].getInputRange(), 1u, 1u, 1u, 7u);
-  EXPECT_SM_RANGE(SM, Notes[1].getInputRange(), 1u, 8u, 1u, 9u);
-  EXPECT_EQ(Notes[0].getCustomNote(), "captured var \"STRVAR\"");
-  EXPECT_EQ(Notes[1].getCustomNote(), "captured var \"NUMVAR\"");
+  EXPECT_SM_RANGE(SM, Notes[0].getMatchRange(), 1u, 1u, 1u, 7u);
+  EXPECT_SM_RANGE(SM, Notes[1].getMatchRange(), 1u, 8u, 1u, 9u);
+  EXPECT_EQ(Notes[0].getNote(), "captured var \"STRVAR\"");
+  EXPECT_EQ(Notes[1].getNote(), "captured var \"NUMVAR\"");
 }
 } // namespace
diff --git a/llvm/utils/FileCheck/FileCheck.cpp b/llvm/utils/FileCheck/FileCheck.cpp
index 7b29238844e4f..459e13cd2933d 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -192,47 +192,71 @@ struct MarkerStyle {
   std::string Note;
   /// Does this marker indicate inclusion by -dump-input-filter=error?
   bool FiltersAsError;
-  MarkerStyle() = default;
-  MarkerStyle(char Lead, raw_ostream::Colors Color,
-              const std::string &Note = "", bool FiltersAsError = false)
-      : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) {
-    assert((!FiltersAsError || !Note.empty()) &&
-           "expected error diagnostic to have note");
-  }
 };
 
-static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) {
-  switch (MatchTy) {
-  case FileCheckDiag::MatchFoundAndExpected:
-    return MarkerStyle('^', raw_ostream::GREEN);
-  case FileCheckDiag::MatchFoundButExcluded:
-    return MarkerStyle('!', raw_ostream::RED, "error: no match expected",
-                       /*FiltersAsError=*/true);
-  case FileCheckDiag::MatchFoundButWrongLine:
-    return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line",
-                       /*FiltersAsError=*/true);
-  case FileCheckDiag::MatchFoundButDiscarded:
-    return MarkerStyle('!', raw_ostream::CYAN,
-                       "discard: overlaps earlier match");
-  case FileCheckDiag::MatchFoundErrorNote:
-    // Note should always be overridden within the FileCheckDiag.
-    return MarkerStyle('!', raw_ostream::RED,
-                       "error: unknown error after match",
-                       /*FiltersAsError=*/true);
-  case FileCheckDiag::MatchNoneAndExcluded:
-    return MarkerStyle('X', raw_ostream::GREEN);
-  case FileCheckDiag::MatchNoneButExpected:
-    return MarkerStyle('X', raw_ostream::RED, "error: no match found",
-                       /*FiltersAsError=*/true);
-  case FileCheckDiag::MatchNoneForInvalidPattern:
-    return MarkerStyle('X', raw_ostream::RED,
-                       "error: match failed for invalid pattern",
-                       /*FiltersAsError=*/true);
-  case FileCheckDiag::MatchFuzzy:
-    return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match",
-                       /*FiltersAsError=*/true);
+static MarkerStyle getMarker(const FileCheckDiag *Diag) {
+  // By default, the marker is based on whether the diagnostic is an error or is
+  // a MatchNoteDiag on a MatchResultDiag that is an error.
+  //
+  // It's less confusing if diagnostics that don't actually have match ranges
+  // don't have markers.  For example, a marker for the MatchNoteDiag
+  // 'with "VAR" equal to "5"' would seem to indicate where "VAR" matches, but
+  // we don't actually have that location.  Instead, we just place the note
+  // after the start of the associated MatchResultDiag.  This decision is
+  // overriden below for the case of MatchNoneDiag because the search range is
+  // used instead.
+  MarkerStyle Res;
+  bool IsError = Diag->isError() || Diag->getMatchResultDiag().isError();
+  Res.Lead = !Diag->getMatchRange() ? ' ' : IsError ? '!' : '^';
+  Res.Color = IsError ? raw_ostream::RED : raw_ostream::GREEN;
+  Res.FiltersAsError = IsError;
+
+  // Add Note.  Override the default Lead and Color for some diagnostic kinds.
+  switch (Diag->getKind()) {
+  case FileCheckDiag::FCDK_MatchFoundDiag:
+    switch (cast<MatchFoundDiag>(Diag)->getStatus()) {
+    case MatchFoundDiag::Success:
+      break;
+    case MatchFoundDiag::Excluded:
+      Res.Note = "no match expected";
+      break;
+    case MatchFoundDiag::WrongLine:
+      Res.Note = "match on wrong line";
+      break;
+    case MatchFoundDiag::Discarded:
+      Res.Lead = '!'; // Not an error, but not a successful match either.
+      Res.Color = raw_ostream::CYAN;
+      Res.Note = "discard: overlaps earlier match";
+      break;
+    }
+    break;
+  case FileCheckDiag::FCDK_MatchNoneDiag:
+    Res.Lead = 'X';
+    switch (cast<MatchNoneDiag>(Diag)->getStatus()) {
+    case MatchNoneDiag::Success:
+      break;
+    case MatchNoneDiag::InvalidPattern:
+      Res.Note = "match failed for invalid pattern";
+      break;
+    case MatchNoneDiag::Expected:
+      Res.Note = "no match found";
+      break;
+    }
+    break;
+  case FileCheckDiag::FCDK_MatchFuzzyDiag:
+    Res.Lead = '?';
+    Res.Color = raw_ostream::MAGENTA;
+    Res.Note = "possible intended match";
+    break;
+  case FileCheckDiag::FCDK_MatchCustomNoteDiag:
+    Res.Note = cast<MatchCustomNoteDiag>(Diag)->getNote();
+    break;
   }
-  llvm_unreachable_internal("unexpected match type");
+  if (Diag->isError()) {
+    assert(!Res.Note.empty() && "expected error diagnostic to have note");
+    Res.Note = "error: " + Res.Note;
+  }
+  return Res;
 }
 
 static void DumpInputAnnotationHelp(raw_ostream &OS) {
@@ -428,39 +452,39 @@ buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
           << DiagIndexPerPattern[Diag->getMatchResultDiag().getCheckLoc()]++;
     LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size());
 
-    A.Marker = GetMarker(Diag->getMatchType());
-    std::optional<StringRef> CustomNote = std::nullopt;
-    if (const MatchNoteDiag *NoteDiag = dyn_cast<MatchNoteDiag>(&*Diag)) {
-      CustomNote = NoteDiag->getCustomNote();
-      if (CustomNote) {
-        A.Marker.Note = *CustomNote;
-        // It's less confusing if notes that don't actually have ranges don't
-        // have markers.  For example, a marker for 'with "VAR" equal to "5"'
-        // would seem to indicate where "VAR" matches, but the location we
-        // actually have for the marker simply points to the start of the
-        // match/search range for the full pattern of which the substitution is
-        // potentially just one component.
-        SMRange InputRange = Diag->getInputRange();
-        if (InputRange.Start == InputRange.End)
-          A.Marker.Lead = ' ';
-      }
+    // Build the input marker.
+    A.Marker = getMarker(Diag.get());
+
+    // Does this diagnostic mark text that has been successfully matched?
+    A.FoundAndExpectedMatch = false;
+    if (const MatchFoundDiag *Found = dyn_cast<MatchFoundDiag>(Diag.get())) {
+      if (Found->getStatus() == MatchFoundDiag::Success)
+        A.FoundAndExpectedMatch = true;
     }
-    if (Diag->getMatchType() == FileCheckDiag::MatchFoundErrorNote) {
-      assert(CustomNote && "expected custom note for MatchFoundErrorNote");
-      A.Marker.Note = "error: " + A.Marker.Note;
+
+    // If Diag has a match range, position the marker there.  If it is a
+    // MatchNoneDiag, position the marker at its search range.  Otherwise,
+    // position the marker at the start of the most recent MatchResultDiag with
+    // which it is associated.
+    SMRange InputRange;
+    if (Diag->getMatchRange()) {
+      InputRange = *Diag->getMatchRange();
+    } else if (MatchNoneDiag *MND = dyn_cast<MatchNoneDiag>(Diag.get())) {
+      InputRange = MND->getSearchRange();
+    } else {
+      assert(isa<MatchNoteDiag>(Diag.get()) &&
+             "expected only MatchNoteDiag to have no input range");
+      const MatchResultDiag &MRD = Diag->getMatchResultDiag();
+      InputRange = MRD.getMatchRange() ? *MRD.getMatchRange()
+                                       : MRD.getSearchRange();
+      InputRange.End = InputRange.Start;
     }
-    A.FoundAndExpectedMatch =
-        Diag->getMatchType() == FileCheckDiag::MatchFoundAndExpected;
+    auto [InputStartLine, InputStartCol] =
+        SM.getLineAndColumn(InputRange.Start);
+    auto [InputEndLine, InputEndCol] = SM.getLineAndColumn(InputRange.End);
 
-    // Compute the mark location, and break annotation into multiple
+    // Compute the marker location, and break annotation into multiple
     // annotations if it spans multiple lines.
-    SMRange InputRange = Diag->getInputRange();
-    auto InputStartLineAndCol = SM.getLineAndColumn(InputRange.Start);
-    auto InputEndLineAndCol = SM.getLineAndColumn(InputRange.End);
-    unsigned InputStartLine = InputStartLineAndCol.first;
-    unsigned InputStartCol = InputStartLineAndCol.second;
-    unsigned InputEndLine = InputEndLineAndCol.first;
-    unsigned InputEndCol = InputEndLineAndCol.second;
     A.IsFirstLine = true;
     A.InputLine = InputStartLine;
     A.InputStartCol = InputStartCol;

>From 2a338aec06f68a34f26c33b1e161c2321e33918f Mon Sep 17 00:00:00 2001
From: "Joel E. Denny" <jdenny.ornl at gmail.com>
Date: Sun, 3 May 2026 21:27:44 -0400
Subject: [PATCH 04/10] Apply clang-format

---
 llvm/utils/FileCheck/FileCheck.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/utils/FileCheck/FileCheck.cpp b/llvm/utils/FileCheck/FileCheck.cpp
index 459e13cd2933d..3088777fa846f 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -475,8 +475,8 @@ buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
       assert(isa<MatchNoteDiag>(Diag.get()) &&
              "expected only MatchNoteDiag to have no input range");
       const MatchResultDiag &MRD = Diag->getMatchResultDiag();
-      InputRange = MRD.getMatchRange() ? *MRD.getMatchRange()
-                                       : MRD.getSearchRange();
+      InputRange =
+          MRD.getMatchRange() ? *MRD.getMatchRange() : MRD.getSearchRange();
       InputRange.End = InputRange.Start;
     }
     auto [InputStartLine, InputStartCol] =

>From 1ff98e8507b0303d0a95230a7de43cf4e0d96b7f Mon Sep 17 00:00:00 2001
From: "Joel E. Denny" <jdenny.ornl at gmail.com>
Date: Mon, 4 May 2026 10:17:46 -0400
Subject: [PATCH 05/10] Fix emplace_back.

---
 llvm/include/llvm/FileCheck/FileCheck.h | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/llvm/include/llvm/FileCheck/FileCheck.h b/llvm/include/llvm/FileCheck/FileCheck.h
index 07684e6fc6cff..5dac41406585c 100644
--- a/llvm/include/llvm/FileCheck/FileCheck.h
+++ b/llvm/include/llvm/FileCheck/FileCheck.h
@@ -181,8 +181,9 @@ class FileCheckDiagList {
 
 public:
   /// Emplace a new \c FileCheckDiag.
-  template <typename... ArgTys> void emplace_back(const ArgTys &...Args) {
-    DiagList.emplace_back(std::make_unique<FileCheckDiag>(Args...));
+  template <typename... ArgTys> void emplace_back(ArgTys &&...Args) {
+    DiagList.emplace_back(
+        std::make_unique<FileCheckDiag>(std::forward<ArgTys>(Args)...));
   }
   /// Adjust recent consecutive diagnostics of the same \c CheckLoc to have
   /// \c MatchTy.

>From 919b0f024235ed247df43885b1bce4804835d70d Mon Sep 17 00:00:00 2001
From: "Joel E. Denny" <jdenny.ornl at gmail.com>
Date: Mon, 4 May 2026 12:19:16 -0400
Subject: [PATCH 06/10] Replace getList with begin, end, operator[], etc.

The new `const_iterator` then relieves clients of dealing with the
`std::unique_ptr`.
---
 llvm/include/llvm/FileCheck/FileCheck.h    | 47 ++++++++++++++++--
 llvm/unittests/FileCheck/FileCheckTest.cpp | 24 +++++-----
 llvm/utils/FileCheck/FileCheck.cpp         | 56 +++++++++++-----------
 3 files changed, 82 insertions(+), 45 deletions(-)

diff --git a/llvm/include/llvm/FileCheck/FileCheck.h b/llvm/include/llvm/FileCheck/FileCheck.h
index 5dac41406585c..ff3de0d4aef20 100644
--- a/llvm/include/llvm/FileCheck/FileCheck.h
+++ b/llvm/include/llvm/FileCheck/FileCheck.h
@@ -18,6 +18,7 @@
 #include "llvm/Support/Regex.h"
 #include "llvm/Support/SMLoc.h"
 #include <bitset>
+#include <iterator>
 #include <memory>
 #include <string>
 #include <vector>
@@ -177,7 +178,8 @@ struct FileCheckDiag {
 /// A \c FileCheckDiag series emitted by the FileCheck library.
 class FileCheckDiagList {
 private:
-  std::vector<std::unique_ptr<FileCheckDiag>> DiagList;
+  using vector_type = std::vector<std::unique_ptr<FileCheckDiag>>;
+  vector_type DiagList;
 
 public:
   /// Emplace a new \c FileCheckDiag.
@@ -193,10 +195,45 @@ class FileCheckDiagList {
          I != E && (*I)->CheckLoc == CheckLoc; ++I)
       (*I)->MatchTy = MatchTy;
   }
-  /// The \c FileCheckDiag list.
-  const std::vector<std::unique_ptr<FileCheckDiag>> &getList() const {
-    return DiagList;
-  }
+  class const_iterator {
+    friend FileCheckDiagList;
+
+  public:
+    using difference_type = std::ptrdiff_t;
+    using value_type = FileCheckDiag;
+    using pointer = const FileCheckDiag *;
+    using reference = const FileCheckDiag &;
+    using iterator_category = std::forward_iterator_tag;
+
+  private:
+    vector_type::const_iterator Itr;
+    const_iterator(vector_type::const_iterator Itr) : Itr(Itr) {}
+
+  public:
+    reference operator*() const { return **Itr; }
+    pointer operator->() const { return &operator*(); }
+    const_iterator &operator++() {
+      ++Itr;
+      return *this;
+    }
+    const_iterator operator++(int) {
+      const_iterator Old = *this;
+      ++Itr;
+      return Old;
+    }
+    bool operator==(const const_iterator &Other) const {
+      return Itr == Other.Itr;
+    }
+    bool operator!=(const const_iterator &Other) const {
+      return Itr != Other.Itr;
+    }
+  };
+
+  using size_type = vector_type::size_type;
+  const_iterator begin() const { return const_iterator(DiagList.begin()); }
+  const_iterator end() const { return const_iterator(DiagList.end()); }
+  const FileCheckDiag &operator[](size_type I) const { return *DiagList[I]; }
+  size_type size() const { return DiagList.size(); }
 };
 
 class FileCheckPatternContext;
diff --git a/llvm/unittests/FileCheck/FileCheckTest.cpp b/llvm/unittests/FileCheck/FileCheckTest.cpp
index cff958a114c63..cc6d4f9ce9325 100644
--- a/llvm/unittests/FileCheck/FileCheckTest.cpp
+++ b/llvm/unittests/FileCheck/FileCheckTest.cpp
@@ -1643,18 +1643,18 @@ TEST_F(FileCheckTest, CapturedVarDiags) {
   EXPECT_THAT_EXPECTED(Tester.match("foobar 2"), Succeeded());
   FileCheckDiagList Diags;
   Tester.printVariableDefs(FileCheckDiag::MatchFoundAndExpected, Diags);
-  EXPECT_EQ(Diags.getList().size(), 2ul);
-  for (const std::unique_ptr<FileCheckDiag> &Diag : Diags.getList()) {
-    EXPECT_EQ(Diag->CheckTy, Check::CheckPlain);
-    EXPECT_EQ(Diag->MatchTy, FileCheckDiag::MatchFoundAndExpected);
-    EXPECT_EQ(Diag->InputStartLine, 1u);
-    EXPECT_EQ(Diag->InputEndLine, 1u);
+  EXPECT_EQ(Diags.size(), 2ul);
+  for (const FileCheckDiag &Diag : Diags) {
+    EXPECT_EQ(Diag.CheckTy, Check::CheckPlain);
+    EXPECT_EQ(Diag.MatchTy, FileCheckDiag::MatchFoundAndExpected);
+    EXPECT_EQ(Diag.InputStartLine, 1u);
+    EXPECT_EQ(Diag.InputEndLine, 1u);
   }
-  EXPECT_EQ(Diags.getList()[0]->InputStartCol, 1u);
-  EXPECT_EQ(Diags.getList()[0]->InputEndCol, 7u);
-  EXPECT_EQ(Diags.getList()[1]->InputStartCol, 8u);
-  EXPECT_EQ(Diags.getList()[1]->InputEndCol, 9u);
-  EXPECT_EQ(Diags.getList()[0]->Note, "captured var \"STRVAR\"");
-  EXPECT_EQ(Diags.getList()[1]->Note, "captured var \"NUMVAR\"");
+  EXPECT_EQ(Diags[0].InputStartCol, 1u);
+  EXPECT_EQ(Diags[0].InputEndCol, 7u);
+  EXPECT_EQ(Diags[1].InputStartCol, 8u);
+  EXPECT_EQ(Diags[1].InputEndCol, 9u);
+  EXPECT_EQ(Diags[0].Note, "captured var \"STRVAR\"");
+  EXPECT_EQ(Diags[1].Note, "captured var \"NUMVAR\"");
 }
 } // namespace
diff --git a/llvm/utils/FileCheck/FileCheck.cpp b/llvm/utils/FileCheck/FileCheck.cpp
index 6cf762bda68d0..45d823dca6808 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -379,7 +379,7 @@ static std::string GetCheckTypeAbbreviation(Check::FileCheckType Ty) {
 static void
 buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
                       const std::pair<unsigned, unsigned> &ImpPatBufferIDRange,
-                      const std::vector<std::unique_ptr<FileCheckDiag>> &Diags,
+                      const FileCheckDiagList &Diags,
                       std::vector<InputAnnotation> &Annotations,
                       unsigned &LabelWidth) {
   struct CompareSMLoc {
@@ -389,23 +389,23 @@ buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
   };
   // How many diagnostics does each pattern have?
   std::map<SMLoc, unsigned, CompareSMLoc> DiagCountPerPattern;
-  for (const std::unique_ptr<FileCheckDiag> &Diag : Diags)
-    ++DiagCountPerPattern[Diag->CheckLoc];
+  for (const FileCheckDiag &Diag : Diags)
+    ++DiagCountPerPattern[Diag.CheckLoc];
   // How many diagnostics have we seen so far per pattern?
   std::map<SMLoc, unsigned, CompareSMLoc> DiagIndexPerPattern;
   // How many total diagnostics have we seen so far?
   unsigned DiagIndex = 0;
   // What's the widest label?
   LabelWidth = 0;
-  for (const std::unique_ptr<FileCheckDiag> &Diag : Diags) {
+  for (const FileCheckDiag &Diag : Diags) {
     InputAnnotation A;
     A.DiagIndex = DiagIndex++;
 
     // Build label, which uniquely identifies this check result.
-    unsigned CheckBufferID = SM.FindBufferContainingLoc(Diag->CheckLoc);
-    auto CheckLineAndCol = SM.getLineAndColumn(Diag->CheckLoc, CheckBufferID);
+    unsigned CheckBufferID = SM.FindBufferContainingLoc(Diag.CheckLoc);
+    auto CheckLineAndCol = SM.getLineAndColumn(Diag.CheckLoc, CheckBufferID);
     llvm::raw_string_ostream Label(A.Label);
-    Label << GetCheckTypeAbbreviation(Diag->CheckTy) << ":";
+    Label << GetCheckTypeAbbreviation(Diag.CheckTy) << ":";
     if (CheckBufferID == CheckFileBufferID)
       Label << CheckLineAndCol.first;
     else if (ImpPatBufferIDRange.first <= CheckBufferID &&
@@ -414,52 +414,52 @@ buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
     else
       llvm_unreachable("expected diagnostic's check location to be either in "
                        "the check file or for an implicit pattern");
-    if (DiagCountPerPattern[Diag->CheckLoc] > 1)
-      Label << "'" << DiagIndexPerPattern[Diag->CheckLoc]++;
+    if (DiagCountPerPattern[Diag.CheckLoc] > 1)
+      Label << "'" << DiagIndexPerPattern[Diag.CheckLoc]++;
     LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size());
 
-    A.Marker = GetMarker(Diag->MatchTy);
-    if (!Diag->Note.empty()) {
-      A.Marker.Note = Diag->Note;
+    A.Marker = GetMarker(Diag.MatchTy);
+    if (!Diag.Note.empty()) {
+      A.Marker.Note = Diag.Note;
       // It's less confusing if notes that don't actually have ranges don't have
       // markers.  For example, a marker for 'with "VAR" equal to "5"' would
       // seem to indicate where "VAR" matches, but the location we actually have
       // for the marker simply points to the start of the match/search range for
       // the full pattern of which the substitution is potentially just one
       // component.
-      if (Diag->InputStartLine == Diag->InputEndLine &&
-          Diag->InputStartCol == Diag->InputEndCol)
+      if (Diag.InputStartLine == Diag.InputEndLine &&
+          Diag.InputStartCol == Diag.InputEndCol)
         A.Marker.Lead = ' ';
     }
-    if (Diag->MatchTy == FileCheckDiag::MatchFoundErrorNote) {
-      assert(!Diag->Note.empty() &&
+    if (Diag.MatchTy == FileCheckDiag::MatchFoundErrorNote) {
+      assert(!Diag.Note.empty() &&
              "expected custom note for MatchFoundErrorNote");
       A.Marker.Note = "error: " + A.Marker.Note;
     }
     A.FoundAndExpectedMatch =
-        Diag->MatchTy == FileCheckDiag::MatchFoundAndExpected;
+        Diag.MatchTy == FileCheckDiag::MatchFoundAndExpected;
 
     // Compute the mark location, and break annotation into multiple
     // annotations if it spans multiple lines.
     A.IsFirstLine = true;
-    A.InputLine = Diag->InputStartLine;
-    A.InputStartCol = Diag->InputStartCol;
-    if (Diag->InputStartLine == Diag->InputEndLine) {
+    A.InputLine = Diag.InputStartLine;
+    A.InputStartCol = Diag.InputStartCol;
+    if (Diag.InputStartLine == Diag.InputEndLine) {
       // Sometimes ranges are empty in order to indicate a specific point, but
       // that would mean nothing would be marked, so adjust the range to
       // include the following character.
-      A.InputEndCol = std::max(Diag->InputStartCol + 1, Diag->InputEndCol);
+      A.InputEndCol = std::max(Diag.InputStartCol + 1, Diag.InputEndCol);
       Annotations.push_back(A);
     } else {
-      assert(Diag->InputStartLine < Diag->InputEndLine &&
+      assert(Diag.InputStartLine < Diag.InputEndLine &&
              "expected input range not to be inverted");
       A.InputEndCol = UINT_MAX;
       Annotations.push_back(A);
-      for (unsigned L = Diag->InputStartLine + 1, E = Diag->InputEndLine;
-           L <= E; ++L) {
+      for (unsigned L = Diag.InputStartLine + 1, E = Diag.InputEndLine; L <= E;
+           ++L) {
         // If a range ends before the first column on a line, then it has no
         // characters on that line, so there's nothing to render.
-        if (Diag->InputEndCol == 1 && L == E)
+        if (Diag.InputEndCol == 1 && L == E)
           break;
         InputAnnotation B;
         B.DiagIndex = A.DiagIndex;
@@ -473,7 +473,7 @@ buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
         if (L != E)
           B.InputEndCol = UINT_MAX;
         else
-          B.InputEndCol = Diag->InputEndCol;
+          B.InputEndCol = Diag.InputEndCol;
         B.FoundAndExpectedMatch = A.FoundAndExpectedMatch;
         Annotations.push_back(B);
       }
@@ -865,8 +865,8 @@ int main(int argc, char **argv) {
            << "\n";
     std::vector<InputAnnotation> Annotations;
     unsigned LabelWidth;
-    buildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange,
-                          Diags.getList(), Annotations, LabelWidth);
+    buildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags,
+                          Annotations, LabelWidth);
     DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext,
                        InputFileText, Annotations, LabelWidth);
   }

>From 425edb5ff7f2012b3ecdb5db9f82418232af1545 Mon Sep 17 00:00:00 2001
From: "Joel E. Denny" <jdenny.ornl at gmail.com>
Date: Tue, 5 May 2026 09:19:46 -0400
Subject: [PATCH 07/10] Drop virtual if override

---
 llvm/include/llvm/FileCheck/FileCheck.h | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/llvm/include/llvm/FileCheck/FileCheck.h b/llvm/include/llvm/FileCheck/FileCheck.h
index c2cb73cf746f4..9a95e76a98cd8 100644
--- a/llvm/include/llvm/FileCheck/FileCheck.h
+++ b/llvm/include/llvm/FileCheck/FileCheck.h
@@ -208,9 +208,7 @@ class MatchResultDiag : public FileCheckDiag {
     return FCD->getKind() == FCDK_MatchResultDiag;
   }
   /// Get itself.
-  virtual const MatchResultDiag &getMatchResultDiag() const override {
-    return *this;
-  }
+  const MatchResultDiag &getMatchResultDiag() const override { return *this; }
   /// What is the type of pattern for this match result?
   Check::FileCheckType getCheckTy() const { return CheckTy; }
   /// Where is the pattern for this match result?
@@ -234,9 +232,7 @@ class MatchNoteDiag : public FileCheckDiag {
     return FCD->getKind() == FCDK_MatchNoteDiag;
   }
   /// Get the note's associated \c MatchResultDiag.
-  virtual const MatchResultDiag &getMatchResultDiag() const override {
-    return *MRD;
-  }
+  const MatchResultDiag &getMatchResultDiag() const override { return *MRD; }
   /// Set the note's associated \c MatchResultDiag.
   void setMatchResultDiag(MatchResultDiag *MRDNew) {
     assert(!MRD && "expected setMatchResultDiag to be called only once");

>From b6fa0615c4a4ba3dd905e81fec8184d39916c7a6 Mon Sep 17 00:00:00 2001
From: "Joel E. Denny" <jdenny.ornl at gmail.com>
Date: Tue, 5 May 2026 09:38:33 -0400
Subject: [PATCH 08/10] Drop redundant init

---
 llvm/utils/FileCheck/FileCheck.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/utils/FileCheck/FileCheck.cpp b/llvm/utils/FileCheck/FileCheck.cpp
index 6492b32861f47..0a8d0e1d10572 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -429,7 +429,7 @@ buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
     LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size());
 
     A.Marker = GetMarker(Diag.getMatchType());
-    std::optional<StringRef> CustomNote = std::nullopt;
+    std::optional<StringRef> CustomNote;
     if (const MatchNoteDiag *NoteDiag = dyn_cast<MatchNoteDiag>(&Diag)) {
       CustomNote = NoteDiag->getCustomNote();
       if (CustomNote) {

>From 82bbf0b1971f98440d99added554c36e0307aee5 Mon Sep 17 00:00:00 2001
From: "Joel E. Denny" <jdenny.ornl at gmail.com>
Date: Tue, 5 May 2026 09:45:00 -0400
Subject: [PATCH 09/10] Drop virtual if override

---
 llvm/include/llvm/FileCheck/FileCheck.h | 22 ++++++++--------------
 1 file changed, 8 insertions(+), 14 deletions(-)

diff --git a/llvm/include/llvm/FileCheck/FileCheck.h b/llvm/include/llvm/FileCheck/FileCheck.h
index 802a31babc769..de76fa789caee 100644
--- a/llvm/include/llvm/FileCheck/FileCheck.h
+++ b/llvm/include/llvm/FileCheck/FileCheck.h
@@ -235,7 +235,7 @@ class MatchFoundDiag : public MatchResultDiag {
   ///
   /// This is not always the same as \c getStatus()!=Success.  For example,
   /// \c CHECK-DAG discarded matches are neither successful matches nor errors.
-  virtual bool isError() const override {
+  bool isError() const override {
     return Status != Success && Status != Discarded;
   }
   /// Was this a successful match?  If not, why not?
@@ -257,9 +257,7 @@ class MatchFoundDiag : public MatchResultDiag {
     Status = S;
   }
   /// Return the match's input range, never \c std::nullopt.
-  virtual std::optional<SMRange> getMatchRange() const override {
-    return MatchRange;
-  }
+  std::optional<SMRange> getMatchRange() const override { return MatchRange; }
 };
 
 /// \c MatchResultDiag for a pattern that did not match the input.
@@ -291,13 +289,11 @@ class MatchNoneDiag : public MatchResultDiag {
     return FCD->getKind() == FCDK_MatchNoneDiag;
   }
   /// Does the lack of match represent an error?
-  virtual bool isError() const override { return Status != Success; }
+  bool isError() const override { return Status != Success; }
   /// Does the lack of a match indicate a success?  If not, why not?
   StatusTy getStatus() const { return Status; }
   /// Return \c std::nullopt.
-  virtual std::optional<SMRange> getMatchRange() const override {
-    return std::nullopt;
-  }
+  std::optional<SMRange> getMatchRange() const override { return std::nullopt; }
 };
 
 /// Abstract base class for recording a FileCheck diagnostic that provides an
@@ -340,10 +336,10 @@ class MatchFuzzyDiag : public MatchNoteDiag {
   }
   /// Always false.  A fuzzy match is not an error even though it is performed
   /// due to an error.
-  virtual bool isError() const override { return false; }
+  bool isError() const override { return false; }
   /// Return an input range (never \c std::nullopt) starting and ending at the
   /// match start.  The actual match end is not computed.
-  virtual std::optional<SMRange> getMatchRange() const override {
+  std::optional<SMRange> getMatchRange() const override {
     return SMRange(MatchStart, MatchStart);
   }
 };
@@ -394,11 +390,9 @@ class MatchCustomNoteDiag : public MatchNoteDiag {
   ///
   /// For details, see the \c MatchCustomNoteDiag::MatchCustomNoteDiag comments
   /// for its \c AddsError parameter.
-  virtual bool isError() const override { return AddsError; }
+  bool isError() const override { return AddsError; }
   /// Return the match range described by the note, or \c std::nullopt if none.
-  virtual std::optional<SMRange> getMatchRange() const override {
-    return MatchRange;
-  }
+  std::optional<SMRange> getMatchRange() const override { return MatchRange; }
 };
 
 /// A \c FileCheckDiag series emitted by the FileCheck library.

>From 843f8e2cdf65e0ab96904ca11e1baa7828909b32 Mon Sep 17 00:00:00 2001
From: "Joel E. Denny" <jdenny.ornl at gmail.com>
Date: Tue, 5 May 2026 18:06:12 -0400
Subject: [PATCH 10/10] Drop FCDK_

---
 llvm/include/llvm/FileCheck/FileCheck.h    | 48 +++++++++++-----------
 llvm/unittests/FileCheck/FileCheckTest.cpp |  2 +-
 llvm/utils/FileCheck/FileCheck.cpp         |  8 ++--
 3 files changed, 30 insertions(+), 28 deletions(-)

diff --git a/llvm/include/llvm/FileCheck/FileCheck.h b/llvm/include/llvm/FileCheck/FileCheck.h
index de76fa789caee..b000f5b0dac73 100644
--- a/llvm/include/llvm/FileCheck/FileCheck.h
+++ b/llvm/include/llvm/FileCheck/FileCheck.h
@@ -134,15 +134,15 @@ class FileCheckDiag {
 public:
   enum FileCheckDiagKind {
     // MatchResultDiag
-    FCDK_MatchResultDiag_First,
-    FCDK_MatchFoundDiag = FCDK_MatchResultDiag_First,
-    FCDK_MatchNoneDiag,
-    FCDK_MatchResultDiag_Last = FCDK_MatchNoneDiag,
+    MatchResultDiag_First,
+    MatchFoundDiag = MatchResultDiag_First,
+    MatchNoneDiag,
+    MatchResultDiag_Last = MatchNoneDiag,
     // MatchNoteDiag
-    FCDK_MatchNoteDiag_First,
-    FCDK_MatchFuzzyDiag = FCDK_MatchNoteDiag_First,
-    FCDK_MatchCustomNoteDiag,
-    FCDK_MatchNoteDiag_Last = FCDK_MatchCustomNoteDiag
+    MatchNoteDiag_First,
+    MatchFuzzyDiag = MatchNoteDiag_First,
+    MatchCustomNoteDiag,
+    MatchNoteDiag_Last = MatchCustomNoteDiag
   };
 
 private:
@@ -189,9 +189,8 @@ class MatchResultDiag : public FileCheckDiag {
   virtual ~MatchResultDiag() = 0;
   /// Is \p FCD an instance of \c MatchResultDiag?
   static bool classof(const FileCheckDiag *FCD) {
-    FileCheckDiagKind FCDK = FCD->getKind();
-    return FCDK_MatchResultDiag_First <= FCDK &&
-           FCDK <= FCDK_MatchResultDiag_Last;
+    FileCheckDiagKind Kind = FCD->getKind();
+    return MatchResultDiag_First <= Kind && Kind <= MatchResultDiag_Last;
   }
   /// Get itself.
   const MatchResultDiag &getMatchResultDiag() const override { return *this; }
@@ -225,11 +224,12 @@ class MatchFoundDiag : public MatchResultDiag {
 public:
   MatchFoundDiag(const Check::FileCheckType &CheckTy, SMLoc CheckLoc,
                  StatusTy Status, SMRange MatchRange, SMRange SearchRange)
-      : MatchResultDiag(FCDK_MatchFoundDiag, CheckTy, CheckLoc, SearchRange),
+      : MatchResultDiag(FileCheckDiag::MatchFoundDiag, CheckTy, CheckLoc,
+                        SearchRange),
         Status(Status), MatchRange(MatchRange) {}
   /// Is \p FCD an instance of \c MatchFoundDiag?
   static bool classof(const FileCheckDiag *FCD) {
-    return FCD->getKind() == FCDK_MatchFoundDiag;
+    return FCD->getKind() == FileCheckDiag::MatchFoundDiag;
   }
   /// Does this match produce an error?
   ///
@@ -282,11 +282,12 @@ class MatchNoneDiag : public MatchResultDiag {
 public:
   MatchNoneDiag(const Check::FileCheckType &CheckTy, SMLoc CheckLoc,
                 StatusTy Status, SMRange SearchRange)
-      : MatchResultDiag(FCDK_MatchNoneDiag, CheckTy, CheckLoc, SearchRange),
+      : MatchResultDiag(FileCheckDiag::MatchNoneDiag, CheckTy, CheckLoc,
+                        SearchRange),
         Status(Status) {}
   /// Is \p FCD an instance of \c MatchNoneDiag?
   static bool classof(const FileCheckDiag *FCD) {
-    return FCD->getKind() == FCDK_MatchNoneDiag;
+    return FCD->getKind() == FileCheckDiag::MatchNoneDiag;
   }
   /// Does the lack of match represent an error?
   bool isError() const override { return Status != Success; }
@@ -304,13 +305,13 @@ class MatchNoteDiag : public FileCheckDiag {
   MatchResultDiag *MRD;
 
 public:
-  MatchNoteDiag(FileCheckDiagKind FCDK) : FileCheckDiag(FCDK), MRD(nullptr) {}
+  MatchNoteDiag(FileCheckDiagKind Kind) : FileCheckDiag(Kind), MRD(nullptr) {}
   /// Destructor is purely virtual to ensure this remains an abstract class.
   virtual ~MatchNoteDiag() = 0;
   /// Is \p FCD an instance of \c MatchNoteDiag?
   static bool classof(const FileCheckDiag *FCD) {
-    FileCheckDiagKind FCDK = FCD->getKind();
-    return FCDK_MatchNoteDiag_First <= FCDK && FCDK <= FCDK_MatchNoteDiag_Last;
+    FileCheckDiagKind Kind = FCD->getKind();
+    return MatchNoteDiag_First <= Kind && Kind <= MatchNoteDiag_Last;
   }
   /// Get the note's associated \c MatchResultDiag.
   const MatchResultDiag &getMatchResultDiag() const override { return *MRD; }
@@ -329,10 +330,10 @@ class MatchFuzzyDiag : public MatchNoteDiag {
 
 public:
   MatchFuzzyDiag(SMLoc MatchStart)
-      : MatchNoteDiag(FCDK_MatchFuzzyDiag), MatchStart(MatchStart) {}
+      : MatchNoteDiag(FileCheckDiag::MatchFuzzyDiag), MatchStart(MatchStart) {}
   /// Is \p FCD an instance of \c MatchFuzzyDiag?
   static bool classof(const FileCheckDiag *FCD) {
-    return FCD->getKind() == FCDK_MatchFuzzyDiag;
+    return FCD->getKind() == FileCheckDiag::MatchFuzzyDiag;
   }
   /// Always false.  A fuzzy match is not an error even though it is performed
   /// due to an error.
@@ -375,14 +376,15 @@ class MatchCustomNoteDiag : public MatchNoteDiag {
   ///@{
   MatchCustomNoteDiag(SMRange MatchRange, StringRef Note,
                       bool AddsError = false)
-      : MatchNoteDiag(FCDK_MatchCustomNoteDiag), Note(Note),
+      : MatchNoteDiag(FileCheckDiag::MatchCustomNoteDiag), Note(Note),
         AddsError(AddsError), MatchRange(MatchRange) {}
   MatchCustomNoteDiag(StringRef Note)
-      : MatchNoteDiag(FCDK_MatchCustomNoteDiag), Note(Note), AddsError(false) {}
+      : MatchNoteDiag(FileCheckDiag::MatchCustomNoteDiag), Note(Note),
+        AddsError(false) {}
   ///@}
   /// Is \p FCD an instance of \c MatchCustomNoteDiag?
   static bool classof(const FileCheckDiag *FCD) {
-    return FCD->getKind() == FCDK_MatchCustomNoteDiag;
+    return FCD->getKind() == FileCheckDiag::MatchCustomNoteDiag;
   }
   const std::string &getNote() const { return Note; }
   /// Does this note indicate an \a additional error not indicated by the
diff --git a/llvm/unittests/FileCheck/FileCheckTest.cpp b/llvm/unittests/FileCheck/FileCheckTest.cpp
index 77065cfa5a94a..a76edfb426433 100644
--- a/llvm/unittests/FileCheck/FileCheckTest.cpp
+++ b/llvm/unittests/FileCheck/FileCheckTest.cpp
@@ -1661,7 +1661,7 @@ TEST_F(FileCheckTest, CapturedVarDiags) {
   EXPECT_EQ(Diags.size(), 2ul);
   SmallVector<MatchCustomNoteDiag, 2> Notes;
   for (const FileCheckDiag &Diag : Diags) {
-    EXPECT_EQ(Diag.getKind(), FileCheckDiag::FCDK_MatchCustomNoteDiag);
+    EXPECT_EQ(Diag.getKind(), FileCheckDiag::MatchCustomNoteDiag);
     EXPECT_FALSE(Diag.isError());
     Notes.push_back(cast<MatchCustomNoteDiag>(Diag));
   }
diff --git a/llvm/utils/FileCheck/FileCheck.cpp b/llvm/utils/FileCheck/FileCheck.cpp
index f0533574a344e..63b745469b8ee 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -213,7 +213,7 @@ static MarkerStyle getMarker(const FileCheckDiag &Diag) {
 
   // Add Note.  Override the default Lead and Color for some diagnostic kinds.
   switch (Diag.getKind()) {
-  case FileCheckDiag::FCDK_MatchFoundDiag:
+  case FileCheckDiag::MatchFoundDiag:
     switch (cast<MatchFoundDiag>(Diag).getStatus()) {
     case MatchFoundDiag::Success:
       break;
@@ -230,7 +230,7 @@ static MarkerStyle getMarker(const FileCheckDiag &Diag) {
       break;
     }
     break;
-  case FileCheckDiag::FCDK_MatchNoneDiag:
+  case FileCheckDiag::MatchNoneDiag:
     Res.Lead = 'X';
     switch (cast<MatchNoneDiag>(Diag).getStatus()) {
     case MatchNoneDiag::Success:
@@ -243,12 +243,12 @@ static MarkerStyle getMarker(const FileCheckDiag &Diag) {
       break;
     }
     break;
-  case FileCheckDiag::FCDK_MatchFuzzyDiag:
+  case FileCheckDiag::MatchFuzzyDiag:
     Res.Lead = '?';
     Res.Color = raw_ostream::MAGENTA;
     Res.Note = "possible intended match";
     break;
-  case FileCheckDiag::FCDK_MatchCustomNoteDiag:
+  case FileCheckDiag::MatchCustomNoteDiag:
     Res.Note = cast<MatchCustomNoteDiag>(Diag).getNote();
     break;
   }



More information about the llvm-commits mailing list