[llvm-branch-commits] [llvm] [mlir] [Support][llvm-mc] Add column checking to -verify directives (PR #227570)

Alexander Richardson via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Sep 29 22:50:16 PDT 2026


https://github.com/arichardson created https://github.com/llvm/llvm-project/pull/227570

Extend `SourceMgrDiagnosticVerifier` directives with an optional `:<col>`
suffix after the line specifier (`expected-error:10 {{...}}`,
`expected-error at +1:15 {{...}}`, `expected-error at 42:8 {{...}}`) so tests can
verify diagnostic column locations when desired while keeping column
checking opt-in.

This commit was created with the help of AI tools

>From 950d5d1ec861acfc80561a0c0bfe6fc1c60f29d1 Mon Sep 17 00:00:00 2001
From: Alex Richardson <alexrichardson at google.com>
Date: Tue, 29 Sep 2026 22:49:13 -0700
Subject: [PATCH] [Support][llvm-mc] Add column checking to -verify directives

Extend `SourceMgrDiagnosticVerifier` directives with an optional `:<col>`
suffix after the line specifier (`expected-error:10 {{...}}`,
`expected-error at +1:15 {{...}}`, `expected-error at 42:8 {{...}}`) so tests can
verify diagnostic column locations when desired while keeping column
checking opt-in.

This commit was created with the help of AI tools
---
 llvm/docs/CommandGuide/llvm-mc.md             | 25 +++++++++++-------
 .../Support/SourceMgrDiagnosticVerifier.h     | 22 ++++++++++------
 .../Support/SourceMgrDiagnosticVerifier.cpp   | 26 +++++++++++++++----
 llvm/test/tools/llvm-mc/verify/basic.s        | 20 ++++++++++++--
 llvm/tools/llvm-mc/Verifier.cpp               |  7 ++++-
 mlir/lib/IR/Diagnostics.cpp                   |  3 ++-
 6 files changed, 77 insertions(+), 26 deletions(-)

diff --git a/llvm/docs/CommandGuide/llvm-mc.md b/llvm/docs/CommandGuide/llvm-mc.md
index 7ca33b7d7ed99..ca138760ba707 100644
--- a/llvm/docs/CommandGuide/llvm-mc.md
+++ b/llvm/docs/CommandGuide/llvm-mc.md
@@ -166,24 +166,31 @@ comma-separated list of prefixes to recognize; a bare `--verify` (with no
 `=<prefixes>`) is equivalent to `--verify=expected`.
 
 A directive has the form:
-```asm
-<prefix>-<kind>[-re][@<offset>] {{<text>}}
-```
+
+``asm
+<prefix>-<kind>[-re][@<offset>][:<col>] {{<text>}}
+``
 
 `<kind>` is one of `error`, `warning`, `note` or `remark`. Without
 an `@<offset>`, the directive applies to its own line; `@+N`/`@-N`
 apply to the line `N` lines below/above, and `@above`/`@below` apply to
-the nearest non-directive line above/below. By default `<text>` is matched
-as a substring of the diagnostic's message; with the `-re` suffix, any
-`{{...}}` block nested inside `<text>` is matched as a regular
-expression instead (the rest of `<text>` is matched literally), for
-example:
+the nearest non-directive line above/below. An optional `:<col>` (matching
+the column shown in the tool's own `file:line:col:` diagnostic output)
+additionally requires the diagnostic to be at that exact column; without it,
+only the line is checked. By default `<text>` is matched as a substring of
+the diagnostic's message; with the `-re` suffix, any `{{...}}` block
+nested inside `<text>` is matched as a regular expression instead (the
+rest of `<text>` is matched literally), for example::
 
 ```asm
-## Matches only diagnostics containing this exact text.
+## Matches only diagnostics containing this exact text, on this line.
 .foo
 # expected-error at -1 {{unknown directive}}
 
+## Also requires the diagnostic to be at column 1.
+.foo
+# expected-error at -1:1 {{unknown directive}}
+
 ## Matches any diagnostic starting with "unknown" and ending with
 ## "directive"; only the nested {{.*}} is a regex, the surrounding text
 ## is literal.
diff --git a/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h b/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h
index 2dc5566a48f47..4ff2e96e1090a 100644
--- a/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h
+++ b/llvm/include/llvm/Support/SourceMgrDiagnosticVerifier.h
@@ -58,6 +58,9 @@ class LLVM_ABI SourceMgrDiagnosticVerifier {
     SourceMgr::DiagKind Kind;
     /// The line number the expected diagnostic should be on.
     unsigned LineNo;
+    /// The column the expected diagnostic should be at, or 0 if the column
+    /// isn't checked (only the line is).
+    unsigned ColNo = 0;
     /// The location of the expected diagnostic within the input file.
     SMLoc FileLoc;
     /// A flag indicating if the expected diagnostic has been matched yet.
@@ -114,18 +117,21 @@ class LLVM_ABI SourceMgrDiagnosticVerifier {
   }
 
   /// Matches a single actual diagnostic against the expected diagnostics
-  /// recorded for \p Buf / \p LineNo, computing them first via \p
+  /// recorded for \p Buf / \p LineNo / \p ColNo, 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.
+  /// '@unknown' list instead (\p Buf / \p LineNo / \p ColNo 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. \p ColNo is only checked
+  /// against expected diagnostics that requested a column (via ':<col>');
+  /// others match on line alone regardless of \p ColNo. 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);
+                      unsigned ColNo, 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,
diff --git a/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp b/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp
index f04602a6e7cc2..e640dea0b9723 100644
--- a/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp
+++ b/llvm/lib/Support/SourceMgrDiagnosticVerifier.cpp
@@ -28,7 +28,8 @@ SourceMgrDiagnosticVerifier::SourceMgrDiagnosticVerifier(
   }
   Expected = Regex("(" + PrefixAlt +
                    ")-(error|note|remark|warning)(-re)? "
-                   "*(@([+-][0-9]+|above|below|unknown))? *{{(.*)}}$");
+                   "*(@([+-][0-9]+|above|below|unknown))?(:([0-9]+))? "
+                   "*{{(.*)}}$");
 }
 
 /// Given a diagnostic kind, return a human readable string for it.
@@ -140,7 +141,7 @@ SourceMgrDiagnosticVerifier::computeExpectedDiags(raw_ostream &OS,
                                              : Line.substr(CommentStart);
     }
 
-    SmallVector<StringRef, 5> Matches;
+    SmallVector<StringRef, 9> Matches;
     if (!Expected.match(Line, &Matches)) {
       // Check for designators that apply to this line.
       if (!DesignatorsForNextLine.empty()) {
@@ -166,7 +167,7 @@ SourceMgrDiagnosticVerifier::computeExpectedDiags(raw_ostream &OS,
       assert(Matches[2] == "note");
       Kind = SourceMgr::DK_Note;
     }
-    ExpectedDiag Record(Kind, LineNo + 1, ExpectedStart, Matches[6]);
+    ExpectedDiag Record(Kind, LineNo + 1, ExpectedStart, Matches[8]);
 
     // Check to see if this is a regex match, i.e. it includes the `-re`.
     if (!Matches[3].empty() && !Record.computeRegex(OS, Mgr)) {
@@ -174,6 +175,17 @@ SourceMgrDiagnosticVerifier::computeExpectedDiags(raw_ostream &OS,
       continue;
     }
 
+    // Parse an optional ':<col>' column requirement. Applied before the
+    // offset handling below since some offset forms (e.g. '@unknown')
+    // 'continue' early, and a column requirement should still take effect
+    // for those.
+    if (!Matches[7].empty() &&
+        (Matches[7].getAsInteger(10, Record.ColNo) || Record.ColNo == 0)) {
+      OK = false;
+      Record.emitError(OS, Mgr, "invalid column '" + Matches[6] + "'");
+      continue;
+    }
+
     StringRef OffsetMatch = Matches[4];
     if (!OffsetMatch.empty()) {
       OffsetMatch = OffsetMatch.drop_front(1);
@@ -240,8 +252,8 @@ SourceMgrDiagnosticVerifier::getExpectedDiags(StringRef BufName) {
 
 SourceMgrDiagnosticVerifier::MatchResult SourceMgrDiagnosticVerifier::process(
     raw_ostream &OS, SourceMgr &Mgr, SourceMgr::DiagKind Kind, bool HasLoc,
-    const MemoryBuffer *Buf, unsigned LineNo, StringRef Message,
-    bool ReportUnexpected) {
+    const MemoryBuffer *Buf, unsigned LineNo, unsigned ColNo,
+    StringRef Message, bool ReportUnexpected) {
   MutableArrayRef<ExpectedDiag> Diags;
   if (HasLoc) {
     // If the buffer couldn't be resolved, `Diags` stays empty: a diagnostic
@@ -261,6 +273,10 @@ SourceMgrDiagnosticVerifier::MatchResult SourceMgrDiagnosticVerifier::process(
     // File line must match (unless it's an unknown location).
     if (HasLoc && E.LineNo != LineNo)
       continue;
+    // Column is only checked against expected diagnostics that requested one
+    // (via ':<col>'); others match on line alone.
+    if (HasLoc && E.ColNo != 0 && E.ColNo != ColNo)
+      continue;
     if (E.match(Message)) {
       if (E.Kind == Kind) {
         E.Matched = true;
diff --git a/llvm/test/tools/llvm-mc/verify/basic.s b/llvm/test/tools/llvm-mc/verify/basic.s
index ce4b717c91132..7b72e88b344c2 100644
--- a/llvm/test/tools/llvm-mc/verify/basic.s
+++ b/llvm/test/tools/llvm-mc/verify/basic.s
@@ -4,10 +4,16 @@
 # RUN: rm -rf %t && split-file %s %t
 
 ## Matching expected-error directives: a plain substring match, a '-re'
-## regex match (note the nested {{...}} for the regex portion itself), and
-## an '@+1' forward offset all pass.
+## regex match (note the nested {{...}} for the regex portion itself), an
+## '@+1' forward offset, a bare ':<col>' column check, and a combined
+## '@offset:col' all pass.
 # RUN: llvm-mc -triple riscv32 -verify %t/match.s
 
+## A directive with a column that doesn't match the actual diagnostic's
+## column fails verification, same as a line mismatch would.
+# RUN: not llvm-mc -triple riscv32 -verify %t/wrong-column.s 2>&1 \
+# RUN:   | FileCheck %t/wrong-column.s --check-prefix=WRONG-COLUMN
+
 ## A custom '-verify=<prefix>' prefix is recognized instead of 'expected'.
 # RUN: llvm-mc -triple riscv32 -verify=check %t/match-custom-prefix.s
 
@@ -66,6 +72,16 @@
 # expected-error at +1 {{unknown directive}}
 .baz_directive
 
+.qux_directive # expected-error:1 {{unknown directive}}
+
+.quux_directive
+# expected-error at -1:1 {{unknown directive}}
+
+#--- wrong-column.s
+.foo_directive # expected-error:99 {{unknown directive}}
+# WRONG-COLUMN: error: unknown directive
+# WRONG-COLUMN: expected error "unknown directive" was not produced
+
 #--- match-custom-prefix.s
 .foo_directive
 # check-error at -1 {{unknown directive}}
diff --git a/llvm/tools/llvm-mc/Verifier.cpp b/llvm/tools/llvm-mc/Verifier.cpp
index 19de8d7f785cd..a87455f4f78b9 100644
--- a/llvm/tools/llvm-mc/Verifier.cpp
+++ b/llvm/tools/llvm-mc/Verifier.cpp
@@ -89,9 +89,14 @@ void MCVerifier::process(const SMDiagnostic &Diag) {
       Buf = SrcMgr.getMemoryBuffer(ID);
   }
 
+  // SMDiagnostic::getColumnNo() is 0-based; -verify directives are written
+  // against the printed (1-based) column, matching what a user actually sees
+  // in "file:line:col:" diagnostic output.
   auto Result = Verifier.process(
       errs(), SrcMgr, Diag.getKind(), HasLoc, Buf,
-      HasLoc ? static_cast<unsigned>(Diag.getLineNo()) : 0, Diag.getMessage());
+      HasLoc ? static_cast<unsigned>(Diag.getLineNo()) : 0,
+      HasLoc ? static_cast<unsigned>(Diag.getColumnNo()) + 1 : 0,
+      Diag.getMessage());
   if (Result == SourceMgrDiagnosticVerifier::MatchResult::Unexpected)
     SrcMgr.PrintMessage(errs(), Diag);
 }
diff --git a/mlir/lib/IR/Diagnostics.cpp b/mlir/lib/IR/Diagnostics.cpp
index ae678e1c44dfe..255dc187e897f 100644
--- a/mlir/lib/IR/Diagnostics.cpp
+++ b/mlir/lib/IR/Diagnostics.cpp
@@ -726,7 +726,8 @@ void SourceMgrDiagnosticVerifierHandler::process(LocationAttr loc,
   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);
+      fileLoc ? fileLoc.getLine() : 0, fileLoc ? fileLoc.getColumn() : 0, msg,
+      reportUnexpected);
   if (result == MatchResult::Unexpected)
     emitDiagnostic(loc, "unexpected " + getDiagKindStr(kind) + ": " + msg,
                    DiagnosticSeverity::Error);



More information about the llvm-branch-commits mailing list