Skip to content

Conversation

MythreyaK
Copy link
Contributor

@MythreyaK MythreyaK commented Aug 27, 2025

Fixes #155545.

Is probably a bit rough and could use improvements, happy to address any feedback!

@llvmbot llvmbot added the clang Clang issues not falling into any other category label Aug 27, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 27, 2025

@llvm/pr-subscribers-clang

Author: Mythreya Kuricheti (MythreyaK)

Changes

Fixes #155545.


Full diff: https://github.com/llvm/llvm-project/pull/155561.diff

1 Files Affected:

  • (modified) clang/unittests/Sema/HeuristicResolverTest.cpp (+73-2)
diff --git a/clang/unittests/Sema/HeuristicResolverTest.cpp b/clang/unittests/Sema/HeuristicResolverTest.cpp
index 7df25e01e66d4..7264c017dfaf9 100644
--- a/clang/unittests/Sema/HeuristicResolverTest.cpp
+++ b/clang/unittests/Sema/HeuristicResolverTest.cpp
@@ -8,7 +8,12 @@
 #include "clang/Sema/HeuristicResolver.h"
 #include "clang/ASTMatchers/ASTMatchFinder.h"
 #include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/DiagnosticIDs.h"
 #include "clang/Tooling/Tooling.h"
+#include "llvm/ADT/SmallSet.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SmallVector.h"
 #include "gmock/gmock-matchers.h"
 #include "gtest/gtest.h"
 
@@ -31,6 +36,69 @@ template <typename InputNode>
 using ResolveFnT = std::function<std::vector<const NamedDecl *>(
     const HeuristicResolver *, InputNode)>;
 
+struct DiagsConsumer : DiagnosticConsumer {
+  struct PrettyDiagnostic {
+    unsigned int ID;
+    DiagnosticsEngine::Level DiagLevel;
+    llvm::SmallString<64> Message;
+
+    template <typename Stream>
+    friend Stream &operator<<(Stream &OS, const PrettyDiagnostic &D) {
+      if (D.DiagLevel == DiagnosticsEngine::Level::Ignored)
+        OS << "Ignored: ";
+      if (D.DiagLevel == DiagnosticsEngine::Level::Note)
+        OS << "Note: ";
+      if (D.DiagLevel == DiagnosticsEngine::Level::Remark)
+        OS << "Remark: ";
+      if (D.DiagLevel == DiagnosticsEngine::Level::Warning)
+        OS << "Warning: ";
+      if (D.DiagLevel == DiagnosticsEngine::Level::Error)
+        OS << "Error: ";
+      if (D.DiagLevel == DiagnosticsEngine::Level::Fatal)
+        OS << "Fatal: ";
+      OS << "ID: " << D.ID << " Message: " << D.Message.str().str() << '\n';
+      return OS;
+    }
+  };
+
+  llvm::SmallSet<unsigned int, 16> IgnoredDiagnostics{};
+  llvm::SmallVector<PrettyDiagnostic, 16> GeneratedDiagnostics{};
+
+  void HandleDiagnostic(DiagnosticsEngine::Level Level,
+                        const Diagnostic &Info) override {
+    DiagnosticConsumer::HandleDiagnostic(Level, Info);
+    const auto ID = Info.getID();
+
+    if (Level >= DiagnosticsEngine::Level::Warning &&
+        !IgnoredDiagnostics.contains(ID)) {
+      llvm::SmallString<64> Message;
+
+      Info.FormatDiagnostic(Message);
+      GeneratedDiagnostics.emplace_back(PrettyDiagnostic{ID, Level, Message});
+    }
+  }
+};
+
+std::unique_ptr<ASTUnit>
+buildASTUnit(llvm::StringRef Code, std::vector<std::string> Args,
+             llvm::SmallSet<unsigned int, 16> IgnoredDiagnostics) {
+  auto VFS = llvm::vfs::getRealFileSystem();
+  DiagnosticOptions DiagOpts{};
+  DiagsConsumer DiagConsumer{};
+  DiagConsumer.IgnoredDiagnostics = IgnoredDiagnostics;
+
+  auto TU = tooling::buildASTFromCodeWithArgs(
+      Code, Args, "input.cc", "clang-tool",
+      std::make_shared<PCHContainerOperations>(),
+      tooling::getClangStripDependencyFileAdjuster(),
+      tooling::FileContentMappings(), &DiagConsumer, VFS);
+
+  auto Diags =
+      CompilerInstance::createDiagnostics(*VFS, DiagOpts, &DiagConsumer, false);
+  EXPECT_TRUE(DiagConsumer.GeneratedDiagnostics.size() == 0);
+  return TU;
+}
+
 // Test heuristic resolution on `Code` using the resolution procedure
 // `ResolveFn`, which takes a `HeuristicResolver` and an input AST node of type
 // `InputNode` and returns a `std::vector<const NamedDecl *>`.
@@ -41,8 +109,10 @@ template <typename InputNode, typename ParamT, typename InputMatcher,
           typename... OutputMatchers>
 void expectResolution(llvm::StringRef Code, ResolveFnT<ParamT> ResolveFn,
                       const InputMatcher &IM, const OutputMatchers &...OMS) {
-  auto TU = tooling::buildASTFromCodeWithArgs(Code, {"-std=c++20"});
-  auto &Ctx = TU->getASTContext();
+
+  auto AST = buildASTUnit(Code, {"-std=c++20"}, {});
+  auto &Ctx = AST->getASTContext();
+
   auto InputMatches = match(IM, Ctx);
   ASSERT_EQ(1u, InputMatches.size());
   const auto *Input = InputMatches[0].template getNodeAs<InputNode>("input");
@@ -105,6 +175,7 @@ TEST(HeuristicResolver, MemberExpr_Overloads) {
     struct S {
       void bar(int);
       void bar(float);
+      void bar(this S);
     };
 
     template <typename T, typename U>

@MythreyaK MythreyaK force-pushed the mythreyak/heuristicresolver-error-check branch from 7304a9a to e2e72a4 Compare August 27, 2025 06:23

auto Diags =
CompilerInstance::createDiagnostics(*VFS, DiagOpts, &DiagConsumer, false);
EXPECT_TRUE(DiagConsumer.GeneratedDiagnostics.size() == 0);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could probably use a better check, perhaps one that writes out the errors?

Copy link
Collaborator

@HighCommander4 HighCommander4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together!

It's a bit unfortunate to have to do this much plumbing to accomplish a relatively simple goal though.

Poking around a bit, I see that ASTUnit's constructor takes a CaptureDiagsKind parameter which, if not None, makes the diagnostics available on the ASTUnit itself (example access).

However, buildASTFromCodeWithArgs() does not expose this parameter (it passes None unconditionally).

WDYT think about giving buildASTFromCodeWithArgs() an additional (optional) CaptureDiagsKind parameter, plumbing it through to the ASTUnit, and then passing CaptureDiagsKind::All here in the test file?

@MythreyaK
Copy link
Contributor Author

Oh sounds good, I'll give that a shot!

@MythreyaK MythreyaK force-pushed the mythreyak/heuristicresolver-error-check branch from 110b460 to b1a30c8 Compare August 28, 2025 09:42
@MythreyaK
Copy link
Contributor Author

MythreyaK commented Aug 28, 2025

You were right lol, all that was completely unnecessary. I should've dug around a bit more 😆.

Rebased the whole branch with new changes.

(investigating test failures ...)

Comment on lines 62 to +64
void expectResolution(llvm::StringRef Code, ResolveFnT<ParamT> ResolveFn,
const InputMatcher &IM, const OutputMatchers &...OMS) {
llvm::SmallSet<unsigned int, 16> IgnoredDiagnostics{};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't use a trailing default parameter here, so thought of adding an IgnoredDiagnostics to the function.

Suggested change
void expectResolution(llvm::StringRef Code, ResolveFnT<ParamT> ResolveFn,
const InputMatcher &IM, const OutputMatchers &...OMS) {
llvm::SmallSet<unsigned int, 16> IgnoredDiagnostics{};
void expectResolution(llvm::StringRef Code,
llvm::SmallSet<unsigned int, 16> IgnoredDiagnostics,
ResolveFnT<ParamT> ResolveFn, const InputMatcher &IM,
const OutputMatchers &...OMS) {

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would hold off on adding something like IgnoreDiagnostics until we actually have a use case for it

@@ -238,6 +239,7 @@ std::unique_ptr<ASTUnit> buildASTFromCodeWithArgs(
ArgumentsAdjuster Adjuster = getClangStripDependencyFileAdjuster(),
const FileContentMappings &VirtualMappedFiles = FileContentMappings(),
DiagnosticConsumer *DiagConsumer = nullptr,
CaptureDiagsKind CaptureKind = CaptureDiagsKind::None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a public API, and likely one with a fair number of downstream users.

While clang doesn't make any stability guarantees about it's C++ API, I think it's still good practice to avoid API breaks that are unnecessary, since it takes work for downstreams to rebase across them.

So, in a case like this, where we're adding a new optional argument to an API function, I think we should add it to the end.

if (!AST)
return false;

ASTs.push_back(std::move(AST));
return true;
}

CaptureDiagsKind CaptureKinds{CaptureDiagsKind::None};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would make this a private field set via a (possibly optional) constructor parameter

ASTBuilderAction Action(ASTs);
Action.CaptureKinds = CaptureDiagsKind::All;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you mean to be propagating the CaptureKind parameter, rather than harcoding CaptureDiagsKind::All here

Comment on lines 62 to +64
void expectResolution(llvm::StringRef Code, ResolveFnT<ParamT> ResolveFn,
const InputMatcher &IM, const OutputMatchers &...OMS) {
llvm::SmallSet<unsigned int, 16> IgnoredDiagnostics{};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would hold off on adding something like IgnoreDiagnostics until we actually have a use case for it

auto TU = tooling::buildASTFromCodeWithArgs(Code, {"-std=c++23"});

for (auto D = TU->stored_diag_begin(), DEnd = TU->stored_diag_end();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only tangentially related, but since we're touching libTooling code anyways (and since it's not 1998 any more 😆), shall we take the opportunity to add a range wrapper similar to this to ASTUnit?


for (auto D = TU->stored_diag_begin(), DEnd = TU->stored_diag_end();
D != DEnd; ++D) {
EXPECT_TRUE(D->getLevel() < DiagnosticsEngine::Warning ||
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you intentionally disallowing warnings by default?

(I don't particularly feel strongly either way, but in the clangd test infra the check is level < Error, so I was just curious if you're deliberately being stricter.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[HeuristicResolver] Tests should fail if parsed code has error diagnostics
3 participants