-
Notifications
You must be signed in to change notification settings - Fork 14.9k
Add error check for HeuristicResolver #155561
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add error check for HeuristicResolver #155561
Conversation
@llvm/pr-subscribers-clang Author: Mythreya Kuricheti (MythreyaK) ChangesFixes #155545. Full diff: https://github.com/llvm/llvm-project/pull/155561.diff 1 Files Affected:
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>
|
7304a9a
to
e2e72a4
Compare
|
||
auto Diags = | ||
CompilerInstance::createDiagnostics(*VFS, DiagOpts, &DiagConsumer, false); | ||
EXPECT_TRUE(DiagConsumer.GeneratedDiagnostics.size() == 0); |
There was a problem hiding this comment.
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?
There was a problem hiding this 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?
Oh sounds good, I'll give that a shot! |
110b460
to
b1a30c8
Compare
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 ...) |
void expectResolution(llvm::StringRef Code, ResolveFnT<ParamT> ResolveFn, | ||
const InputMatcher &IM, const OutputMatchers &...OMS) { | ||
llvm::SmallSet<unsigned int, 16> IgnoredDiagnostics{}; |
There was a problem hiding this comment.
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.
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) { |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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}; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
void expectResolution(llvm::StringRef Code, ResolveFnT<ParamT> ResolveFn, | ||
const InputMatcher &IM, const OutputMatchers &...OMS) { | ||
llvm::SmallSet<unsigned int, 16> IgnoredDiagnostics{}; |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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 || |
There was a problem hiding this comment.
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.)
Fixes #155545.
Is probably a bit rough and could use improvements, happy to address any feedback!