Skip to content

Commit 7306dc9

Browse files
committed
[mlir] Add support for regex within expected-* diagnostics
This can be enabled by using a `-re` suffix when defining the expected line, e.g. `expected-error-re`. This support is similar to what clang provides in its "expected" diagnostic framework(e.g. the `-re` is also the same). The regex definitions themselves are similar to FileCheck in that regex blocks are specified within `{{` `}}` blocks. Differential Revision: https://reviews.llvm.org/D129343
1 parent 78cd95c commit 7306dc9

File tree

4 files changed

+123
-30
lines changed

4 files changed

+123
-30
lines changed

mlir/docs/Diagnostics.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,13 @@ This handler is a wrapper around a llvm::SourceMgr that is used to verify that
300300
certain diagnostics have been emitted to the context. To use this handler,
301301
annotate your source file with expected diagnostics in the form of:
302302

303-
* `expected-(error|note|remark|warning) {{ message }}`
303+
* `expected-(error|note|remark|warning)(-re)? {{ message }}`
304304

305-
A few examples are shown below:
305+
The provided `message` is a string expected to be contained within the generated
306+
diagnostic. The `-re` suffix may be used to enable regex matching within the
307+
`message`. When present, the `message` may define regex match sequences within
308+
`{{` `}}` blocks. The regular expression matcher supports Extended POSIX regular
309+
expressions (ERE). A few examples are shown below:
306310

307311
```mlir
308312
// Expect an error on the same line.
@@ -327,6 +331,12 @@ func.func @baz(%a : f32)
327331
// expected-remark@above {{remark on function above}}
328332
// expected-remark@above {{another remark on function above}}
329333
334+
// Expect an error mentioning the parent function, but use regex to avoid
335+
// hardcoding the name.
336+
func.func @foo() -> i32 {
337+
// expected-error-re@+1 {{'func.return' op has 0 operands, but enclosing function (@{{.*}}) returns 1}}
338+
return
339+
}
330340
```
331341

332342
The handler will report an error if any unexpected diagnostics were seen, or if

mlir/lib/IR/Diagnostics.cpp

Lines changed: 94 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -590,14 +590,77 @@ SMLoc SourceMgrDiagnosticHandler::convertLocToSMLoc(FileLineColLoc loc) {
590590

591591
namespace mlir {
592592
namespace detail {
593-
// Record the expected diagnostic's position, substring and whether it was
594-
// seen.
593+
/// This class represents an expected output diagnostic.
595594
struct ExpectedDiag {
595+
ExpectedDiag(DiagnosticSeverity kind, unsigned lineNo, SMLoc fileLoc,
596+
StringRef substring)
597+
: kind(kind), lineNo(lineNo), fileLoc(fileLoc), substring(substring) {}
598+
599+
/// Emit an error at the location referenced by this diagnostic.
600+
LogicalResult emitError(raw_ostream &os, llvm::SourceMgr &mgr,
601+
const Twine &msg) {
602+
SMRange range(fileLoc, SMLoc::getFromPointer(fileLoc.getPointer() +
603+
substring.size()));
604+
mgr.PrintMessage(os, fileLoc, llvm::SourceMgr::DK_Error, msg, range);
605+
return failure();
606+
}
607+
608+
/// Returns true if this diagnostic matches the given string.
609+
bool match(StringRef str) const {
610+
// If this isn't a regex diagnostic, we simply check if the string was
611+
// contained.
612+
if (substringRegex)
613+
return substringRegex->match(str);
614+
return str.contains(substring);
615+
}
616+
617+
/// Compute the regex matcher for this diagnostic, using the provided stream
618+
/// and manager to emit diagnostics as necessary.
619+
LogicalResult computeRegex(raw_ostream &os, llvm::SourceMgr &mgr) {
620+
std::string regexStr;
621+
llvm::raw_string_ostream regexOS(regexStr);
622+
StringRef strToProcess = substring;
623+
while (!strToProcess.empty()) {
624+
// Find the next regex block.
625+
size_t regexIt = strToProcess.find("{{");
626+
if (regexIt == StringRef::npos) {
627+
regexOS << llvm::Regex::escape(strToProcess);
628+
break;
629+
}
630+
regexOS << llvm::Regex::escape(strToProcess.take_front(regexIt));
631+
strToProcess = strToProcess.drop_front(regexIt + 2);
632+
633+
// Find the end of the regex block.
634+
size_t regexEndIt = strToProcess.find("}}");
635+
if (regexEndIt == StringRef::npos)
636+
return emitError(os, mgr, "found start of regex with no end '}}'");
637+
StringRef regexStr = strToProcess.take_front(regexEndIt);
638+
639+
// Validate that the regex is actually valid.
640+
std::string regexError;
641+
if (!llvm::Regex(regexStr).isValid(regexError))
642+
return emitError(os, mgr, "invalid regex: " + regexError);
643+
644+
regexOS << '(' << regexStr << ')';
645+
strToProcess = strToProcess.drop_front(regexEndIt + 2);
646+
}
647+
substringRegex = llvm::Regex(regexOS.str());
648+
return success();
649+
}
650+
651+
/// The severity of the diagnosic expected.
596652
DiagnosticSeverity kind;
653+
/// The line number the expected diagnostic should be on.
597654
unsigned lineNo;
598-
StringRef substring;
655+
/// The location of the expected diagnostic within the input file.
599656
SMLoc fileLoc;
600-
bool matched;
657+
/// A flag indicating if the expected diagnostic has been matched yet.
658+
bool matched = false;
659+
/// The substring that is expected to be within the diagnostic.
660+
StringRef substring;
661+
/// An optional regex matcher, if the expected diagnostic sub-string was a
662+
/// regex string.
663+
Optional<llvm::Regex> substringRegex;
601664
};
602665

603666
struct SourceMgrDiagnosticVerifierHandlerImpl {
@@ -608,7 +671,8 @@ struct SourceMgrDiagnosticVerifierHandlerImpl {
608671

609672
/// Computes the expected diagnostics for the given source buffer.
610673
MutableArrayRef<ExpectedDiag>
611-
computeExpectedDiags(const llvm::MemoryBuffer *buf);
674+
computeExpectedDiags(raw_ostream &os, llvm::SourceMgr &mgr,
675+
const llvm::MemoryBuffer *buf);
612676

613677
/// The current status of the verifier.
614678
LogicalResult status;
@@ -617,8 +681,9 @@ struct SourceMgrDiagnosticVerifierHandlerImpl {
617681
llvm::StringMap<SmallVector<ExpectedDiag, 2>> expectedDiagsPerFile;
618682

619683
/// Regex to match the expected diagnostics format.
620-
llvm::Regex expected = llvm::Regex("expected-(error|note|remark|warning) "
621-
"*(@([+-][0-9]+|above|below))? *{{(.*)}}");
684+
llvm::Regex expected =
685+
llvm::Regex("expected-(error|note|remark|warning)(-re)? "
686+
"*(@([+-][0-9]+|above|below))? *{{(.*)}}$");
622687
};
623688
} // namespace detail
624689
} // namespace mlir
@@ -638,7 +703,6 @@ static StringRef getDiagKindStr(DiagnosticSeverity kind) {
638703
llvm_unreachable("Unknown DiagnosticSeverity");
639704
}
640705

641-
/// Returns the expected diagnostics for the given source file.
642706
Optional<MutableArrayRef<ExpectedDiag>>
643707
SourceMgrDiagnosticVerifierHandlerImpl::getExpectedDiags(StringRef bufName) {
644708
auto expectedDiags = expectedDiagsPerFile.find(bufName);
@@ -647,10 +711,9 @@ SourceMgrDiagnosticVerifierHandlerImpl::getExpectedDiags(StringRef bufName) {
647711
return llvm::None;
648712
}
649713

650-
/// Computes the expected diagnostics for the given source buffer.
651714
MutableArrayRef<ExpectedDiag>
652715
SourceMgrDiagnosticVerifierHandlerImpl::computeExpectedDiags(
653-
const llvm::MemoryBuffer *buf) {
716+
raw_ostream &os, llvm::SourceMgr &mgr, const llvm::MemoryBuffer *buf) {
654717
// If the buffer is invalid, return an empty list.
655718
if (!buf)
656719
return llvm::None;
@@ -667,7 +730,7 @@ SourceMgrDiagnosticVerifierHandlerImpl::computeExpectedDiags(
667730
buf->getBuffer().split(lines, '\n');
668731
for (unsigned lineNo = 0, e = lines.size(); lineNo < e; ++lineNo) {
669732
SmallVector<StringRef, 4> matches;
670-
if (!expected.match(lines[lineNo], &matches)) {
733+
if (!expected.match(lines[lineNo].rtrim(), &matches)) {
671734
// Check for designators that apply to this line.
672735
if (!designatorsForNextLine.empty()) {
673736
for (unsigned diagIndex : designatorsForNextLine)
@@ -679,7 +742,7 @@ SourceMgrDiagnosticVerifierHandlerImpl::computeExpectedDiags(
679742
}
680743

681744
// Point to the start of expected-*.
682-
auto expectedStart = SMLoc::getFromPointer(matches[0].data());
745+
SMLoc expectedStart = SMLoc::getFromPointer(matches[0].data());
683746

684747
DiagnosticSeverity kind;
685748
if (matches[1] == "error")
@@ -692,9 +755,15 @@ SourceMgrDiagnosticVerifierHandlerImpl::computeExpectedDiags(
692755
assert(matches[1] == "note");
693756
kind = DiagnosticSeverity::Note;
694757
}
758+
ExpectedDiag record(kind, lineNo + 1, expectedStart, matches[5]);
695759

696-
ExpectedDiag record{kind, lineNo + 1, matches[4], expectedStart, false};
697-
auto offsetMatch = matches[2];
760+
// Check to see if this is a regex match, i.e. it includes the `-re`.
761+
if (!matches[2].empty() && failed(record.computeRegex(os, mgr))) {
762+
status = failure();
763+
continue;
764+
}
765+
766+
StringRef offsetMatch = matches[3];
698767
if (!offsetMatch.empty()) {
699768
offsetMatch = offsetMatch.drop_front(1);
700769

@@ -722,7 +791,7 @@ SourceMgrDiagnosticVerifierHandlerImpl::computeExpectedDiags(
722791
record.lineNo = e;
723792
}
724793
}
725-
expectedDiags.push_back(record);
794+
expectedDiags.emplace_back(std::move(record));
726795
}
727796
return expectedDiags;
728797
}
@@ -734,7 +803,7 @@ SourceMgrDiagnosticVerifierHandler::SourceMgrDiagnosticVerifierHandler(
734803
// Compute the expected diagnostics for each of the current files in the
735804
// source manager.
736805
for (unsigned i = 0, e = mgr.getNumBuffers(); i != e; ++i)
737-
(void)impl->computeExpectedDiags(mgr.getMemoryBuffer(i + 1));
806+
(void)impl->computeExpectedDiags(out, mgr, mgr.getMemoryBuffer(i + 1));
738807

739808
// Register a handler to verify the diagnostics.
740809
setHandler([&](Diagnostic &diag) {
@@ -765,14 +834,10 @@ LogicalResult SourceMgrDiagnosticVerifierHandler::verify() {
765834
for (auto &err : expectedDiagsPair.second) {
766835
if (err.matched)
767836
continue;
768-
SMRange range(err.fileLoc,
769-
SMLoc::getFromPointer(err.fileLoc.getPointer() +
770-
err.substring.size()));
771-
mgr.PrintMessage(os, err.fileLoc, llvm::SourceMgr::DK_Error,
772-
"expected " + getDiagKindStr(err.kind) + " \"" +
773-
err.substring + "\" was not produced",
774-
range);
775-
impl->status = failure();
837+
impl->status =
838+
err.emitError(os, mgr,
839+
"expected " + getDiagKindStr(err.kind) + " \"" +
840+
err.substring + "\" was not produced");
776841
}
777842
}
778843
impl->expectedDiagsPerFile.clear();
@@ -799,8 +864,10 @@ void SourceMgrDiagnosticVerifierHandler::process(FileLineColLoc loc,
799864
DiagnosticSeverity kind) {
800865
// Get the expected diagnostics for this file.
801866
auto diags = impl->getExpectedDiags(loc.getFilename());
802-
if (!diags)
803-
diags = impl->computeExpectedDiags(getBufferForFile(loc.getFilename()));
867+
if (!diags) {
868+
diags = impl->computeExpectedDiags(os, mgr,
869+
getBufferForFile(loc.getFilename()));
870+
}
804871

805872
// Search for a matching expected diagnostic.
806873
// If we find something that is close then emit a more specific error.
@@ -809,7 +876,7 @@ void SourceMgrDiagnosticVerifierHandler::process(FileLineColLoc loc,
809876
// If this was an expected error, remember that we saw it and return.
810877
unsigned line = loc.getLine();
811878
for (auto &e : *diags) {
812-
if (line == e.lineNo && msg.contains(e.substring)) {
879+
if (line == e.lineNo && e.match(msg)) {
813880
if (e.kind == kind) {
814881
e.matched = true;
815882
return;

mlir/test/Dialect/SPIRV/IR/memory-ops.mlir

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ func.func @simple_store_missing_operand(%arg0 : f32) -> () {
405405

406406
func.func @simple_store_missing_operand(%arg0 : f32) -> () {
407407
%0 = spv.Variable : !spv.ptr<f32, Function>
408-
// expected-error @+1 {{custom op 'spv.Store' expected 2 operands}} : f32
408+
// expected-error @+1 {{custom op 'spv.Store' expected 2 operands}}
409409
spv.Store "Function" %0 : f32
410410
return
411411
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// RUN: not mlir-opt -allow-unregistered-dialect %s -split-input-file -verify-diagnostics 2>&1 | FileCheck %s
2+
3+
// CHECK: found start of regex with no end '}}'
4+
// expected-error-re {{{{}}
5+
6+
// -----
7+
8+
// CHECK: invalid regex: parentheses not balanced
9+
// expected-error-re {{ {{(}} }}
10+
11+
// -----
12+
13+
func.func @foo() -> i32 {
14+
// expected-error-re@+1 {{'func.return' op has 0 operands, but enclosing function (@{{.*}}) returns 1}}
15+
return
16+
}

0 commit comments

Comments
 (0)