[llvm] [mlir] [Support] Extract SourceMgrDiagnosticVerifier from MLIR's verifier handler. NFC (PR #227565)

Alexander Richardson via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 29 22:49:48 PDT 2026


https://github.com/arichardson created https://github.com/llvm/llvm-project/pull/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`.

This commit was created with the help of AI tools

>From 87076969aabcd2ea4f09e7a0ffb93b7ec578fe1d Mon Sep 17 00:00:00 2001
From: Alex Richardson <alexrichardson at google.com>
Date: Tue, 29 Sep 2026 22:49:12 -0700
Subject: [PATCH] [Support] Extract SourceMgrDiagnosticVerifier from MLIR's
 verifier handler. NFC

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`.

This commit was created with the help of AI tools
---
 .../Support/SourceMgrDiagnosticVerifier.h     | 142 +++++++++
 llvm/lib/Support/CMakeLists.txt               |   1 +
 .../Support/SourceMgrDiagnosticVerifier.cpp   | 256 ++++++++++++++++
 mlir/lib/IR/Diagnostics.cpp                   | 289 ++----------------
 4 files changed, 417 insertions(+), 271 deletions(-)
 create mode 100644 llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h
 create mode 100644 llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp

diff --git a/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h b/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h
new file mode 100644
index 0000000000000..cbb4643eb2743
--- /dev/null
+++ b/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h
@@ -0,0 +1,142 @@
+//===- 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/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, exactly once each.
+class LLVM_ABI SourceMgrDiagnosticVerifier {
+public:
+  /// 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.
+    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.
+    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.
+    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;
+  };
+
+  /// 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,
+  };
+
+  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 65ec233299495..0e6be542133da 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..5941001b65205
--- /dev/null
+++ b/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp
@@ -0,0 +1,256 @@
+//===- 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 differs 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/mlir/lib/IR/Diagnostics.cpp b/mlir/lib/IR/Diagnostics.cpp
index a3ddb76a66313..ae678e1c44dfe 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 <optional>
 
@@ -637,122 +638,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
@@ -772,104 +668,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),
@@ -877,7 +675,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
@@ -898,21 +697,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>
@@ -934,55 +719,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 differs 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 llvm-commits mailing list