[clang-tools-extra] [clangd] Replay macro definitions from preamble for clang-tidy checks (PR #202495)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jun 11 01:06:46 PDT 2026
https://github.com/QixiQAQ updated https://github.com/llvm/llvm-project/pull/202495
>From 98fef51122104728d2d7d751525a327f952b6f32 Mon Sep 17 00:00:00 2001
From: tongjinxuan <tongjinxuan at longcheer.com>
Date: Tue, 9 Jun 2026 15:08:54 +0800
Subject: [PATCH] [clangd] Replay macro definitions from preamble for
clang-tidy checks
Clang-tidy checkers observe preprocessor events via PPCallbacks.
When using a preamble, macro definitions in the preamble region of
the main file are not replayed during the main-file build, causing
checkers like bugprone-reserved-identifier to miss them.
This patch extends ReplayPreamble::replay() to also replay MacroDefined
events for macros defined directly in the preamble region of the open
file, similar to how InclusionDirective events are already replayed.
Fixes: https://github.com/clangd/clangd/issues/2501
---
clang-tools-extra/clangd/ParsedAST.cpp | 43 +++++++++++----
.../clangd/unittests/ReplayPeambleTests.cpp | 52 +++++++++++++++++++
2 files changed, 85 insertions(+), 10 deletions(-)
diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp
index e2a49f384a3e9..d52e182176342 100644
--- a/clang-tools-extra/clangd/ParsedAST.cpp
+++ b/clang-tools-extra/clangd/ParsedAST.cpp
@@ -142,16 +142,18 @@ class ReplayPreamble : private PPCallbacks {
// Attach preprocessor hooks such that preamble events will be injected at
// the appropriate time.
// Events will be delivered to the *currently registered* PP callbacks.
- static void attach(std::vector<Inclusion> Includes, CompilerInstance &Clang,
- const PreambleBounds &PB) {
+ static void attach(std::vector<Inclusion> Includes,
+ const MainFileMacros Macros,
+ CompilerInstance &Clang,
+ const PreambleBounds &PB) {
auto &PP = Clang.getPreprocessor();
auto *ExistingCallbacks = PP.getPPCallbacks();
// No need to replay events if nobody is listening.
if (!ExistingCallbacks)
return;
PP.addPPCallbacks(std::unique_ptr<PPCallbacks>(new ReplayPreamble(
- std::move(Includes), ExistingCallbacks, Clang.getSourceManager(), PP,
- Clang.getLangOpts(), PB)));
+ std::move(Includes), std::move(Macros), ExistingCallbacks,
+ Clang.getSourceManager(), PP, Clang.getLangOpts(), PB)));
// We're relying on the fact that addPPCallbacks keeps the old PPCallbacks
// around, creating a chaining wrapper. Guard against other implementations.
assert(PP.getPPCallbacks() != ExistingCallbacks &&
@@ -159,10 +161,12 @@ class ReplayPreamble : private PPCallbacks {
}
private:
- ReplayPreamble(std::vector<Inclusion> Includes, PPCallbacks *Delegate,
- const SourceManager &SM, Preprocessor &PP,
- const LangOptions &LangOpts, const PreambleBounds &PB)
- : Includes(std::move(Includes)), Delegate(Delegate), SM(SM), PP(PP) {
+ReplayPreamble(std::vector<Inclusion> Includes, MainFileMacros Macros,
+ PPCallbacks *Delegate, const SourceManager &SM,
+ Preprocessor &PP, const LangOptions &LangOpts,
+ const PreambleBounds &PB)
+ : Includes(std::move(Includes)), Macros(std::move(Macros)), Delegate(Delegate),
+ SM(SM), PP(PP) {
// Only tokenize the preamble section of the main file, as we are not
// interested in the rest of the tokens.
MainFileTokens = syntax::tokenize(
@@ -193,6 +197,24 @@ class ReplayPreamble : private PPCallbacks {
}
void replay() {
+ // Replay macro definitions from the preamble region of the main file,
+ // so that clang-tidy checks can observe them.
+ for (const auto &[SID, Refs] : Macros.MacroRefs) {
+ for (const auto &Ref : Refs) {
+ if (!Ref.IsDefinition)
+ continue;
+ auto Loc = SM.getComposedLoc(SM.getMainFileID(), Ref.StartOffset);
+ Token Tok;
+ if (Lexer::getRawToken(Loc, Tok, SM, PP.getLangOpts(), false))
+ continue;
+ if (auto *II = PP.getIdentifierInfo(Tok.getRawIdentifier())) {
+ Tok.setIdentifierInfo(II);
+ Tok.setKind(tok::identifier);
+ if (auto *MD = PP.getLocalMacroDirective(II))
+ Delegate->MacroDefined(Tok, MD);
+ }
+ }
+ }
for (const auto &Inc : Includes) {
OptionalFileEntryRef File;
if (Inc.Resolved != "")
@@ -250,6 +272,7 @@ class ReplayPreamble : private PPCallbacks {
}
const std::vector<Inclusion> Includes;
+ const MainFileMacros Macros;
PPCallbacks *Delegate;
const SourceManager &SM;
Preprocessor &PP;
@@ -667,8 +690,8 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs &Inputs,
Includes = Preamble->Includes;
Includes.MainFileIncludes = Patch->preambleIncludes();
// Replay the preamble includes so that clang-tidy checks can see them.
- ReplayPreamble::attach(Patch->preambleIncludes(), *Clang,
- Patch->modifiedBounds());
+ ReplayPreamble::attach(Patch->preambleIncludes(), Patch->mainFileMacros(),
+ *Clang, Patch->modifiedBounds());
PI = *Preamble->Pragmas;
}
// Important: collectIncludeStructure is registered *after* ReplayPreamble!
diff --git a/clang-tools-extra/clangd/unittests/ReplayPeambleTests.cpp b/clang-tools-extra/clangd/unittests/ReplayPeambleTests.cpp
index 3200b6b3cb98d..77ec04abf5125 100644
--- a/clang-tools-extra/clangd/unittests/ReplayPeambleTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ReplayPeambleTests.cpp
@@ -67,6 +67,7 @@ struct Inclusion {
};
static std::vector<Inclusion> Includes;
static std::vector<syntax::Token> SkippedFiles;
+static std::vector<std::string> DefinedMacros;
struct ReplayPreamblePPCallback : public PPCallbacks {
const SourceManager &SM;
explicit ReplayPreamblePPCallback(const SourceManager &SM) : SM(SM) {}
@@ -84,6 +85,11 @@ struct ReplayPreamblePPCallback : public PPCallbacks {
SrcMgr::CharacteristicKind) override {
SkippedFiles.emplace_back(FilenameTok);
}
+
+ void MacroDefined(const Token &MacroNameTok,
+ const MacroDirective *MD) override {
+ DefinedMacros.push_back(MacroNameTok.getIdentifierInfo()->getName().str());
+ }
};
struct ReplayPreambleCheck : public tidy::ClangTidyCheck {
ReplayPreambleCheck(StringRef Name, tidy::ClangTidyContext *Context)
@@ -107,6 +113,52 @@ MATCHER_P(rangeIs, R, "") {
return arg.beginOffset() == R.Begin && arg.endOffset() == R.End;
}
+TEST(ReplayPreambleTest, MacroDefinitions) {
+ DefinedMacros.clear();
+
+ TestTU TU;
+ TU.ClangTidyProvider = addTidyChecks(CheckName);
+ TU.Code = R"cpp(
+ #ifndef _TEST_H
+ #define _TEST_H
+ #define _TEST_MACRO
+ #endif
+ )cpp";
+
+ Config Cfg;
+ Cfg.Diagnostics.ClangTidy.FastCheckFilter = Config::FastCheckPolicy::Loose;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+
+ TU.build();
+
+ EXPECT_THAT(DefinedMacros, testing::Contains(std::string("_TEST_H")));
+ EXPECT_THAT(DefinedMacros, testing::Contains(std::string("_TEST_MACRO")));
+}
+
+TEST(ReplayPreambleTest, MacroDefinitionsPartialPreamble) {
+ DefinedMacros.clear();
+
+ TestTU TU;
+ TU.ClangTidyProvider = addTidyChecks(CheckName);
+ TU.Code = R"cpp(
+ #ifndef _TEST_H
+ #define _TEST_H
+ void unused(void);
+ #define _TEST_MACRO
+ #endif
+ )cpp";
+
+ Config Cfg;
+ Cfg.Diagnostics.ClangTidy.FastCheckFilter = Config::FastCheckPolicy::Loose;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+
+ TU.build();
+
+ // Both macros should be seen by clang-tidy
+ EXPECT_THAT(DefinedMacros, testing::Contains(std::string("_TEST_H")));
+ EXPECT_THAT(DefinedMacros, testing::Contains(std::string("_TEST_MACRO")));
+}
+
TEST(ReplayPreambleTest, IncludesAndSkippedFiles) {
TestTU TU;
// This check records inclusion directives replayed by clangd.
More information about the cfe-commits
mailing list