[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