[Mlir-commits] [mlir] a1fa72e - [Support] Extract SourceMgrDiagnosticVerifier from MLIR's verifier handler. NFC (#227565)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Mon Oct 19 16:46:21 PDT 2026


Author: Alexander Richardson
Date: 2026-10-19T16:46:14-07:00
New Revision: a1fa72ed1dbfa02f07d0b0e98b8b4a22906578d3

URL: https://github.com/llvm/llvm-project/commit/a1fa72ed1dbfa02f07d0b0e98b8b4a22906578d3
DIFF: https://github.com/llvm/llvm-project/commit/a1fa72ed1dbfa02f07d0b0e98b8b4a22906578d3.diff

LOG: [Support] Extract SourceMgrDiagnosticVerifier from MLIR's verifier handler. NFC (#227565)

Move the SourceMgr-based `expected-(error|warning|remark|note)` diagnostic
parsing and matching logic out of `mlir/lib/IR/Diagnostics.cpp` into a
reusable `llvm::SourceMgrDiagnosticVerifier` class in `LLVMSupport` so that
non-MLIR tools (such as `llvm-mc -verify`) can share the same
implementation.

`mlir::SourceMgrDiagnosticVerifierHandler` is now a thin MLIR-specific
adapter around `llvm::SourceMgrDiagnosticVerifier`.

Assisted-by: AI tooling
Pull-Request: https://github.com/llvm/llvm-project/pull/227565

Added: 
    llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h
    llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp

Modified: 
    llvm/lib/Support/CMakeLists.txt
    llvm/utils/gn/secondary/llvm/lib/Support/BUILD.gn
    mlir/lib/IR/Diagnostics.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h b/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h
new file mode 100644
index 0000000000000..0a81f461d618c
--- /dev/null
+++ b/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h
@@ -0,0 +1,144 @@
+//===- SourceMgrDiagnosticVerifier.h ---------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines a utility for verifying that diagnostics reported through
+// a SourceMgr match 'expected-<kind>' comments in the source, for
+// implementing '-verify'-style diagnostic tests on top of a plain
+// llvm::SourceMgr.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_SUPPORT_SOURCEMGRDIAGNOSTICVERIFIER_H
+#define LLVM_SUPPORT_SOURCEMGRDIAGNOSTICVERIFIER_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/Support/Compiler.h"
+#include "llvm/Support/Regex.h"
+#include "llvm/Support/SourceMgr.h"
+#include <optional>
+
+namespace llvm {
+
+class MemoryBuffer;
+class raw_ostream;
+
+/// Scans SourceMgr source buffers for 'expected-<kind>' comments (e.g.
+/// 'expected-error {{message}}') and verifies that the diagnostics reported
+/// to it via \c process match them, and that every expected diagnostic is
+/// matched at least once.
+class LLVM_ABI SourceMgrDiagnosticVerifier {
+public:
+  /// The result of matching a single actual diagnostic against the expected
+  /// diagnostics recorded for its location.
+  enum class MatchResult {
+    /// The diagnostic matched an expected diagnostic (kind and text).
+    Matched,
+    /// The diagnostic's text matched an expected diagnostic, but its kind
+    /// didn't; a message about this has already been printed.
+    NearMiss,
+    /// The diagnostic did not match any expected diagnostic. The caller is
+    /// responsible for reporting it, if desired.
+    Unexpected,
+    /// The diagnostic did not match, but \p reportUnexpected was false, so
+    /// nothing was printed and nothing needs to be done.
+    Ignored,
+  };
+
+  /// A single diagnostic expected via an 'expected-<kind>' comment.
+  struct ExpectedDiag {
+    ExpectedDiag(SourceMgr::DiagKind kind, unsigned lineNo, SMLoc fileLoc,
+                 StringRef substring)
+        : kind(kind), lineNo(lineNo), fileLoc(fileLoc), substring(substring) {}
+
+    /// Returns true if this diagnostic matches the given message.
+    LLVM_ABI bool match(StringRef str) const;
+
+    /// Computes the regex matcher for a '-re' diagnostic's substring.
+    /// Returns false and prints a message through \p mgr on error.
+    LLVM_ABI bool computeRegex(raw_ostream &os, SourceMgr &mgr);
+
+    /// Prints \p msg at this diagnostic's location and returns false, for
+    /// use as `return emitError(...);` in functions that report failure via
+    /// a bool return.
+    LLVM_ABI bool emitError(raw_ostream &os, SourceMgr &mgr,
+                            const Twine &msg) const;
+
+    /// The severity of the diagnostic expected.
+    SourceMgr::DiagKind kind;
+    /// The line number the expected diagnostic should be on.
+    unsigned lineNo;
+    /// The location of the expected diagnostic within the input file.
+    SMLoc fileLoc;
+    /// A flag indicating if the expected diagnostic has been matched yet.
+    bool matched = false;
+    /// The substring that is expected to be within the diagnostic.
+    StringRef substring;
+    /// An optional regex matcher, if the expected diagnostic substring was a
+    /// regex string.
+    std::optional<Regex> substringRegex;
+  };
+
+  SourceMgrDiagnosticVerifier() = default;
+
+  /// Computes and caches the list of expected diagnostics for \p buf, if not
+  /// already cached. Returns the (mutable) cached list.
+  MutableArrayRef<ExpectedDiag> computeExpectedDiags(raw_ostream &os,
+                                                     SourceMgr &mgr,
+                                                     const MemoryBuffer *buf);
+
+  /// Returns the cached expected diagnostics for the buffer named \p bufName,
+  /// or std::nullopt if \p computeExpectedDiags hasn't been called for it.
+  std::optional<MutableArrayRef<ExpectedDiag>>
+  getExpectedDiags(StringRef bufName);
+
+  /// Returns the expected diagnostics with an '@unknown' location.
+  MutableArrayRef<ExpectedDiag> getExpectedUnknownLocDiags() {
+    return expectedUnknownLocDiags;
+  }
+
+  /// Matches a single actual diagnostic against the expected diagnostics
+  /// recorded for \p buf / \p lineNo, computing them first via \p
+  /// computeExpectedDiags if they haven't been already. If \p hasLoc is
+  /// false, the diagnostic has no location and is matched against the
+  /// '@unknown' list instead (\p buf / \p lineNo are ignored). If \p hasLoc
+  /// is true but \p buf is null (e.g. the diagnostic's file isn't a known
+  /// buffer), the diagnostic is matched against an empty list, i.e. it can
+  /// never match and is always unexpected. On a near miss, prints a message
+  /// through \p mgr. \p reportUnexpected controls whether near misses /
+  /// unexpected diagnostics are reported at all.
+  MatchResult process(raw_ostream &os, SourceMgr &mgr, SourceMgr::DiagKind kind,
+                      bool hasLoc, const MemoryBuffer *buf, unsigned lineNo,
+                      StringRef message, bool reportUnexpected = true);
+
+  /// Reports (through \p mgr) any expected diagnostic that was never matched
+  /// by a call to \p process. Returns whether verification succeeded overall,
+  /// i.e. no diagnostic mismatches were recorded either here or by \p
+  /// process.
+  bool verify(raw_ostream &os, SourceMgr &mgr);
+
+private:
+  /// Regex used to recognize 'expected-<kind>' comments.
+  Regex expected{"expected-(error|note|remark|warning)(-re)? "
+                 "*(@([+-][0-9]+|above|below|unknown))? *{{(.*)}}$"};
+
+  /// The expected diagnostics for each buffer that has been scanned so far,
+  /// keyed by buffer identifier (i.e. file name).
+  StringMap<SmallVector<ExpectedDiag, 2>> expectedDiagsPerFile;
+
+  /// The expected diagnostics with an '@unknown' location.
+  SmallVector<ExpectedDiag, 2> expectedUnknownLocDiags;
+
+  /// Whether any diagnostic mismatch has been recorded so far.
+  bool ok = true;
+};
+
+} // namespace llvm
+
+#endif // LLVM_SUPPORT_SOURCEMGRDIAGNOSTICVERIFIER_H

diff  --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt
index 40c5c392addd5..3d3e27d89ef35 100644
--- a/llvm/lib/Support/CMakeLists.txt
+++ b/llvm/lib/Support/CMakeLists.txt
@@ -271,6 +271,7 @@ add_llvm_component_library(LLVMSupport
   SmallPtrSet.cpp
   SmallVector.cpp
   SourceMgr.cpp
+  SourceMgrDiagnosticVerifier.cpp
   SpecialCaseList.cpp
   Statistic.cpp
   StringExtras.cpp

diff  --git a/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp b/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp
new file mode 100644
index 0000000000000..1e33637f05bc4
--- /dev/null
+++ b/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp
@@ -0,0 +1,255 @@
+//===- SourceMgrDiagnosticVerifier.cpp -----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/SourceMgrDiagnosticVerifier.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace llvm;
+
+/// Given a diagnostic kind, return a human readable string for it.
+static StringRef getDiagKindStr(SourceMgr::DiagKind kind) {
+  switch (kind) {
+  case SourceMgr::DK_Note:
+    return "note";
+  case SourceMgr::DK_Warning:
+    return "warning";
+  case SourceMgr::DK_Error:
+    return "error";
+  case SourceMgr::DK_Remark:
+    return "remark";
+  }
+  llvm_unreachable("Unknown SourceMgr::DiagKind");
+}
+
+bool SourceMgrDiagnosticVerifier::ExpectedDiag::emitError(
+    raw_ostream &os, SourceMgr &mgr, const Twine &msg) const {
+  if (fileLoc.isValid()) {
+    SMRange range(fileLoc, SMLoc::getFromPointer(fileLoc.getPointer() +
+                                                 substring.size()));
+    mgr.PrintMessage(os, fileLoc, SourceMgr::DK_Error, msg, range);
+  } else {
+    mgr.PrintMessage(os, fileLoc, SourceMgr::DK_Error, msg);
+  }
+  return false;
+}
+
+bool SourceMgrDiagnosticVerifier::ExpectedDiag::match(StringRef str) const {
+  // If this isn't a regex diagnostic, we simply check if the string was
+  // contained.
+  if (substringRegex)
+    return substringRegex->match(str);
+  return str.contains(substring);
+}
+
+bool SourceMgrDiagnosticVerifier::ExpectedDiag::computeRegex(raw_ostream &os,
+                                                             SourceMgr &mgr) {
+  std::string regexStr;
+  raw_string_ostream regexOS(regexStr);
+  StringRef strToProcess = substring;
+  while (!strToProcess.empty()) {
+    // Find the next regex block.
+    size_t regexIt = strToProcess.find("{{");
+    if (regexIt == StringRef::npos) {
+      regexOS << Regex::escape(strToProcess);
+      break;
+    }
+    regexOS << Regex::escape(strToProcess.take_front(regexIt));
+    strToProcess = strToProcess.drop_front(regexIt + 2);
+
+    // Find the end of the regex block.
+    size_t regexEndIt = strToProcess.find("}}");
+    if (regexEndIt == StringRef::npos)
+      return emitError(os, mgr, "found start of regex with no end '}}'");
+    StringRef regexBlock = strToProcess.take_front(regexEndIt);
+
+    // Validate that the regex is actually valid.
+    std::string regexError;
+    if (!Regex(regexBlock).isValid(regexError))
+      return emitError(os, mgr, "invalid regex: " + regexError);
+
+    regexOS << '(' << regexBlock << ')';
+    strToProcess = strToProcess.drop_front(regexEndIt + 2);
+  }
+  substringRegex = Regex(regexStr);
+  return true;
+}
+
+MutableArrayRef<SourceMgrDiagnosticVerifier::ExpectedDiag>
+SourceMgrDiagnosticVerifier::computeExpectedDiags(raw_ostream &os,
+                                                  SourceMgr &mgr,
+                                                  const MemoryBuffer *buf) {
+  // If the buffer is invalid, return an empty list.
+  if (!buf)
+    return {};
+  auto &expectedDiags = expectedDiagsPerFile[buf->getBufferIdentifier()];
+
+  // The number of the last line that did not correlate to a designator.
+  unsigned lastNonDesignatorLine = 0;
+
+  // The indices of designators that apply to the next non designator line.
+  SmallVector<unsigned, 1> designatorsForNextLine;
+
+  // Scan the file for expected-* designators.
+  SmallVector<StringRef, 100> lines;
+  buf->getBuffer().split(lines, '\n');
+  for (unsigned lineNo = 0, e = lines.size(); lineNo < e; ++lineNo) {
+    SmallVector<StringRef, 4> matches;
+    if (!expected.match(lines[lineNo].rtrim(), &matches)) {
+      // Check for designators that apply to this line.
+      if (!designatorsForNextLine.empty()) {
+        for (unsigned diagIndex : designatorsForNextLine)
+          expectedDiags[diagIndex].lineNo = lineNo + 1;
+        designatorsForNextLine.clear();
+      }
+      lastNonDesignatorLine = lineNo;
+      continue;
+    }
+
+    // Point to the start of expected-*.
+    SMLoc expectedStart = SMLoc::getFromPointer(matches[0].data());
+
+    SourceMgr::DiagKind kind;
+    if (matches[1] == "error")
+      kind = SourceMgr::DK_Error;
+    else if (matches[1] == "warning")
+      kind = SourceMgr::DK_Warning;
+    else if (matches[1] == "remark")
+      kind = SourceMgr::DK_Remark;
+    else {
+      assert(matches[1] == "note");
+      kind = SourceMgr::DK_Note;
+    }
+    ExpectedDiag record(kind, lineNo + 1, expectedStart, matches[5]);
+
+    // Check to see if this is a regex match, i.e. it includes the `-re`.
+    if (!matches[2].empty() && !record.computeRegex(os, mgr)) {
+      ok = false;
+      continue;
+    }
+
+    StringRef offsetMatch = matches[3];
+    if (!offsetMatch.empty()) {
+      offsetMatch = offsetMatch.drop_front(1);
+
+      // Get the integer value without the @ and +/- prefix.
+      if (offsetMatch[0] == '+' || offsetMatch[0] == '-') {
+        int offset;
+        offsetMatch.drop_front().getAsInteger(0, offset);
+
+        if (offsetMatch.front() == '+')
+          record.lineNo += offset;
+        else
+          record.lineNo -= offset;
+      } else if (offsetMatch.consume_front("unknown")) {
+        // This is matching unknown locations.
+        record.fileLoc = SMLoc();
+        expectedUnknownLocDiags.emplace_back(std::move(record));
+        continue;
+      } else if (offsetMatch.consume_front("above")) {
+        // If the designator applies 'above' we add it to the last non
+        // designator line.
+        record.lineNo = lastNonDesignatorLine + 1;
+      } else {
+        // Otherwise, this is a 'below' designator and applies to the next
+        // non-designator line.
+        assert(offsetMatch.consume_front("below"));
+        designatorsForNextLine.push_back(expectedDiags.size());
+
+        // Set the line number to the last in the case that this designator
+        // ends up dangling.
+        record.lineNo = e;
+      }
+    }
+    expectedDiags.emplace_back(std::move(record));
+  }
+  return expectedDiags;
+}
+
+std::optional<MutableArrayRef<SourceMgrDiagnosticVerifier::ExpectedDiag>>
+SourceMgrDiagnosticVerifier::getExpectedDiags(StringRef bufName) {
+  auto expectedDiags = expectedDiagsPerFile.find(bufName);
+  if (expectedDiags != expectedDiagsPerFile.end())
+    return MutableArrayRef<ExpectedDiag>(expectedDiags->second);
+  return std::nullopt;
+}
+
+SourceMgrDiagnosticVerifier::MatchResult
+SourceMgrDiagnosticVerifier::process(raw_ostream &os, SourceMgr &mgr,
+                                     SourceMgr::DiagKind kind, bool hasLoc,
+                                     const MemoryBuffer *buf, unsigned lineNo,
+                                     StringRef message, bool reportUnexpected) {
+  MutableArrayRef<ExpectedDiag> diags;
+  if (hasLoc) {
+    // If the buffer couldn't be resolved, `diags` stays empty: a diagnostic
+    // with a location in an unknown file can never match anything.
+    if (buf) {
+      if (auto maybeDiags = getExpectedDiags(buf->getBufferIdentifier()))
+        diags = *maybeDiags;
+      else
+        diags = computeExpectedDiags(os, mgr, buf);
+    }
+  } else {
+    diags = expectedUnknownLocDiags;
+  }
+
+  // Search for a matching expected diagnostic.
+  // If we find something that is close then emit a more specific error.
+  ExpectedDiag *nearMiss = nullptr;
+
+  // If this was an expected error, remember that we saw it and return.
+  for (auto &e : diags) {
+    // File line must match (unless it's an unknown location).
+    if (hasLoc && e.lineNo != lineNo)
+      continue;
+    if (e.match(message)) {
+      if (e.kind == kind) {
+        e.matched = true;
+        return MatchResult::Matched;
+      }
+
+      // If this only 
diff ers based on the diagnostic kind, then consider it
+      // to be a near miss.
+      nearMiss = &e;
+    }
+  }
+
+  if (!reportUnexpected)
+    return MatchResult::Ignored;
+
+  ok = false;
+
+  // Otherwise, emit an error for the near miss.
+  if (nearMiss) {
+    mgr.PrintMessage(os, nearMiss->fileLoc, SourceMgr::DK_Error,
+                     "'" + getDiagKindStr(kind) +
+                         "' diagnostic emitted when expecting a '" +
+                         getDiagKindStr(nearMiss->kind) + "'");
+    return MatchResult::NearMiss;
+  }
+  return MatchResult::Unexpected;
+}
+
+bool SourceMgrDiagnosticVerifier::verify(raw_ostream &os, SourceMgr &mgr) {
+  // Verify that all expected errors were seen.
+  auto checkExpectedDiags = [&](ExpectedDiag &diag) {
+    if (!diag.matched) {
+      diag.emitError(os, mgr,
+                     "expected " + getDiagKindStr(diag.kind) + " \"" +
+                         diag.substring + "\" was not produced");
+      ok = false;
+    }
+  };
+  for (auto &expectedDiagsPair : expectedDiagsPerFile)
+    for (auto &diag : expectedDiagsPair.second)
+      checkExpectedDiags(diag);
+  for (auto &diag : expectedUnknownLocDiags)
+    checkExpectedDiags(diag);
+  expectedDiagsPerFile.clear();
+  return ok;
+}

diff  --git a/llvm/utils/gn/secondary/llvm/lib/Support/BUILD.gn b/llvm/utils/gn/secondary/llvm/lib/Support/BUILD.gn
index fb7b057acbf7c..35f364e073a69 100644
--- a/llvm/utils/gn/secondary/llvm/lib/Support/BUILD.gn
+++ b/llvm/utils/gn/secondary/llvm/lib/Support/BUILD.gn
@@ -153,6 +153,7 @@ static_library("Support") {
     "SmallPtrSet.cpp",
     "SmallVector.cpp",
     "SourceMgr.cpp",
+    "SourceMgrDiagnosticVerifier.cpp",
     "SpecialCaseList.cpp",
     "Statistic.cpp",
     "StringExtras.cpp",

diff  --git a/mlir/lib/IR/Diagnostics.cpp b/mlir/lib/IR/Diagnostics.cpp
index 8d1cdb4df12be..1e0a6aca7f19d 100644
--- a/mlir/lib/IR/Diagnostics.cpp
+++ b/mlir/lib/IR/Diagnostics.cpp
@@ -21,6 +21,7 @@
 #include "llvm/Support/Regex.h"
 #include "llvm/Support/Signals.h"
 #include "llvm/Support/SourceMgr.h"
+#include "llvm/Support/SourceMgrDiagnosticVerifier.h"
 #include "llvm/Support/raw_ostream.h"
 #include <atomic>
 #include <optional>
@@ -638,122 +639,17 @@ SMLoc SourceMgrDiagnosticHandler::convertLocToSMLoc(FileLineColLoc loc) {
 
 namespace mlir {
 namespace detail {
-/// This class represents an expected output diagnostic.
-struct ExpectedDiag {
-  ExpectedDiag(DiagnosticSeverity kind, unsigned lineNo, SMLoc fileLoc,
-               StringRef substring)
-      : kind(kind), lineNo(lineNo), fileLoc(fileLoc), substring(substring) {}
-
-  /// Emit an error at the location referenced by this diagnostic.
-  LogicalResult emitError(raw_ostream &os, llvm::SourceMgr &mgr,
-                          const Twine &msg) {
-    // fileLoc may be invalid when the expected diagnostic used an unknown
-    // location specifier (e.g. `// expected-error @unknown {{...}}`). In that
-    // case, skip the source range to avoid a null-pointer dereference and an
-    // assertion in SMRange that both endpoints must have the same validity.
-    if (fileLoc.isValid()) {
-      SMRange range(fileLoc, SMLoc::getFromPointer(fileLoc.getPointer() +
-                                                   substring.size()));
-      mgr.PrintMessage(os, fileLoc, llvm::SourceMgr::DK_Error, msg, range);
-    } else {
-      mgr.PrintMessage(os, fileLoc, llvm::SourceMgr::DK_Error, msg);
-    }
-    return failure();
-  }
-
-  /// Returns true if this diagnostic matches the given string.
-  bool match(StringRef str) const {
-    // If this isn't a regex diagnostic, we simply check if the string was
-    // contained.
-    if (substringRegex)
-      return substringRegex->match(str);
-    return str.contains(substring);
-  }
-
-  /// Compute the regex matcher for this diagnostic, using the provided stream
-  /// and manager to emit diagnostics as necessary.
-  LogicalResult computeRegex(raw_ostream &os, llvm::SourceMgr &mgr) {
-    std::string regexStr;
-    llvm::raw_string_ostream regexOS(regexStr);
-    StringRef strToProcess = substring;
-    while (!strToProcess.empty()) {
-      // Find the next regex block.
-      size_t regexIt = strToProcess.find("{{");
-      if (regexIt == StringRef::npos) {
-        regexOS << llvm::Regex::escape(strToProcess);
-        break;
-      }
-      regexOS << llvm::Regex::escape(strToProcess.take_front(regexIt));
-      strToProcess = strToProcess.drop_front(regexIt + 2);
-
-      // Find the end of the regex block.
-      size_t regexEndIt = strToProcess.find("}}");
-      if (regexEndIt == StringRef::npos)
-        return emitError(os, mgr, "found start of regex with no end '}}'");
-      StringRef regexStr = strToProcess.take_front(regexEndIt);
-
-      // Validate that the regex is actually valid.
-      std::string regexError;
-      if (!llvm::Regex(regexStr).isValid(regexError))
-        return emitError(os, mgr, "invalid regex: " + regexError);
-
-      regexOS << '(' << regexStr << ')';
-      strToProcess = strToProcess.drop_front(regexEndIt + 2);
-    }
-    substringRegex = llvm::Regex(regexStr);
-    return success();
-  }
-
-  /// The severity of the diagnosic expected.
-  DiagnosticSeverity kind;
-  /// The line number the expected diagnostic should be on.
-  unsigned lineNo;
-  /// The location of the expected diagnostic within the input file.
-  SMLoc fileLoc;
-  /// A flag indicating if the expected diagnostic has been matched yet.
-  bool matched = false;
-  /// The substring that is expected to be within the diagnostic.
-  StringRef substring;
-  /// An optional regex matcher, if the expected diagnostic sub-string was a
-  /// regex string.
-  std::optional<llvm::Regex> substringRegex;
-};
-
 struct SourceMgrDiagnosticVerifierHandlerImpl {
   SourceMgrDiagnosticVerifierHandlerImpl(
       SourceMgrDiagnosticVerifierHandler::Level level)
-      : status(success()), level(level) {}
+      : level(level) {}
 
-  /// Returns the expected diagnostics for the given source file.
-  std::optional<MutableArrayRef<ExpectedDiag>>
-  getExpectedDiags(StringRef bufName);
-
-  /// Computes the expected diagnostics for the given source buffer.
-  MutableArrayRef<ExpectedDiag>
-  computeExpectedDiags(raw_ostream &os, llvm::SourceMgr &mgr,
-                       const llvm::MemoryBuffer *buf);
-
-  SourceMgrDiagnosticVerifierHandler::Level getVerifyLevel() const {
-    return level;
-  }
-
-  /// The current status of the verifier.
-  LogicalResult status;
-
-  /// A list of expected diagnostics for each buffer of the source manager.
-  llvm::StringMap<SmallVector<ExpectedDiag, 2>> expectedDiagsPerFile;
-
-  /// A list of expected diagnostics with unknown locations.
-  SmallVector<ExpectedDiag, 2> expectedUnknownLocDiags;
-
-  /// Regex to match the expected diagnostics format.
-  llvm::Regex expected =
-      llvm::Regex("expected-(error|note|remark|warning)(-re)? "
-                  "*(@([+-][0-9]+|above|below|unknown))? *{{(.*)}}$");
+  /// The generic SourceMgr-based verifier core that this handler delegates
+  /// scanning and matching of 'expected-*' diagnostics to.
+  llvm::SourceMgrDiagnosticVerifier verifier;
 
   /// Verification level.
-  SourceMgrDiagnosticVerifierHandler::Level level =
-      SourceMgrDiagnosticVerifierHandler::Level::All;
+  SourceMgrDiagnosticVerifierHandler::Level level;
 };
 } // namespace detail
 } // namespace mlir
@@ -773,104 +669,6 @@ static StringRef getDiagKindStr(DiagnosticSeverity kind) {
   llvm_unreachable("Unknown DiagnosticSeverity");
 }
 
-std::optional<MutableArrayRef<ExpectedDiag>>
-SourceMgrDiagnosticVerifierHandlerImpl::getExpectedDiags(StringRef bufName) {
-  auto expectedDiags = expectedDiagsPerFile.find(bufName);
-  if (expectedDiags != expectedDiagsPerFile.end())
-    return MutableArrayRef<ExpectedDiag>(expectedDiags->second);
-  return std::nullopt;
-}
-
-MutableArrayRef<ExpectedDiag>
-SourceMgrDiagnosticVerifierHandlerImpl::computeExpectedDiags(
-    raw_ostream &os, llvm::SourceMgr &mgr, const llvm::MemoryBuffer *buf) {
-  // If the buffer is invalid, return an empty list.
-  if (!buf)
-    return {};
-  auto &expectedDiags = expectedDiagsPerFile[buf->getBufferIdentifier()];
-
-  // The number of the last line that did not correlate to a designator.
-  unsigned lastNonDesignatorLine = 0;
-
-  // The indices of designators that apply to the next non designator line.
-  SmallVector<unsigned, 1> designatorsForNextLine;
-
-  // Scan the file for expected-* designators.
-  SmallVector<StringRef, 100> lines;
-  buf->getBuffer().split(lines, '\n');
-  for (unsigned lineNo = 0, e = lines.size(); lineNo < e; ++lineNo) {
-    SmallVector<StringRef, 4> matches;
-    if (!expected.match(lines[lineNo].rtrim(), &matches)) {
-      // Check for designators that apply to this line.
-      if (!designatorsForNextLine.empty()) {
-        for (unsigned diagIndex : designatorsForNextLine)
-          expectedDiags[diagIndex].lineNo = lineNo + 1;
-        designatorsForNextLine.clear();
-      }
-      lastNonDesignatorLine = lineNo;
-      continue;
-    }
-
-    // Point to the start of expected-*.
-    SMLoc expectedStart = SMLoc::getFromPointer(matches[0].data());
-
-    DiagnosticSeverity kind;
-    if (matches[1] == "error")
-      kind = DiagnosticSeverity::Error;
-    else if (matches[1] == "warning")
-      kind = DiagnosticSeverity::Warning;
-    else if (matches[1] == "remark")
-      kind = DiagnosticSeverity::Remark;
-    else {
-      assert(matches[1] == "note");
-      kind = DiagnosticSeverity::Note;
-    }
-    ExpectedDiag record(kind, lineNo + 1, expectedStart, matches[5]);
-
-    // Check to see if this is a regex match, i.e. it includes the `-re`.
-    if (!matches[2].empty() && failed(record.computeRegex(os, mgr))) {
-      status = failure();
-      continue;
-    }
-
-    StringRef offsetMatch = matches[3];
-    if (!offsetMatch.empty()) {
-      offsetMatch = offsetMatch.drop_front(1);
-
-      // Get the integer value without the @ and +/- prefix.
-      if (offsetMatch[0] == '+' || offsetMatch[0] == '-') {
-        int offset;
-        offsetMatch.drop_front().getAsInteger(0, offset);
-
-        if (offsetMatch.front() == '+')
-          record.lineNo += offset;
-        else
-          record.lineNo -= offset;
-      } else if (offsetMatch.consume_front("unknown")) {
-        // This is matching unknown locations.
-        record.fileLoc = SMLoc();
-        expectedUnknownLocDiags.emplace_back(std::move(record));
-        continue;
-      } else if (offsetMatch.consume_front("above")) {
-        // If the designator applies 'above' we add it to the last non
-        // designator line.
-        record.lineNo = lastNonDesignatorLine + 1;
-      } else {
-        // Otherwise, this is a 'below' designator and applies to the next
-        // non-designator line.
-        assert(offsetMatch.consume_front("below"));
-        designatorsForNextLine.push_back(expectedDiags.size());
-
-        // Set the line number to the last in the case that this designator ends
-        // up dangling.
-        record.lineNo = e;
-      }
-    }
-    expectedDiags.emplace_back(std::move(record));
-  }
-  return expectedDiags;
-}
-
 SourceMgrDiagnosticVerifierHandler::SourceMgrDiagnosticVerifierHandler(
     llvm::SourceMgr &srcMgr, MLIRContext *ctx, raw_ostream &out, Level level)
     : SourceMgrDiagnosticHandler(srcMgr, ctx, out),
@@ -878,7 +676,8 @@ SourceMgrDiagnosticVerifierHandler::SourceMgrDiagnosticVerifierHandler(
   // Compute the expected diagnostics for each of the current files in the
   // source manager.
   for (unsigned i = 0, e = mgr.getNumBuffers(); i != e; ++i)
-    (void)impl->computeExpectedDiags(out, mgr, mgr.getMemoryBuffer(i + 1));
+    (void)impl->verifier.computeExpectedDiags(out, mgr,
+                                              mgr.getMemoryBuffer(i + 1));
 
   // The base class registered a handler that prints every diagnostic. The
   // verifier takes its place: it consumes diagnostics and reports unexpected
@@ -899,21 +698,7 @@ SourceMgrDiagnosticVerifierHandler::~SourceMgrDiagnosticVerifierHandler() {
 /// diagnostics were emitted. This return success if all diagnostics were
 /// verified correctly, failure otherwise.
 LogicalResult SourceMgrDiagnosticVerifierHandler::verify() {
-  // Verify that all expected errors were seen.
-  auto checkExpectedDiags = [&](ExpectedDiag &err) {
-    if (!err.matched)
-      impl->status =
-          err.emitError(os, mgr,
-                        "expected " + getDiagKindStr(err.kind) + " \"" +
-                            err.substring + "\" was not produced");
-  };
-  for (auto &expectedDiagsPair : impl->expectedDiagsPerFile)
-    for (auto &err : expectedDiagsPair.second)
-      checkExpectedDiags(err);
-  for (auto &err : impl->expectedUnknownLocDiags)
-    checkExpectedDiags(err);
-  impl->expectedDiagsPerFile.clear();
-  return impl->status;
+  return success(impl->verifier.verify(os, mgr));
 }
 
 std::unique_ptr<ScopedDiagnosticHandler>
@@ -935,55 +720,17 @@ void SourceMgrDiagnosticVerifierHandler::process(LocationAttr loc,
                                                  StringRef msg,
                                                  DiagnosticSeverity kind) {
   FileLineColLoc fileLoc = loc.findInstanceOf<FileLineColLoc>();
-  MutableArrayRef<ExpectedDiag> diags;
-
-  if (fileLoc) {
-    // Get the expected diagnostics for this file.
-    if (auto maybeDiags = impl->getExpectedDiags(fileLoc.getFilename())) {
-      diags = *maybeDiags;
-    } else {
-      diags = impl->computeExpectedDiags(
-          os, mgr, getBufferForFile(fileLoc.getFilename()));
-    }
-  } else {
-    // Get all expected diagnostics at unknown locations.
-    diags = impl->expectedUnknownLocDiags;
-  }
-
-  // Search for a matching expected diagnostic.
-  // If we find something that is close then emit a more specific error.
-  ExpectedDiag *nearMiss = nullptr;
-
-  // If this was an expected error, remember that we saw it and return.
-  for (auto &e : diags) {
-    // File line must match (unless it's an unknown location).
-    if (fileLoc && fileLoc.getLine() != e.lineNo)
-      continue;
-    if (e.match(msg)) {
-      if (e.kind == kind) {
-        e.matched = true;
-        return;
-      }
-
-      // If this only 
diff ers based on the diagnostic kind, then consider it
-      // to be a near miss.
-      nearMiss = &e;
-    }
-  }
-
-  if (impl->getVerifyLevel() == Level::OnlyExpected)
-    return;
-
-  // Otherwise, emit an error for the near miss.
-  if (nearMiss)
-    mgr.PrintMessage(os, nearMiss->fileLoc, llvm::SourceMgr::DK_Error,
-                     "'" + getDiagKindStr(kind) +
-                         "' diagnostic emitted when expecting a '" +
-                         getDiagKindStr(nearMiss->kind) + "'");
-  else
+  const llvm::MemoryBuffer *buf =
+      fileLoc ? getBufferForFile(fileLoc.getFilename()) : nullptr;
+  bool reportUnexpected = impl->level != Level::OnlyExpected;
+
+  using MatchResult = llvm::SourceMgrDiagnosticVerifier::MatchResult;
+  MatchResult result = impl->verifier.process(
+      os, mgr, getDiagKind(kind), /*hasLoc=*/static_cast<bool>(fileLoc), buf,
+      fileLoc ? fileLoc.getLine() : 0, msg, reportUnexpected);
+  if (result == MatchResult::Unexpected)
     emitDiagnostic(loc, "unexpected " + getDiagKindStr(kind) + ": " + msg,
                    DiagnosticSeverity::Error);
-  impl->status = failure();
 }
 
 //===----------------------------------------------------------------------===//


        


More information about the Mlir-commits mailing list