[Mlir-commits] [mlir] [mlir] Add next streaming manipulator to MLIR diagnostic infrastructure (PR #217804)

lonely eagle llvmlistbot at llvm.org
Wed Sep 23 06:56:21 PDT 2026


https://github.com/linuxlonelyeagle updated https://github.com/llvm/llvm-project/pull/217804

>From a2008f0f221557832cdb770706a5af30ddc47648 Mon Sep 17 00:00:00 2001
From: linuxlonelyeagle <2020382038 at qq.com>
Date: Wed, 9 Sep 2026 10:43:07 +0000
Subject: [PATCH 1/3] add next to InFlightDiagnostic.

---
 mlir/include/mlir/IR/Diagnostics.h        | 35 +++++++++++++++-
 mlir/lib/IR/Diagnostics.cpp               | 51 ++++++++++++++++++++---
 mlir/test/IR/diagnostic-handler-next.mlir |  8 ++++
 mlir/test/lib/IR/CMakeLists.txt           |  1 +
 mlir/test/lib/IR/TestDiagnosticsNext.cpp  | 49 ++++++++++++++++++++++
 mlir/tools/mlir-opt/mlir-opt.cpp          |  2 +
 6 files changed, 138 insertions(+), 8 deletions(-)
 create mode 100644 mlir/test/IR/diagnostic-handler-next.mlir
 create mode 100644 mlir/test/lib/IR/TestDiagnosticsNext.cpp

diff --git a/mlir/include/mlir/IR/Diagnostics.h b/mlir/include/mlir/IR/Diagnostics.h
index 3b8fb46b06a484..8f802c131f3ceb 100644
--- a/mlir/include/mlir/IR/Diagnostics.h
+++ b/mlir/include/mlir/IR/Diagnostics.h
@@ -249,12 +249,18 @@ class Diagnostic {
     return *this;
   }
 
-  /// Outputs this diagnostic to a stream.
-  void print(raw_ostream &os) const;
+  /// Outputs this diagnostic to a stream. `ChunkIdx` specifies which chunk to
+  /// print to `os`. If empty, all arguments are printed to `os`.
+  void print(raw_ostream &os,
+             std::optional<int64_t> chunkIdx = std::nullopt) const;
 
   /// Converts the diagnostic to a string.
   std::string str() const;
 
+  /// Converts the diagnostic to a vector of strings, where each element
+  /// represents a chunk.
+  SmallVector<std::string> strs() const;
+
   /// Attaches a note to this diagnostic. A new location may be optionally
   /// provided, if not, then the location defaults to the one specified for this
   /// diagnostic. Notes may not be attached to other notes.
@@ -288,6 +294,9 @@ class Diagnostic {
   /// Returns the current list of diagnostic metadata.
   SmallVectorImpl<DiagnosticArgument> &getMetadata() { return metadata; }
 
+  /// Finalizes the current group of arguments and starts a new chunk.
+  void checkpointArguments();
+
 private:
   Diagnostic(const Diagnostic &rhs) = delete;
   Diagnostic &operator=(const Diagnostic &rhs) = delete;
@@ -305,6 +314,9 @@ class Diagnostic {
   /// liveness of non-constant strings used in diagnostics.
   std::vector<std::unique_ptr<char[]>> strings;
 
+  /// Boundary indices in `arguments` that separate different chunks.
+  SmallVector<size_t, 2> chunkSizes;
+
   /// A list of attached notes.
   NoteVector notes;
 
@@ -348,6 +360,17 @@ class InFlightDiagnostic {
     return std::move(append(std::forward<Arg>(arg)));
   }
 
+  /// Enables stream-style manipulators to be chained into the diagnostic.
+  InFlightDiagnostic &
+  operator<<(InFlightDiagnostic &(*manip)(InFlightDiagnostic &)) & {
+    return manip(*this);
+  }
+
+  InFlightDiagnostic &&
+  operator<<(InFlightDiagnostic &(*manip)(InFlightDiagnostic &)) && {
+    return std::move(manip(*this));
+  }
+
   /// Append arguments to the diagnostic.
   template <typename... Args>
   InFlightDiagnostic &append(Args &&...args) & {
@@ -408,6 +431,7 @@ class InFlightDiagnostic {
 
   // Allow access to the constructor.
   friend DiagnosticEngine;
+  friend inline InFlightDiagnostic &next(InFlightDiagnostic &diag);
 
   /// The engine that this diagnostic is to report to.
   DiagnosticEngine *owner = nullptr;
@@ -416,6 +440,13 @@ class InFlightDiagnostic {
   std::optional<Diagnostic> impl;
 };
 
+/// A stream manipulator that checkpoints the current messages, and allowing a
+/// single InFlightDiagnostic to output multiple lines.
+inline InFlightDiagnostic &next(InFlightDiagnostic &diag) {
+  diag.impl->checkpointArguments();
+  return diag;
+}
+
 //===----------------------------------------------------------------------===//
 // DiagnosticEngine
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/IR/Diagnostics.cpp b/mlir/lib/IR/Diagnostics.cpp
index 5caf826c84bdd0..a10f581f38522b 100644
--- a/mlir/lib/IR/Diagnostics.cpp
+++ b/mlir/lib/IR/Diagnostics.cpp
@@ -161,9 +161,22 @@ Diagnostic &Diagnostic::operator<<(Value val) {
   return *this << str;
 }
 
-/// Outputs this diagnostic to a stream.
-void Diagnostic::print(raw_ostream &os) const {
-  for (auto &arg : getArguments())
+/// Outputs this diagnostic to a stream. `ChunkIdx` specifies which chunk to
+/// print to `os`. If empty, all arguments are printed to `os`.
+void Diagnostic::print(raw_ostream &os, std::optional<int64_t> chunkIdx) const {
+  if (!chunkIdx.has_value()) {
+    for (auto &arg : getArguments())
+      arg.print(os);
+    return;
+  }
+
+  assert(0 <= chunkIdx && chunkIdx <= chunkSizes.size());
+  size_t argumentStart = *chunkIdx - 1 >= 0 ? chunkSizes[*chunkIdx - 1] : 0;
+  size_t argumentEnd = *chunkIdx == static_cast<int64_t>(chunkSizes.size())
+                           ? arguments.size()
+                           : chunkSizes[*chunkIdx];
+  for (auto &arg :
+       getArguments().slice(argumentStart, argumentEnd - argumentStart))
     arg.print(os);
 }
 
@@ -175,6 +188,19 @@ std::string Diagnostic::str() const {
   return str;
 }
 
+/// Converts the diagnostic to a vector of strings, where each element
+/// represents a chunk.
+SmallVector<std::string> Diagnostic::strs() const {
+  SmallVector<std::string, 2> strs;
+  for (size_t i = 0, e = chunkSizes.size(); i <= e; ++i) {
+    std::string str;
+    llvm::raw_string_ostream os(str);
+    print(os, i);
+    strs.push_back(str);
+  }
+  return strs;
+}
+
 /// Attaches a note to this diagnostic. A new location may be optionally
 /// provided, if not, then the location defaults to the one specified for this
 /// diagnostic. Notes may not be attached to other notes.
@@ -196,6 +222,16 @@ Diagnostic &Diagnostic::attachNote(std::optional<Location> noteLoc) {
 /// Allow a diagnostic to be converted to 'failure'.
 Diagnostic::operator LogicalResult() const { return failure(); }
 
+/// Finalizes the current group of arguments and starts a new chunk.
+void Diagnostic::checkpointArguments() {
+  size_t size = arguments.size();
+  if (size == 0)
+    return;
+  if (!chunkSizes.empty() && chunkSizes.back() == arguments.size())
+    return;
+  chunkSizes.push_back(arguments.size());
+}
+
 //===----------------------------------------------------------------------===//
 // InFlightDiagnostic
 //===----------------------------------------------------------------------===//
@@ -505,11 +541,13 @@ void SourceMgrDiagnosticHandler::emitDiagnostic(Diagnostic &diag) {
 
   // If the location stack is empty, use the initial location.
   if (locationStack.empty()) {
-    emitDiagnostic(diag.getLocation(), diag.str(), diag.getSeverity());
+    for (const std::string &str : diag.strs())
+      emitDiagnostic(diag.getLocation(), str, diag.getSeverity());
 
     // Otherwise, use the location stack.
   } else {
-    emitDiagnostic(locationStack.front().first, diag.str(), diag.getSeverity());
+    for (const std::string &str : diag.strs())
+      emitDiagnostic(locationStack.front().first, str, diag.getSeverity());
     for (auto &it : llvm::drop_begin(locationStack))
       emitDiagnostic(it.first, it.second, DiagnosticSeverity::Note);
   }
@@ -879,7 +917,8 @@ void SourceMgrDiagnosticVerifierHandler::registerInContext(MLIRContext *ctx) {
 
 /// Process a single diagnostic.
 void SourceMgrDiagnosticVerifierHandler::process(Diagnostic &diag) {
-  return process(diag.getLocation(), diag.str(), diag.getSeverity());
+  for (const std::string &str : diag.strs())
+    process(diag.getLocation(), str, diag.getSeverity());
 }
 
 /// Process a diagnostic at a certain location.
diff --git a/mlir/test/IR/diagnostic-handler-next.mlir b/mlir/test/IR/diagnostic-handler-next.mlir
new file mode 100644
index 00000000000000..9f7b6853ef9f2f
--- /dev/null
+++ b/mlir/test/IR/diagnostic-handler-next.mlir
@@ -0,0 +1,8 @@
+// RUN: mlir-opt %s -pass-pipeline="builtin.module(func.func(test-diagnostic-next))" -verify-diagnostics 
+
+// expected-remark @+3 {{test1}}
+// expected-remark @+2 {{test3}}
+// expected-remark @+1 {{test2}}
+func.func @test() {
+  return
+}
diff --git a/mlir/test/lib/IR/CMakeLists.txt b/mlir/test/lib/IR/CMakeLists.txt
index 1abcfc77d2d9b0..8310edec228c15 100644
--- a/mlir/test/lib/IR/CMakeLists.txt
+++ b/mlir/test/lib/IR/CMakeLists.txt
@@ -7,6 +7,7 @@ add_mlir_library(MLIRTestIR
   TestClone.cpp
   TestDiagnostics.cpp
   TestDiagnosticsMetadata.cpp
+  TestDiagnosticsNext.cpp
   TestDominance.cpp
   TestFunc.cpp
   TestInterfaces.cpp
diff --git a/mlir/test/lib/IR/TestDiagnosticsNext.cpp b/mlir/test/lib/IR/TestDiagnosticsNext.cpp
new file mode 100644
index 00000000000000..5cab0864c4f764
--- /dev/null
+++ b/mlir/test/lib/IR/TestDiagnosticsNext.cpp
@@ -0,0 +1,49 @@
+//===- TestDiagnosticsNext.cpp - Test Diagnostic Utilities ----------------===//
+//
+// 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 contains test passes for the next diagnostic manipulator.
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/Diagnostics.h"
+#include "mlir/IR/SymbolTable.h"
+#include "mlir/Pass/Pass.h"
+#include "llvm/ADT/StringRef.h"
+
+using namespace mlir;
+
+namespace {
+struct TestDiagnosticsNextPass
+    : public PassWrapper<TestDiagnosticsNextPass,
+                         InterfacePass<SymbolOpInterface>> {
+  MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(TestDiagnosticsNextPass)
+
+  StringRef getArgument() const final { return "test-diagnostic-next"; }
+  StringRef getDescription() const final {
+    return "Test diagnostic next support.";
+  }
+
+  void runOnOperation() override {
+    getOperation()->walk([](SymbolOpInterface op) {
+      StringRef opName = op.getNameAttr();
+      InFlightDiagnostic diag = op->emitRemark(opName) << "1" << next;
+      diag << opName << "2" << next << opName << "3";
+    });
+  }
+};
+
+} // namespace
+
+namespace mlir {
+namespace test {
+void registerTestDiagnosticsNextPass() {
+  PassRegistration<TestDiagnosticsNextPass>{};
+}
+} // namespace test
+} // namespace mlir
diff --git a/mlir/tools/mlir-opt/mlir-opt.cpp b/mlir/tools/mlir-opt/mlir-opt.cpp
index 47dffb1b2d28bc..730e48914281a0 100644
--- a/mlir/tools/mlir-opt/mlir-opt.cpp
+++ b/mlir/tools/mlir-opt/mlir-opt.cpp
@@ -93,6 +93,7 @@ void registerTestDeadCodeAnalysisPass();
 void registerTestDecomposeCallGraphTypes();
 void registerTestDiagnosticsPass();
 void registerTestDiagnosticsMetadataPass();
+void registerTestDiagnosticsNextPass();
 void registerTestDominancePass();
 void registerTestDynamicPipelinePass();
 void registerTestRemarkPass();
@@ -242,6 +243,7 @@ static void registerTestPasses() {
   mlir::test::registerTestDecomposeCallGraphTypes();
   mlir::test::registerTestDiagnosticsPass();
   mlir::test::registerTestDiagnosticsMetadataPass();
+  mlir::test::registerTestDiagnosticsNextPass();
   mlir::test::registerTestDominancePass();
   mlir::test::registerTestDynamicPipelinePass();
   mlir::test::registerTestRemarkPass();

>From 8f33a026ee8be9ab52735d9f175f7d6399b45f91 Mon Sep 17 00:00:00 2001
From: linuxlonelyeagle <2020382038 at qq.com>
Date: Fri, 11 Sep 2026 03:12:06 +0000
Subject: [PATCH 2/3] fix commemt and make code more easy to understand.

---
 mlir/include/mlir/IR/Diagnostics.h | 29 +++++++++++---------
 mlir/lib/IR/Diagnostics.cpp        | 43 +++++++++++++++++-------------
 2 files changed, 42 insertions(+), 30 deletions(-)

diff --git a/mlir/include/mlir/IR/Diagnostics.h b/mlir/include/mlir/IR/Diagnostics.h
index 8f802c131f3ceb..76ecba5cea481a 100644
--- a/mlir/include/mlir/IR/Diagnostics.h
+++ b/mlir/include/mlir/IR/Diagnostics.h
@@ -153,6 +153,9 @@ inline raw_ostream &operator<<(raw_ostream &os, const DiagnosticArgument &arg) {
 /// This class contains all of the information necessary to report a diagnostic
 /// to the DiagnosticEngine. It should generally not be constructed directly,
 /// and instead used transitively via InFlightDiagnostic.
+///
+/// A diagnostic may contain multiple message parts that share its location,
+/// severity, metadata, and attached notes. Empty message parts are ignored.
 class Diagnostic {
   using NoteVector = std::vector<std::unique_ptr<Diagnostic>>;
 
@@ -249,16 +252,17 @@ class Diagnostic {
     return *this;
   }
 
-  /// Outputs this diagnostic to a stream. `ChunkIdx` specifies which chunk to
-  /// print to `os`. If empty, all arguments are printed to `os`.
+  /// Outputs this diagnostic to a stream. If `messagePartIndex` is provided,
+  /// only that message part is printed; otherwise, all arguments are printed
+  /// without separators between message parts.
   void print(raw_ostream &os,
-             std::optional<int64_t> chunkIdx = std::nullopt) const;
+             std::optional<int64_t> messagePartIndex = std::nullopt) const;
 
   /// Converts the diagnostic to a string.
   std::string str() const;
 
-  /// Converts the diagnostic to a vector of strings, where each element
-  /// represents a chunk.
+  /// Converts each message part to a separate string. Returns a single string
+  /// if the diagnostic has not been divided into multiple parts.
   SmallVector<std::string> strs() const;
 
   /// Attaches a note to this diagnostic. A new location may be optionally
@@ -294,8 +298,9 @@ class Diagnostic {
   /// Returns the current list of diagnostic metadata.
   SmallVectorImpl<DiagnosticArgument> &getMetadata() { return metadata; }
 
-  /// Finalizes the current group of arguments and starts a new chunk.
-  void checkpointArguments();
+  /// Starts a new message part. This has no effect if the current message part
+  /// is empty.
+  void startNewMessagePart();
 
 private:
   Diagnostic(const Diagnostic &rhs) = delete;
@@ -314,8 +319,8 @@ class Diagnostic {
   /// liveness of non-constant strings used in diagnostics.
   std::vector<std::unique_ptr<char[]>> strings;
 
-  /// Boundary indices in `arguments` that separate different chunks.
-  SmallVector<size_t, 2> chunkSizes;
+  /// The exclusive end indices in `arguments` of completed message parts.
+  SmallVector<size_t, 2> messagePartEnds;
 
   /// A list of attached notes.
   NoteVector notes;
@@ -440,10 +445,10 @@ class InFlightDiagnostic {
   std::optional<Diagnostic> impl;
 };
 
-/// A stream manipulator that checkpoints the current messages, and allowing a
-/// single InFlightDiagnostic to output multiple lines.
+/// Starts a new message part in an in-flight diagnostic. Leading, trailing, and
+/// consecutive uses of `next` do not create empty message parts.
 inline InFlightDiagnostic &next(InFlightDiagnostic &diag) {
-  diag.impl->checkpointArguments();
+  diag.impl->startNewMessagePart();
   return diag;
 }
 
diff --git a/mlir/lib/IR/Diagnostics.cpp b/mlir/lib/IR/Diagnostics.cpp
index a10f581f38522b..81e666db485623 100644
--- a/mlir/lib/IR/Diagnostics.cpp
+++ b/mlir/lib/IR/Diagnostics.cpp
@@ -161,20 +161,23 @@ Diagnostic &Diagnostic::operator<<(Value val) {
   return *this << str;
 }
 
-/// Outputs this diagnostic to a stream. `ChunkIdx` specifies which chunk to
-/// print to `os`. If empty, all arguments are printed to `os`.
-void Diagnostic::print(raw_ostream &os, std::optional<int64_t> chunkIdx) const {
-  if (!chunkIdx.has_value()) {
+/// Outputs this diagnostic to a stream.
+void Diagnostic::print(raw_ostream &os,
+                       std::optional<int64_t> messagePartIndex) const {
+  if (!messagePartIndex.has_value()) {
     for (auto &arg : getArguments())
       arg.print(os);
     return;
   }
 
-  assert(0 <= chunkIdx && chunkIdx <= chunkSizes.size());
-  size_t argumentStart = *chunkIdx - 1 >= 0 ? chunkSizes[*chunkIdx - 1] : 0;
-  size_t argumentEnd = *chunkIdx == static_cast<int64_t>(chunkSizes.size())
-                           ? arguments.size()
-                           : chunkSizes[*chunkIdx];
+  assert(0 <= *messagePartIndex &&
+         *messagePartIndex <= static_cast<int64_t>(messagePartEnds.size()));
+  size_t argumentStart =
+      *messagePartIndex == 0 ? 0 : messagePartEnds[*messagePartIndex - 1];
+  size_t argumentEnd =
+      *messagePartIndex == static_cast<int64_t>(messagePartEnds.size())
+          ? arguments.size()
+          : messagePartEnds[*messagePartIndex];
   for (auto &arg :
        getArguments().slice(argumentStart, argumentEnd - argumentStart))
     arg.print(os);
@@ -188,11 +191,16 @@ std::string Diagnostic::str() const {
   return str;
 }
 
-/// Converts the diagnostic to a vector of strings, where each element
-/// represents a chunk.
+/// Converts each message part to a separate string.
 SmallVector<std::string> Diagnostic::strs() const {
   SmallVector<std::string, 2> strs;
-  for (size_t i = 0, e = chunkSizes.size(); i <= e; ++i) {
+  size_t numMessageParts = messagePartEnds.size();
+
+  // Include the current message part if there are no completed parts or if it
+  // contains arguments after the last completed part.
+  if (messagePartEnds.empty() || messagePartEnds.back() != arguments.size())
+    ++numMessageParts;
+  for (size_t i = 0; i < numMessageParts; ++i) {
     std::string str;
     llvm::raw_string_ostream os(str);
     print(os, i);
@@ -222,14 +230,13 @@ Diagnostic &Diagnostic::attachNote(std::optional<Location> noteLoc) {
 /// Allow a diagnostic to be converted to 'failure'.
 Diagnostic::operator LogicalResult() const { return failure(); }
 
-/// Finalizes the current group of arguments and starts a new chunk.
-void Diagnostic::checkpointArguments() {
-  size_t size = arguments.size();
-  if (size == 0)
+/// Starts a new message part.
+void Diagnostic::startNewMessagePart() {
+  if (arguments.empty())
     return;
-  if (!chunkSizes.empty() && chunkSizes.back() == arguments.size())
+  if (!messagePartEnds.empty() && messagePartEnds.back() == arguments.size())
     return;
-  chunkSizes.push_back(arguments.size());
+  messagePartEnds.push_back(arguments.size());
 }
 
 //===----------------------------------------------------------------------===//

>From 382d56f4882070559c0ec1871293ed948be757d8 Mon Sep 17 00:00:00 2001
From: lonely eagle <2020382038 at qq.com>
Date: Wed, 23 Sep 2026 21:56:03 +0800
Subject: [PATCH 3/3] Apply suggestion from @joker-eph

Co-authored-by: Mehdi Amini <joker.eph at gmail.com>
---
 mlir/include/mlir/IR/Diagnostics.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mlir/include/mlir/IR/Diagnostics.h b/mlir/include/mlir/IR/Diagnostics.h
index 76ecba5cea481a..7fba0fed449811 100644
--- a/mlir/include/mlir/IR/Diagnostics.h
+++ b/mlir/include/mlir/IR/Diagnostics.h
@@ -258,7 +258,7 @@ class Diagnostic {
   void print(raw_ostream &os,
              std::optional<int64_t> messagePartIndex = std::nullopt) const;
 
-  /// Converts the diagnostic to a string.
+  /// Converts the diagnostic to a string, concatenating all the parts if there are multiples.
   std::string str() const;
 
   /// Converts each message part to a separate string. Returns a single string



More information about the Mlir-commits mailing list