https://github.com/koparasy created 
https://github.com/llvm/llvm-project/pull/199297

Install a scoped MLIR diagnostic handler on the CIRGenerator's MLIRContext that 
translates mlir::Diagnostic severity and location to clang diag IDs and 
SourceLocation, so CIR pass / verifier / lowering errors surface in clang's 
standard format and respect -W/-R flags.

Disable MLIR multithreading on that context (DiagnosticsEngine is not 
thread-safe). Gate the generic CIR-to-CIR fallback errors on hasErrorOccurred() 
to avoid double-printing alongside specific routed diagnostics.

LLVM-side opt/codegen diagnostics are out of scope and will be addressed by 
sharing BackendConsumer in a follow-up.

>From 9e44a5d9e20cd27ca3e98ac72d7a62930d848a78 Mon Sep 17 00:00:00 2001
From: y <[email protected]>
Date: Fri, 22 May 2026 16:09:18 -0700
Subject: [PATCH] [CIR] Route MLIR diagnostics through clang::DiagnosticsEngine

Install a scoped MLIR diagnostic handler on the CIRGenerator's
MLIRContext that translates mlir::Diagnostic severity and location to
clang diag IDs and SourceLocation, so CIR pass / verifier / lowering
errors surface in clang's standard format and respect -W/-R flags.

Disable MLIR multithreading on that context (DiagnosticsEngine is not
thread-safe). Gate the generic CIR-to-CIR fallback errors on
hasErrorOccurred() to avoid double-printing alongside specific routed
diagnostics.

LLVM-side opt/codegen diagnostics are out of scope and
will be addressed by sharing BackendConsumer in a follow-up.
---
 .../clang/Basic/DiagnosticFrontendKinds.td    |   7 +
 clang/include/clang/Basic/DiagnosticGroups.td |   4 +
 clang/lib/CIR/CodeGen/CIRGenerator.cpp        |   5 +
 .../FrontendAction/CIRDiagnosticHandler.cpp   |  94 +++++++
 .../CIR/FrontendAction/CIRDiagnosticHandler.h |  54 ++++
 clang/lib/CIR/FrontendAction/CIRGenAction.cpp |  20 +-
 clang/lib/CIR/FrontendAction/CMakeLists.txt   |   1 +
 .../CIR/Diagnostics/mlir-error-routing.cu     |  20 ++
 .../CIR/CIRDiagnosticHandlerTest.cpp          | 247 ++++++++++++++++++
 clang/unittests/CIR/CMakeLists.txt            |   3 +
 10 files changed, 452 insertions(+), 3 deletions(-)
 create mode 100644 clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.cpp
 create mode 100644 clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.h
 create mode 100644 clang/test/CIR/Diagnostics/mlir-error-routing.cu
 create mode 100644 clang/unittests/CIR/CIRDiagnosticHandlerTest.cpp

diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td 
b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index b585f0d3fa9a9..3b7d9a1d56c1b 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -519,4 +519,11 @@ def err_cir_to_cir_transform_failed : Error<
 def err_cir_verification_failed_pre_passes : Error<
     "CIR module verification error before running CIR-to-CIR passes">,
     DefaultFatal;
+
+// Diagnostics relayed from MLIR-side passes/verifier through the
+// clang::DiagnosticsEngine. The full message text comes from MLIR.
+def err_cir_mlir_diagnostic    : Error<"%0">;
+def warn_cir_mlir_diagnostic   : Warning<"%0">, InGroup<ClangIR>;
+def remark_cir_mlir_diagnostic : Remark<"%0">, InGroup<RemarkClangIR>;
+def note_cir_mlir_diagnostic   : Note<"%0">;
 }
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 8031f99419bdc..92fcda5ec6d6a 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1728,6 +1728,10 @@ def OpenMP : DiagGroup<"openmp", [
 def SourceUsesOpenACC : DiagGroup<"source-uses-openacc">;
 def OpenACC : DiagGroup<"openacc", [SourceUsesOpenACC]>;
 
+// ClangIR (CIR) warnings emitted by MLIR-side passes routed through clang.
+def ClangIR : DiagGroup<"clangir">;
+def RemarkClangIR : DiagGroup<"remark-clangir">;
+
 // Backend warnings.
 def BackendInlineAsm : DiagGroup<"inline-asm">;
 def BackendSourceMgr : DiagGroup<"source-mgr">;
diff --git a/clang/lib/CIR/CodeGen/CIRGenerator.cpp 
b/clang/lib/CIR/CodeGen/CIRGenerator.cpp
index 31d40c21ef6e1..3b4700d2ed38b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenerator.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenerator.cpp
@@ -52,6 +52,11 @@ void CIRGenerator::Initialize(ASTContext &astContext) {
   this->astContext = &astContext;
 
   mlirContext = std::make_unique<mlir::MLIRContext>();
+  // Disable MLIR multithreading: clang::DiagnosticsEngine is not thread-safe,
+  // and the per-context handler installed by CIRGenAction reports diagnostics
+  // straight through it. CIR's pass pipeline is short enough that we don't
+  // miss meaningful parallelism here.
+  mlirContext->disableMultithreading();
   mlirContext->loadDialect<mlir::DLTIDialect>();
   mlirContext->loadDialect<cir::CIRDialect>();
   mlirContext->getOrLoadDialect<mlir::acc::OpenACCDialect>();
diff --git a/clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.cpp 
b/clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.cpp
new file mode 100644
index 0000000000000..7c42f69661788
--- /dev/null
+++ b/clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.cpp
@@ -0,0 +1,94 @@
+//===--- CIRDiagnosticHandler.cpp - Route MLIR diags to clang ---------===//
+//
+// 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 "CIRDiagnosticHandler.h"
+
+#include "mlir/IR/BuiltinAttributes.h"
+#include "mlir/IR/Diagnostics.h"
+#include "mlir/IR/Location.h"
+#include "mlir/IR/MLIRContext.h"
+
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/DiagnosticFrontend.h"
+#include "clang/Basic/FileManager.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Basic/SourceManager.h"
+
+namespace cir {
+
+CIRDiagnosticHandler::CIRDiagnosticHandler(
+    mlir::MLIRContext *ctx, clang::DiagnosticsEngine &diags,
+    clang::SourceManager &srcMgr, clang::FileManager &fileMgr)
+    : mlir::ScopedDiagnosticHandler(ctx), Diags(diags), SrcMgr(srcMgr),
+      FileMgr(fileMgr) {
+  setHandler([this](mlir::Diagnostic &D) { return handle(D); });
+}
+
+clang::SourceLocation
+CIRDiagnosticHandler::translateLoc(mlir::Location loc) {
+  // Walk common location wrappers to reach a usable file/line/column triple.
+  // Anything we can't translate becomes an invalid SourceLocation, which the
+  // diagnostics engine renders without a source line.
+  if (auto file = mlir::dyn_cast<mlir::FileLineColLoc>(loc)) {
+    // SourceManager::translateFileLineCol requires 1-based line/column.
+    // Module-level locations carry (0, 0); fall through unattached.
+    if (file.getLine() == 0 || file.getColumn() == 0)
+      return clang::SourceLocation();
+    auto fileRef = FileMgr.getOptionalFileRef(file.getFilename().getValue());
+    if (!fileRef)
+      return clang::SourceLocation();
+    return SrcMgr.translateFileLineCol(&fileRef->getFileEntry(),
+                                       file.getLine(), file.getColumn());
+  }
+  if (auto fused = mlir::dyn_cast<mlir::FusedLoc>(loc)) {
+    for (mlir::Location child : fused.getLocations()) {
+      clang::SourceLocation translated = translateLoc(child);
+      if (translated.isValid())
+        return translated;
+    }
+    return clang::SourceLocation();
+  }
+  if (auto callsite = mlir::dyn_cast<mlir::CallSiteLoc>(loc))
+    return translateLoc(callsite.getCallee());
+  if (auto named = mlir::dyn_cast<mlir::NameLoc>(loc))
+    return translateLoc(named.getChildLoc());
+  // OpaqueLoc / UnknownLoc and anything else fall through unattached.
+  return clang::SourceLocation();
+}
+
+void CIRDiagnosticHandler::emit(mlir::Diagnostic &diag, bool isNote) {
+  unsigned diagID;
+  if (isNote) {
+    diagID = clang::diag::note_cir_mlir_diagnostic;
+  } else {
+    switch (diag.getSeverity()) {
+    case mlir::DiagnosticSeverity::Error:
+      diagID = clang::diag::err_cir_mlir_diagnostic;
+      break;
+    case mlir::DiagnosticSeverity::Warning:
+      diagID = clang::diag::warn_cir_mlir_diagnostic;
+      break;
+    case mlir::DiagnosticSeverity::Remark:
+      diagID = clang::diag::remark_cir_mlir_diagnostic;
+      break;
+    case mlir::DiagnosticSeverity::Note:
+      diagID = clang::diag::note_cir_mlir_diagnostic;
+      break;
+    }
+  }
+  Diags.Report(translateLoc(diag.getLocation()), diagID) << diag.str();
+}
+
+mlir::LogicalResult CIRDiagnosticHandler::handle(mlir::Diagnostic &diag) {
+  emit(diag, /*isNote=*/false);
+  for (mlir::Diagnostic &note : diag.getNotes())
+    emit(note, /*isNote=*/true);
+  return mlir::success();
+}
+
+} // namespace cir
diff --git a/clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.h 
b/clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.h
new file mode 100644
index 0000000000000..6a2e99125b62d
--- /dev/null
+++ b/clang/lib/CIR/FrontendAction/CIRDiagnosticHandler.h
@@ -0,0 +1,54 @@
+//===--- CIRDiagnosticHandler.h - Route MLIR diags to clang ----*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef CLANG_LIB_CIR_FRONTENDACTION_CIRDIAGNOSTICHANDLER_H
+#define CLANG_LIB_CIR_FRONTENDACTION_CIRDIAGNOSTICHANDLER_H
+
+#include "mlir/IR/Diagnostics.h"
+
+namespace clang {
+class DiagnosticsEngine;
+class FileManager;
+class SourceLocation;
+class SourceManager;
+} // namespace clang
+
+namespace mlir {
+class Location;
+class MLIRContext;
+} // namespace mlir
+
+namespace cir {
+
+/// Routes MLIR-side diagnostics emitted during CIR passes, the verifier, and
+/// CIR-to-LLVM lowering through the surrounding clang::DiagnosticsEngine.
+///
+/// MLIR locations carrying file/line/column info are translated to a
+/// clang::SourceLocation via the active SourceManager + FileManager so errors
+/// surface in the user's preferred format. Locations that cannot be translated
+/// fall back to an invalid clang::SourceLocation.
+class CIRDiagnosticHandler : public mlir::ScopedDiagnosticHandler {
+public:
+  CIRDiagnosticHandler(mlir::MLIRContext *ctx,
+                       clang::DiagnosticsEngine &diags,
+                       clang::SourceManager &srcMgr,
+                       clang::FileManager &fileMgr);
+
+private:
+  mlir::LogicalResult handle(mlir::Diagnostic &diag);
+  void emit(mlir::Diagnostic &diag, bool isNote);
+  clang::SourceLocation translateLoc(mlir::Location loc);
+
+  clang::DiagnosticsEngine &Diags;
+  clang::SourceManager &SrcMgr;
+  clang::FileManager &FileMgr;
+};
+
+} // namespace cir
+
+#endif // CLANG_LIB_CIR_FRONTENDACTION_CIRDIAGNOSTICHANDLER_H
diff --git a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp 
b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
index fccd270a95ccd..d43db7a5e7e27 100644
--- a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
+++ b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
@@ -7,6 +7,7 @@
 
//===----------------------------------------------------------------------===//
 
 #include "clang/CIR/FrontendAction/CIRGenAction.h"
+#include "CIRDiagnosticHandler.h"
 #include "mlir/IR/MLIRContext.h"
 #include "mlir/IR/OwningOpRef.h"
 #include "clang/Basic/DiagnosticFrontend.h"
@@ -82,6 +83,8 @@ class CIRGenConsumer : public clang::ASTConsumer {
   llvm::LLVMContext &LLVMCtx;
   SmallVectorImpl<::clang::LinkModule> &LinkModules;
 
+  std::optional<CIRDiagnosticHandler> MLIRDiagHandler;
+
 public:
   CIRGenConsumer(CIRGenAction::OutputType Action, CompilerInstance &CI,
                  CodeGenOptions &CGO, std::unique_ptr<raw_pwrite_stream> OS,
@@ -98,6 +101,11 @@ class CIRGenConsumer : public clang::ASTConsumer {
     assert(!Context && "initialized multiple times");
     Context = &Ctx;
     Gen->Initialize(Ctx);
+    // Install the MLIR diagnostic handler now that CIRGenerator owns its
+    // MLIRContext. Lifetime is tied to this consumer, which spans CIRGen,
+    // CIR-to-CIR passes, and CIR-to-LLVM lowering.
+    MLIRDiagHandler.emplace(&Gen->getMLIRContext(), CI.getDiagnostics(),
+                            CI.getSourceManager(), CI.getFileManager());
   }
 
   bool HandleTopLevelDecl(DeclGroupRef D) override {
@@ -123,8 +131,11 @@ class CIRGenConsumer : public clang::ASTConsumer {
 
     if (!FEOptions.ClangIRDisableCIRVerifier) {
       if (!Gen->verifyModule()) {
-        CI.getDiagnostics().Report(
-            diag::err_cir_verification_failed_pre_passes);
+        // Verifier output already routed through ClangIRDiagnosticHandler.
+        // Only emit the generic fatal if nothing more specific was reported.
+        if (!CI.getDiagnostics().hasErrorOccurred())
+          CI.getDiagnostics().Report(
+              diag::err_cir_verification_failed_pre_passes);
         llvm::report_fatal_error(
             "CIR codegen: module verification error before running CIR 
passes");
         return;
@@ -140,7 +151,10 @@ class CIRGenConsumer : public clang::ASTConsumer {
               MlirModule, MlirCtx, C, !FEOptions.ClangIRDisableCIRVerifier,
               FEOptions.ClangIREnableIdiomRecognizer, CGO.OptimizationLevel > 
0)
               .failed()) {
-        CI.getDiagnostics().Report(diag::err_cir_to_cir_transform_failed);
+        // Pass-side errors already routed through ClangIRDiagnosticHandler.
+        // Skip the generic catch-all if a specific diagnostic was emitted.
+        if (!CI.getDiagnostics().hasErrorOccurred())
+          CI.getDiagnostics().Report(diag::err_cir_to_cir_transform_failed);
         return;
       }
     }
diff --git a/clang/lib/CIR/FrontendAction/CMakeLists.txt 
b/clang/lib/CIR/FrontendAction/CMakeLists.txt
index b2d2fc1621693..9577a907c9334 100644
--- a/clang/lib/CIR/FrontendAction/CMakeLists.txt
+++ b/clang/lib/CIR/FrontendAction/CMakeLists.txt
@@ -9,6 +9,7 @@ get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS)
 
 add_clang_library(clangCIRFrontendAction
   CIRGenAction.cpp
+  CIRDiagnosticHandler.cpp
 
   DEPENDS
   MLIRCIROpsIncGen
diff --git a/clang/test/CIR/Diagnostics/mlir-error-routing.cu 
b/clang/test/CIR/Diagnostics/mlir-error-routing.cu
new file mode 100644
index 0000000000000..25759ace8b5bd
--- /dev/null
+++ b/clang/test/CIR/Diagnostics/mlir-error-routing.cu
@@ -0,0 +1,20 @@
+// RUN: not %clang_cc1 -triple x86_64-linux-gnu -fclangir -emit-llvm -x cuda \
+// RUN:     -target-sdk-version=12.3 -fcuda-include-gpubinary %t.missing.bin \
+// RUN:     %s -o %t.ll 2>&1 | FileCheck %s
+
+// LoweringPrepare emits an MLIR-side error via mlir::Operation::emitError when
+// the requested CUDA gpubinary cannot be opened. With CIRDiagnosticHandler
+// installed, that diagnostic surfaces through clang's DiagnosticsEngine in
+// clang's standard format (`error: ...`) rather than MLIR's
+// `loc("file":N:M): error: ...` default-handler format.
+
+// CHECK: error: cannot open GPU binary file: {{.*}}.missing.bin
+// CHECK-NOT: loc({{.*}}): error: cannot open GPU binary file
+
+// The generic CIR-to-CIR transform fatal error must NOT be reported on top of
+// the specific MLIR-relayed error. CIRGenAction gates the fallback diag on
+// clang::DiagnosticsEngine::hasErrorOccurred() so users see one root cause,
+// not two.
+// CHECK-NOT: error: CIR-to-CIR transformation failed
+
+__attribute__((global)) void kernel() {}
diff --git a/clang/unittests/CIR/CIRDiagnosticHandlerTest.cpp 
b/clang/unittests/CIR/CIRDiagnosticHandlerTest.cpp
new file mode 100644
index 0000000000000..d5917bc1cdf32
--- /dev/null
+++ b/clang/unittests/CIR/CIRDiagnosticHandlerTest.cpp
@@ -0,0 +1,247 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Unit tests for cir::CIRDiagnosticHandler.
+//
+// The handler routes MLIR-side diagnostics emitted during CIR passes, the
+// verifier, and CIR-to-LLVM lowering through clang::DiagnosticsEngine. These
+// tests exercise severity mapping, location translation, note attachment, and
+// edge cases (missing files, fused/callsite/name locations, 0-line/0-col)
+// without depending on a particular pass-side trigger.
+//
+//===----------------------------------------------------------------------===//
+
+#include "../../lib/CIR/FrontendAction/CIRDiagnosticHandler.h"
+
+#include "mlir/IR/Builders.h"
+#include "mlir/IR/Diagnostics.h"
+#include "mlir/IR/Location.h"
+#include "mlir/IR/MLIRContext.h"
+
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/DiagnosticFrontend.h"
+#include "clang/Basic/DiagnosticIDs.h"
+#include "clang/Basic/DiagnosticOptions.h"
+#include "clang/Basic/FileManager.h"
+#include "clang/Basic/SourceManager.h"
+
+#include "llvm/Support/MemoryBuffer.h"
+#include "gtest/gtest.h"
+
+// Don't pull `using namespace clang;` or `using namespace mlir;` here:
+// `clang::Diagnostic` and `mlir::Diagnostic` would clash.
+
+namespace {
+
+struct CapturedDiag {
+  clang::DiagnosticsEngine::Level Level;
+  unsigned ID;
+  std::string Message;
+  bool HasLoc;
+  unsigned Line = 0;
+  unsigned Column = 0;
+};
+
+class CapturingConsumer : public clang::DiagnosticConsumer {
+public:
+  std::vector<CapturedDiag> Diags;
+
+  void HandleDiagnostic(clang::DiagnosticsEngine::Level Level,
+                        const clang::Diagnostic &Info) override {
+    clang::DiagnosticConsumer::HandleDiagnostic(Level, Info);
+    CapturedDiag CD;
+    CD.Level = Level;
+    CD.ID = Info.getID();
+    llvm::SmallString<128> Buf;
+    Info.FormatDiagnostic(Buf);
+    CD.Message = std::string(Buf);
+    clang::SourceLocation Loc = Info.getLocation();
+    CD.HasLoc = Loc.isValid();
+    if (CD.HasLoc && Info.hasSourceManager()) {
+      clang::PresumedLoc PL = Info.getSourceManager().getPresumedLoc(Loc);
+      if (PL.isValid()) {
+        CD.Line = PL.getLine();
+        CD.Column = PL.getColumn();
+      }
+    }
+    Diags.push_back(std::move(CD));
+  }
+};
+
+class CIRDiagnosticHandlerTest : public ::testing::Test {
+protected:
+  CIRDiagnosticHandlerTest()
+      : Consumer(new CapturingConsumer),
+        FileMgr(FileMgrOpts),
+        Diags(clang::DiagnosticIDs::create(), DiagOpts, Consumer.get(),
+              /*ShouldOwnClient=*/false),
+        SrcMgr(Diags, FileMgr) {
+    // Register a virtual file so FileManager::getOptionalFileRef succeeds and
+    // SourceManager has a buffer for line/col translation.
+    constexpr llvm::StringLiteral Body =
+        "line 1\nline 2\nline 3\nline 4\nline 5\n";
+    auto Buf = llvm::MemoryBuffer::getMemBufferCopy(Body, srcName());
+    clang::FileEntryRef File =
+        FileMgr.getVirtualFileRef(srcName(), Buf->getBufferSize(), 0);
+    SrcMgr.overrideFileContents(File, std::move(Buf));
+    clang::FileID FID = SrcMgr.getOrCreateFileID(File, clang::SrcMgr::C_User);
+    SrcMgr.setMainFileID(FID);
+  }
+
+  static llvm::StringRef srcName() { return "/virtual/file.c"; }
+
+  mlir::Location fileLoc(unsigned Line, unsigned Col,
+                         llvm::StringRef Path = srcName()) {
+    return mlir::FileLineColLoc::get(&MLIRCtx, Path, Line, Col);
+  }
+
+  std::unique_ptr<CapturingConsumer> Consumer;
+  clang::FileSystemOptions FileMgrOpts;
+  clang::FileManager FileMgr;
+  clang::DiagnosticOptions DiagOpts;
+  clang::DiagnosticsEngine Diags;
+  clang::SourceManager SrcMgr;
+  mlir::MLIRContext MLIRCtx{mlir::MLIRContext::Threading::DISABLED};
+};
+
+TEST_F(CIRDiagnosticHandlerTest, ErrorRoutedWithSeverityAndLocation) {
+  cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
+  mlir::emitError(fileLoc(2, 3)) << "boom";
+
+  ASSERT_EQ(Consumer->Diags.size(), 1u);
+  const CapturedDiag &D = Consumer->Diags.front();
+  EXPECT_EQ(D.Level, clang::DiagnosticsEngine::Error);
+  EXPECT_EQ(D.ID,
+            static_cast<unsigned>(clang::diag::err_cir_mlir_diagnostic));
+  EXPECT_EQ(D.Message, "boom");
+  EXPECT_TRUE(D.HasLoc);
+  EXPECT_EQ(D.Line, 2u);
+  EXPECT_EQ(D.Column, 3u);
+}
+
+TEST_F(CIRDiagnosticHandlerTest, WarningRoutesToWarningId) {
+  cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
+  mlir::emitWarning(fileLoc(1, 1)) << "soft";
+
+  ASSERT_EQ(Consumer->Diags.size(), 1u);
+  EXPECT_EQ(Consumer->Diags[0].Level, clang::DiagnosticsEngine::Warning);
+  EXPECT_EQ(Consumer->Diags[0].ID,
+            static_cast<unsigned>(clang::diag::warn_cir_mlir_diagnostic));
+}
+
+TEST_F(CIRDiagnosticHandlerTest, RemarkRoutesToRemarkId) {
+  // Remarks are off by default. Promote -Rclangir so the consumer sees them.
+  bool unknownGroup = Diags.setSeverityForGroup(
+      clang::diag::Flavor::Remark, "remark-clangir",
+      clang::diag::Severity::Remark);
+  ASSERT_FALSE(unknownGroup) << "remark-clangir group not registered";
+  cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
+  mlir::emitRemark(fileLoc(1, 1)) << "fyi";
+
+  ASSERT_EQ(Consumer->Diags.size(), 1u);
+  EXPECT_EQ(Consumer->Diags[0].Level, clang::DiagnosticsEngine::Remark);
+  EXPECT_EQ(Consumer->Diags[0].ID,
+            static_cast<unsigned>(clang::diag::remark_cir_mlir_diagnostic));
+}
+
+TEST_F(CIRDiagnosticHandlerTest, NotesAttachAfterPrimary) {
+  cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
+  {
+    mlir::InFlightDiagnostic d = mlir::emitError(fileLoc(3, 4)) << "primary";
+    d.attachNote(fileLoc(4, 5)) << "extra info";
+  } // InFlightDiagnostic dtor reports.
+
+  ASSERT_EQ(Consumer->Diags.size(), 2u);
+  EXPECT_EQ(Consumer->Diags[0].Level, clang::DiagnosticsEngine::Error);
+  EXPECT_EQ(Consumer->Diags[0].ID,
+            static_cast<unsigned>(clang::diag::err_cir_mlir_diagnostic));
+  EXPECT_EQ(Consumer->Diags[1].Level, clang::DiagnosticsEngine::Note);
+  EXPECT_EQ(Consumer->Diags[1].ID,
+            static_cast<unsigned>(clang::diag::note_cir_mlir_diagnostic));
+  EXPECT_EQ(Consumer->Diags[1].Message, "extra info");
+  EXPECT_EQ(Consumer->Diags[1].Line, 4u);
+  EXPECT_EQ(Consumer->Diags[1].Column, 5u);
+}
+
+TEST_F(CIRDiagnosticHandlerTest, ZeroLineColumnFallsBackToInvalidLoc) {
+  // Module-level locations use line 0 / column 0; SourceManager would assert.
+  // The handler must guard and emit without a source location.
+  cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
+  mlir::emitError(fileLoc(0, 0)) << "module-level";
+
+  ASSERT_EQ(Consumer->Diags.size(), 1u);
+  EXPECT_FALSE(Consumer->Diags[0].HasLoc);
+  EXPECT_EQ(Consumer->Diags[0].Message, "module-level");
+}
+
+TEST_F(CIRDiagnosticHandlerTest, FileNotInManagerFallsBack) {
+  cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
+  mlir::emitError(fileLoc(1, 1, "/not/registered.c")) << "stray";
+
+  ASSERT_EQ(Consumer->Diags.size(), 1u);
+  EXPECT_FALSE(Consumer->Diags[0].HasLoc);
+  EXPECT_EQ(Consumer->Diags[0].Message, "stray");
+}
+
+TEST_F(CIRDiagnosticHandlerTest, FusedLocResolvesToFirstTranslatableChild) {
+  cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
+  mlir::Location unknown = mlir::UnknownLoc::get(&MLIRCtx);
+  mlir::Location good = fileLoc(2, 1);
+  mlir::Location fused = mlir::FusedLoc::get({unknown, good}, {}, &MLIRCtx);
+  mlir::emitError(fused) << "fused";
+
+  ASSERT_EQ(Consumer->Diags.size(), 1u);
+  EXPECT_TRUE(Consumer->Diags[0].HasLoc);
+  EXPECT_EQ(Consumer->Diags[0].Line, 2u);
+}
+
+TEST_F(CIRDiagnosticHandlerTest, CallSiteLocFollowsCallee) {
+  cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
+  mlir::Location callee = fileLoc(3, 2);
+  mlir::Location caller = fileLoc(5, 1);
+  mlir::Location cs = mlir::CallSiteLoc::get(callee, caller);
+  mlir::emitError(cs) << "cs";
+
+  ASSERT_EQ(Consumer->Diags.size(), 1u);
+  EXPECT_EQ(Consumer->Diags[0].Line, 3u);
+  EXPECT_EQ(Consumer->Diags[0].Column, 2u);
+}
+
+TEST_F(CIRDiagnosticHandlerTest, NameLocFollowsChild) {
+  cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
+  mlir::Location child = fileLoc(4, 1);
+  mlir::Location named =
+      mlir::NameLoc::get(mlir::StringAttr::get(&MLIRCtx, "tag"), child);
+  mlir::emitError(named) << "named";
+
+  ASSERT_EQ(Consumer->Diags.size(), 1u);
+  EXPECT_EQ(Consumer->Diags[0].Line, 4u);
+}
+
+TEST_F(CIRDiagnosticHandlerTest, UnknownLocEmitsUnattached) {
+  cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
+  mlir::emitError(mlir::UnknownLoc::get(&MLIRCtx)) << "lost";
+
+  ASSERT_EQ(Consumer->Diags.size(), 1u);
+  EXPECT_FALSE(Consumer->Diags[0].HasLoc);
+}
+
+TEST_F(CIRDiagnosticHandlerTest, ScopedLifetimeUnregistersOnDestruction) {
+  // After the handler scope ends, MLIR's default handler takes over.
+  // Verify the consumer no longer receives diagnostics through our path.
+  {
+    cir::CIRDiagnosticHandler Handler(&MLIRCtx, Diags, SrcMgr, FileMgr);
+    mlir::emitError(fileLoc(1, 1)) << "in-scope";
+  }
+  size_t scopedCount = Consumer->Diags.size();
+  mlir::emitError(fileLoc(1, 1)) << "out-of-scope";
+  // Default handler prints to stderr but does not call our consumer.
+  EXPECT_EQ(Consumer->Diags.size(), scopedCount);
+}
+
+} // namespace
diff --git a/clang/unittests/CIR/CMakeLists.txt 
b/clang/unittests/CIR/CMakeLists.txt
index 81ef87878d33e..6b43389389228 100644
--- a/clang/unittests/CIR/CMakeLists.txt
+++ b/clang/unittests/CIR/CMakeLists.txt
@@ -4,6 +4,7 @@ include_directories(SYSTEM ${MLIR_INCLUDE_DIR})
 include_directories(${MLIR_TABLEGEN_OUTPUT_DIR})
 
 add_distinct_clang_unittest(CIRUnitTests
+  CIRDiagnosticHandlerTest.cpp
   ControlFlowTest.cpp
   PointerLikeTest.cpp
   RecordTypeMetadataTest.cpp
@@ -12,6 +13,8 @@ add_distinct_clang_unittest(CIRUnitTests
   Core
 
   LINK_LIBS
+  clangBasic
+  clangCIRFrontendAction
   MLIRCIR
   CIROpenACCSupport
   MLIRIR

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to