[clang-tools-extra] [llvm] [Clangd] Add AST search capabilities from clang-query (PR #156090)
Fabian Keßler-Schulz via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 4 09:53:14 PDT 2025
https://github.com/Febbe updated https://github.com/llvm/llvm-project/pull/156090
>From 6ed1d5446c11b5d3436d71fa1eb58648633bcb9a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <fabian_kessler at gmx.de>
Date: Wed, 2 Oct 2024 16:33:22 +0200
Subject: [PATCH 1/5] WIP
---
clang-tools-extra/clangd/CMakeLists.txt | 2 +
clang-tools-extra/clangd/ClangdLSPServer.cpp | 14 +++++
clang-tools-extra/clangd/ClangdLSPServer.h | 1 +
clang-tools-extra/clangd/ClangdServer.cpp | 36 +++++++++++
clang-tools-extra/clangd/ClangdServer.h | 5 ++
clang-tools-extra/clangd/Protocol.cpp | 10 ++++
clang-tools-extra/clangd/Protocol.h | 13 ++++
clang-tools-extra/clangd/XRefs.cpp | 63 ++++++++++++++++++++
clang-tools-extra/clangd/XRefs.h | 10 ++++
9 files changed, 154 insertions(+)
diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt
index fb3f05329be21..06e655658645c 100644
--- a/clang-tools-extra/clangd/CMakeLists.txt
+++ b/clang-tools-extra/clangd/CMakeLists.txt
@@ -60,6 +60,7 @@ endif()
include_directories(BEFORE "${CMAKE_CURRENT_BINARY_DIR}/../clang-tidy")
include_directories(BEFORE "${CMAKE_CURRENT_SOURCE_DIR}/../include-cleaner/include")
+include_directories(BEFORE "${CMAKE_CURRENT_SOURCE_DIR}/../clang-query")
add_clang_library(clangDaemon STATIC
AST.cpp
@@ -183,6 +184,7 @@ target_link_libraries(clangDaemon
${LLVM_PTHREAD_LIB}
clangIncludeCleaner
+ clangQuery
clangTidy
clangTidyUtils
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index b445dcf2bbd2e..1330028d160f6 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -76,6 +76,7 @@ std::optional<int64_t> decodeVersion(llvm::StringRef Encoded) {
const llvm::StringLiteral ApplyFixCommand = "clangd.applyFix";
const llvm::StringLiteral ApplyTweakCommand = "clangd.applyTweak";
const llvm::StringLiteral ApplyRenameCommand = "clangd.applyRename";
+constexpr llvm::StringLiteral SearchASTCommand = "clangd.searchAST";
CodeAction toCodeAction(const ClangdServer::CodeActionResult::Rename &R,
const URIForFile &File) {
@@ -852,6 +853,18 @@ void ClangdLSPServer::onCommandApplyRename(const RenameParams &R,
});
}
+void ClangdLSPServer::onCommandSearchAST(const SearchASTArgs &Args,
+ Callback<llvm::json::Value> Reply) {
+ Server->findAST(Args, [Reply = std::move(Reply)](
+ llvm::Expected<std::vector<std::vector<ASTNode>>>
+ BoundNodes) mutable {
+ if (!BoundNodes)
+ return Reply(BoundNodes.takeError());
+ auto v = llvm::json::Value(*BoundNodes);
+ return Reply(*BoundNodes);
+ });
+}
+
void ClangdLSPServer::applyEdit(WorkspaceEdit WE, llvm::json::Value Success,
Callback<llvm::json::Value> Reply) {
ApplyWorkspaceEditParams Edit;
@@ -1728,6 +1741,7 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind,
Bind.command(ApplyFixCommand, this, &ClangdLSPServer::onCommandApplyEdit);
Bind.command(ApplyTweakCommand, this, &ClangdLSPServer::onCommandApplyTweak);
Bind.command(ApplyRenameCommand, this, &ClangdLSPServer::onCommandApplyRename);
+ Bind.command(SearchASTCommand, this, &ClangdLSPServer::onCommandSearchAST);
ApplyWorkspaceEdit = Bind.outgoingMethod("workspace/applyEdit");
PublishDiagnostics = Bind.outgoingNotification("textDocument/publishDiagnostics");
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index 6ada3fd9e6e47..0daf3a4cc5220 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -186,6 +186,7 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
void onCommandApplyEdit(const WorkspaceEdit &, Callback<llvm::json::Value>);
void onCommandApplyTweak(const TweakArgs &, Callback<llvm::json::Value>);
void onCommandApplyRename(const RenameParams &, Callback<llvm::json::Value>);
+ void onCommandSearchAST(const SearchASTArgs &, Callback<llvm::json::Value>);
/// Outgoing LSP calls.
LSPBinder::OutgoingMethod<ApplyWorkspaceEditParams,
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index ac1e9aa5f0ff1..3d5b75f4511e4 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -810,6 +810,42 @@ void ClangdServer::locateSymbolAt(PathRef File, Position Pos,
WorkScheduler->runWithAST("Definitions", File, std::move(Action));
}
+void ClangdServer::findAST(SearchASTArgs const &Args,
+ Callback<std::vector<std::vector<ASTNode>>> CB) {
+ auto Action =
+ [Args, CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {
+ if (!InpAST)
+ return CB(InpAST.takeError());
+ auto BoundNodes = clangd::locateASTQuery(InpAST->AST, Args);
+ if (BoundNodes.empty())
+ return CB(error("No matching AST nodes found"));
+
+ auto &&AST = InpAST->AST;
+ // Convert BoundNodes to a vector of vectors to ASTNode's.
+ std::vector<std::vector<ASTNode>> Result;
+ Result.reserve(BoundNodes.size());
+ for (auto &&BN : BoundNodes) {
+ auto &&Map = BN.getMap();
+ std::vector<ASTNode> Nodes;
+ Nodes.reserve(Map.size());
+ for (const auto &[Key, Value] : Map) {
+ auto Node = dumpAST(Value, AST.getTokens(), AST.getASTContext());
+ Nodes.push_back(std::move(Node));
+ }
+ if (Nodes.empty())
+ continue;
+ Result.push_back(std::move(Nodes));
+ }
+ if (Result.empty()) {
+ return CB(error("No AST nodes found for the query"));
+ }
+ CB(std::move(Result));
+ };
+
+ WorkScheduler->runWithAST("Definitions", Args.textDocument.uri.file(),
+ std::move(Action));
+}
+
void ClangdServer::switchSourceHeader(
PathRef Path, Callback<std::optional<clangd::Path>> CB) {
// We want to return the result as fast as possible, strategy is:
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 4a1eae188f7eb..e2ee463d53b50 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -30,6 +30,8 @@
#include "support/MemoryTree.h"
#include "support/Path.h"
#include "support/ThreadsafeFS.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Tooling/Core/Replacement.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/FunctionExtras.h"
@@ -260,6 +262,9 @@ class ClangdServer {
void locateSymbolAt(PathRef File, Position Pos,
Callback<std::vector<LocatedSymbol>> CB);
+ void findAST(const SearchASTArgs &Args,
+ Callback<std::vector<std::vector<ASTNode>>> CB);
+
/// Switch to a corresponding source file when given a header file, and vice
/// versa.
void switchSourceHeader(PathRef Path,
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 2c858e28fa243..5012dc7edf584 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -13,6 +13,7 @@
#include "Protocol.h"
#include "URI.h"
#include "support/Logger.h"
+#include "clang/AST/ASTTypeTraits.h"
#include "clang/Basic/LLVM.h"
#include "clang/Index/IndexSymbol.h"
#include "llvm/ADT/StringExtras.h"
@@ -1650,6 +1651,15 @@ bool fromJSON(const llvm::json::Value &Params, SelectionRangeParams &S,
O.map("positions", S.positions);
}
+bool fromJSON(const llvm::json::Value &Params, SearchASTArgs &Args, llvm::json::Path P) {
+ llvm::json::ObjectMapper O(Params, P);
+ return O && O.map("query", Args.searchQuery)
+ && O.map("textDocument", Args.textDocument)
+ // && O.map("bindRoot", Args.bindRoot); TODO: add bindRoot to extend this feature
+ // && O.map("traversalKind", Args.tk); TODO: add traversalKind to extend this feature
+ ;
+}
+
llvm::json::Value toJSON(const SelectionRange &Out) {
if (Out.parent) {
return llvm::json::Object{{"range", Out.range},
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index 3a6bf155ee153..1d854f19a5eee 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -26,6 +26,7 @@
#include "URI.h"
#include "index/SymbolID.h"
#include "support/MemoryTree.h"
+#include "clang/AST/ASTTypeTraits.h"
#include "clang/Index/IndexSymbol.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/JSON.h"
@@ -1451,6 +1452,18 @@ struct RenameParams {
bool fromJSON(const llvm::json::Value &, RenameParams &, llvm::json::Path);
llvm::json::Value toJSON(const RenameParams &);
+struct SearchASTArgs {
+ std::string searchQuery;
+ TextDocumentIdentifier textDocument;
+
+ // Todo (extend feature): make them members and modifiable:
+ /// wheter the whole query is shown
+ static auto constexpr BindRoot = true;
+ /// Simplify things for users; default for now.
+ static auto constexpr Tk = TraversalKind::TK_IgnoreUnlessSpelledInSource;
+};
+bool fromJSON(const llvm::json::Value &, SearchASTArgs &, llvm::json::Path);
+
struct PrepareRenameResult {
/// Range of the string to rename.
Range range;
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index a253a630a48cc..fcfa7ce42527e 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -14,6 +14,8 @@
#include "ParsedAST.h"
#include "Protocol.h"
#include "Quality.h"
+#include "Query.h"
+#include "QuerySession.h"
#include "Selection.h"
#include "SourceCode.h"
#include "URI.h"
@@ -41,6 +43,10 @@
#include "clang/AST/StmtCXX.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/AST/Type.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ASTMatchers/Dynamic/Diagnostics.h"
+#include "clang/ASTMatchers/Dynamic/Parser.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceLocation.h"
@@ -52,6 +58,7 @@
#include "clang/Index/IndexingOptions.h"
#include "clang/Index/USRGeneration.h"
#include "clang/Lex/Lexer.h"
+#include "clang/Parse/Parser.h"
#include "clang/Sema/HeuristicResolver.h"
#include "clang/Tooling/Syntax/Tokens.h"
#include "llvm/ADT/ArrayRef.h"
@@ -66,6 +73,7 @@
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
+#include <cmath>
#include <optional>
#include <string>
#include <vector>
@@ -773,6 +781,61 @@ const syntax::Token *findNearbyIdentifier(const SpelledWord &Word,
return BestTok;
}
+auto locateASTQuery(ParsedAST &AST, SearchASTArgs const &Query)
+ -> std::vector<ast_matchers::BoundNodes> {
+ using namespace ast_matchers;
+ using namespace ast_matchers::dynamic;
+ using ast_matchers::dynamic::Parser;
+
+ Diagnostics Diag;
+ auto MatcherSource = llvm::StringRef(Query.searchQuery).ltrim();
+ auto OrigMatcherSource = MatcherSource;
+
+ std::optional<DynTypedMatcher> Matcher = Parser::parseMatcherExpression(
+ MatcherSource,
+ nullptr /* is this sema instance usefull, to reduce overhead?*/,
+ nullptr /*we currently don't support let*/, &Diag);
+ if (!Matcher) {
+ elog("Not a valid top-level matcher.\n");
+ return {/* TODO */};
+ }
+ auto ActualSource = OrigMatcherSource.slice(0, OrigMatcherSource.size() -
+ MatcherSource.size());
+ auto *Q = new query::MatchQuery(ActualSource, *Matcher);
+ Q->RemainingContent = MatcherSource;
+
+ // Q->run(AST);:
+ //==
+
+ struct CollectBoundNodes : MatchFinder::MatchCallback {
+ std::vector<BoundNodes> &Bindings;
+ CollectBoundNodes(std::vector<BoundNodes> &Bindings) : Bindings(Bindings) {}
+ void run(const MatchFinder::MatchResult &Result) override {
+ Bindings.push_back(Result.Nodes);
+ }
+ };
+
+ MatchFinder Finder;
+ std::vector<BoundNodes> Matches;
+ DynTypedMatcher MaybeBoundMatcher = *Matcher;
+ if (Query.BindRoot) {
+ std::optional<DynTypedMatcher> M = Matcher->tryBind("root");
+ if (M)
+ MaybeBoundMatcher = *M;
+ }
+ CollectBoundNodes Collect(Matches);
+ if (!Finder.addDynamicMatcher(MaybeBoundMatcher, &Collect)) {
+ log("Not a valid top-level matcher.\n");
+ return {/* TODO */};
+ }
+
+ ASTContext &Ctx = AST.getASTContext();
+ Ctx.getParentMapContext().setTraversalKind(Query.Tk);
+ Finder.matchAST(Ctx);
+
+ return Matches;
+}
+
std::vector<LocatedSymbol> locateSymbolAt(ParsedAST &AST, Position Pos,
const SymbolIndex *Index) {
const auto &SM = AST.getSourceManager();
diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index 247e52314c3f9..2ece81a8df94a 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -19,6 +19,7 @@
#include "index/SymbolID.h"
#include "support/Path.h"
#include "clang/AST/ASTTypeTraits.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
@@ -32,6 +33,15 @@ class TokenBuffer;
namespace clangd {
class ParsedAST;
+struct LocatedAST {
+ ast_matchers::BoundNodes &AST;
+};
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &, const LocatedAST &);
+
+auto locateASTQuery(ParsedAST &AST, SearchASTArgs const &)
+ -> std::vector<ast_matchers::BoundNodes>;
+
// Describes where a symbol is declared and defined (as far as clangd knows).
// There are three cases:
// - a declaration only, no definition is known (e.g. only header seen)
>From ce1f6de94deea272aba76ec27059b401dca2fccf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <fabian_kessler at gmx.de>
Date: Fri, 29 Aug 2025 20:36:31 +0200
Subject: [PATCH 2/5] WIP: (squash) Improved error handling Cleanup Danger,
this commit will be sqashed, do not rely on it
---
clang-tools-extra/clangd/ClangdLSPServer.cpp | 9 +++---
clang-tools-extra/clangd/ClangdLSPServer.h | 2 +-
clang-tools-extra/clangd/ClangdServer.cpp | 8 +++--
clang-tools-extra/clangd/XRefs.cpp | 34 +++++++++-----------
clang-tools-extra/clangd/XRefs.h | 2 +-
5 files changed, 27 insertions(+), 28 deletions(-)
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 1330028d160f6..2b1a44b7e8fad 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -76,7 +76,7 @@ std::optional<int64_t> decodeVersion(llvm::StringRef Encoded) {
const llvm::StringLiteral ApplyFixCommand = "clangd.applyFix";
const llvm::StringLiteral ApplyTweakCommand = "clangd.applyTweak";
const llvm::StringLiteral ApplyRenameCommand = "clangd.applyRename";
-constexpr llvm::StringLiteral SearchASTCommand = "clangd.searchAST";
+constexpr llvm::StringLiteral SearchASTMethod = "textDocument/searchAST";
CodeAction toCodeAction(const ClangdServer::CodeActionResult::Rename &R,
const URIForFile &File) {
@@ -853,14 +853,13 @@ void ClangdLSPServer::onCommandApplyRename(const RenameParams &R,
});
}
-void ClangdLSPServer::onCommandSearchAST(const SearchASTArgs &Args,
- Callback<llvm::json::Value> Reply) {
+void ClangdLSPServer::onMethodSearchAST(const SearchASTArgs &Args,
+ Callback<llvm::json::Value> Reply) {
Server->findAST(Args, [Reply = std::move(Reply)](
llvm::Expected<std::vector<std::vector<ASTNode>>>
BoundNodes) mutable {
if (!BoundNodes)
return Reply(BoundNodes.takeError());
- auto v = llvm::json::Value(*BoundNodes);
return Reply(*BoundNodes);
});
}
@@ -1741,7 +1740,7 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind,
Bind.command(ApplyFixCommand, this, &ClangdLSPServer::onCommandApplyEdit);
Bind.command(ApplyTweakCommand, this, &ClangdLSPServer::onCommandApplyTweak);
Bind.command(ApplyRenameCommand, this, &ClangdLSPServer::onCommandApplyRename);
- Bind.command(SearchASTCommand, this, &ClangdLSPServer::onCommandSearchAST);
+ Bind.method(SearchASTMethod, this, &ClangdLSPServer::onMethodSearchAST);
ApplyWorkspaceEdit = Bind.outgoingMethod("workspace/applyEdit");
PublishDiagnostics = Bind.outgoingNotification("textDocument/publishDiagnostics");
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index 0daf3a4cc5220..8d7f4ccd67eea 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -186,7 +186,7 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
void onCommandApplyEdit(const WorkspaceEdit &, Callback<llvm::json::Value>);
void onCommandApplyTweak(const TweakArgs &, Callback<llvm::json::Value>);
void onCommandApplyRename(const RenameParams &, Callback<llvm::json::Value>);
- void onCommandSearchAST(const SearchASTArgs &, Callback<llvm::json::Value>);
+ void onMethodSearchAST(const SearchASTArgs &, Callback<llvm::json::Value>);
/// Outgoing LSP calls.
LSPBinder::OutgoingMethod<ApplyWorkspaceEditParams,
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 3d5b75f4511e4..3227b6d8c3fd9 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -817,14 +817,16 @@ void ClangdServer::findAST(SearchASTArgs const &Args,
if (!InpAST)
return CB(InpAST.takeError());
auto BoundNodes = clangd::locateASTQuery(InpAST->AST, Args);
- if (BoundNodes.empty())
+ if (!BoundNodes)
+ return CB(BoundNodes.takeError());
+ if (BoundNodes->empty())
return CB(error("No matching AST nodes found"));
auto &&AST = InpAST->AST;
// Convert BoundNodes to a vector of vectors to ASTNode's.
std::vector<std::vector<ASTNode>> Result;
- Result.reserve(BoundNodes.size());
- for (auto &&BN : BoundNodes) {
+ Result.reserve(BoundNodes->size());
+ for (auto &&BN : *BoundNodes) {
auto &&Map = BN.getMap();
std::vector<ASTNode> Nodes;
Nodes.reserve(Map.size());
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index fcfa7ce42527e..0c0c093398d97 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -73,6 +73,7 @@
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
+#include <algorithm>
#include <cmath>
#include <optional>
#include <string>
@@ -782,57 +783,54 @@ const syntax::Token *findNearbyIdentifier(const SpelledWord &Word,
}
auto locateASTQuery(ParsedAST &AST, SearchASTArgs const &Query)
- -> std::vector<ast_matchers::BoundNodes> {
+ -> llvm::Expected<std::vector<ast_matchers::BoundNodes>> {
using namespace ast_matchers;
using namespace ast_matchers::dynamic;
using ast_matchers::dynamic::Parser;
Diagnostics Diag;
auto MatcherSource = llvm::StringRef(Query.searchQuery).ltrim();
- auto OrigMatcherSource = MatcherSource;
std::optional<DynTypedMatcher> Matcher = Parser::parseMatcherExpression(
MatcherSource,
nullptr /* is this sema instance usefull, to reduce overhead?*/,
nullptr /*we currently don't support let*/, &Diag);
if (!Matcher) {
- elog("Not a valid top-level matcher.\n");
- return {/* TODO */};
+ return error("Not a valid top-level matcher: {}.", Diag.toString());
}
- auto ActualSource = OrigMatcherSource.slice(0, OrigMatcherSource.size() -
- MatcherSource.size());
- auto *Q = new query::MatchQuery(ActualSource, *Matcher);
- Q->RemainingContent = MatcherSource;
- // Q->run(AST);:
- //==
struct CollectBoundNodes : MatchFinder::MatchCallback {
- std::vector<BoundNodes> &Bindings;
- CollectBoundNodes(std::vector<BoundNodes> &Bindings) : Bindings(Bindings) {}
+ std::vector<BoundNodes> *Bindings;
+ CollectBoundNodes(std::vector<BoundNodes> &Bindings)
+ : Bindings(&Bindings) {}
void run(const MatchFinder::MatchResult &Result) override {
- Bindings.push_back(Result.Nodes);
+ Bindings->push_back(Result.Nodes);
}
};
- MatchFinder Finder;
- std::vector<BoundNodes> Matches;
DynTypedMatcher MaybeBoundMatcher = *Matcher;
if (Query.BindRoot) {
std::optional<DynTypedMatcher> M = Matcher->tryBind("root");
if (M)
MaybeBoundMatcher = *M;
}
+ std::vector<BoundNodes> Matches;
CollectBoundNodes Collect(Matches);
+
+ MatchFinder::MatchFinderOptions Opt;
+ Opt.IgnoreSystemHeaders = true;
+ MatchFinder Finder{Opt};
if (!Finder.addDynamicMatcher(MaybeBoundMatcher, &Collect)) {
- log("Not a valid top-level matcher.\n");
- return {/* TODO */};
+ return error("Can't add matcher.");
}
ASTContext &Ctx = AST.getASTContext();
+
+ auto OldTK = Ctx.getParentMapContext().getTraversalKind();
Ctx.getParentMapContext().setTraversalKind(Query.Tk);
Finder.matchAST(Ctx);
-
+ Ctx.getParentMapContext().setTraversalKind(OldTK);
return Matches;
}
diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index 2ece81a8df94a..d6c0e3b99941b 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -40,7 +40,7 @@ struct LocatedAST {
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const LocatedAST &);
auto locateASTQuery(ParsedAST &AST, SearchASTArgs const &)
- -> std::vector<ast_matchers::BoundNodes>;
+ -> llvm::Expected<std::vector<ast_matchers::BoundNodes>>;
// Describes where a symbol is declared and defined (as far as clangd knows).
// There are three cases:
>From 3c84aeee77aa1a996506507577b8cc95c653a038 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <fabian_kessler at gmx.de>
Date: Fri, 29 Aug 2025 21:31:13 +0200
Subject: [PATCH 3/5] Return the bound node names
---
clang-tools-extra/clangd/ClangdLSPServer.cpp | 2 +-
clang-tools-extra/clangd/ClangdServer.cpp | 14 ++++++--------
clang-tools-extra/clangd/ClangdServer.h | 3 ++-
clang-tools-extra/clangd/Protocol.h | 1 +
llvm/include/llvm/Support/JSON.h | 4 ++++
5 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 2b1a44b7e8fad..5cdf615edd8cd 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -856,7 +856,7 @@ void ClangdLSPServer::onCommandApplyRename(const RenameParams &R,
void ClangdLSPServer::onMethodSearchAST(const SearchASTArgs &Args,
Callback<llvm::json::Value> Reply) {
Server->findAST(Args, [Reply = std::move(Reply)](
- llvm::Expected<std::vector<std::vector<ASTNode>>>
+ llvm::Expected<BoundASTNodes>
BoundNodes) mutable {
if (!BoundNodes)
return Reply(BoundNodes.takeError());
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 3227b6d8c3fd9..03f220af16610 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -811,7 +811,7 @@ void ClangdServer::locateSymbolAt(PathRef File, Position Pos,
}
void ClangdServer::findAST(SearchASTArgs const &Args,
- Callback<std::vector<std::vector<ASTNode>>> CB) {
+ Callback<BoundASTNodes> CB) {
auto Action =
[Args, CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
@@ -824,19 +824,17 @@ void ClangdServer::findAST(SearchASTArgs const &Args,
auto &&AST = InpAST->AST;
// Convert BoundNodes to a vector of vectors to ASTNode's.
- std::vector<std::vector<ASTNode>> Result;
+ BoundASTNodes Result;
Result.reserve(BoundNodes->size());
for (auto &&BN : *BoundNodes) {
auto &&Map = BN.getMap();
- std::vector<ASTNode> Nodes;
- Nodes.reserve(Map.size());
+ BoundASTNodes::value_type BAN;
for (const auto &[Key, Value] : Map) {
- auto Node = dumpAST(Value, AST.getTokens(), AST.getASTContext());
- Nodes.push_back(std::move(Node));
+ BAN.emplace(Key, dumpAST(Value, AST.getTokens(), AST.getASTContext()));
}
- if (Nodes.empty())
+ if (BAN.empty())
continue;
- Result.push_back(std::move(Nodes));
+ Result.push_back(std::move(BAN));
}
if (Result.empty()) {
return CB(error("No AST nodes found for the query"));
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index e2ee463d53b50..7a299f89c3cd6 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -37,6 +37,7 @@
#include "llvm/ADT/FunctionExtras.h"
#include "llvm/ADT/StringRef.h"
#include <functional>
+#include <map>
#include <memory>
#include <optional>
#include <string>
@@ -263,7 +264,7 @@ class ClangdServer {
Callback<std::vector<LocatedSymbol>> CB);
void findAST(const SearchASTArgs &Args,
- Callback<std::vector<std::vector<ASTNode>>> CB);
+ Callback<BoundASTNodes> CB);
/// Switch to a corresponding source file when given a header file, and vice
/// versa.
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index 1d854f19a5eee..1a1864cc1e90a 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1463,6 +1463,7 @@ struct SearchASTArgs {
static auto constexpr Tk = TraversalKind::TK_IgnoreUnlessSpelledInSource;
};
bool fromJSON(const llvm::json::Value &, SearchASTArgs &, llvm::json::Path);
+using BoundASTNodes = std::vector<std::map<std::string, struct ASTNode>>;
struct PrepareRenameResult {
/// Range of the string to rename.
diff --git a/llvm/include/llvm/Support/JSON.h b/llvm/include/llvm/Support/JSON.h
index 74858ec559932..05338f7966e0d 100644
--- a/llvm/include/llvm/Support/JSON.h
+++ b/llvm/include/llvm/Support/JSON.h
@@ -111,6 +111,10 @@ class Object {
// (using std::pair forces extra copies).
struct KV;
explicit Object(std::initializer_list<KV> Properties);
+ template <typename Collection> explicit Object(Collection &&C) {
+ for (auto &&P : C)
+ M.insert(P);
+ }
iterator begin() { return M.begin(); }
const_iterator begin() const { return M.begin(); }
>From 288bf22a0e8ca9cd5a0772dea1bec45443e41f6e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <fabian_kessler at gmx.de>
Date: Sat, 30 Aug 2025 20:00:05 +0200
Subject: [PATCH 4/5] format
---
clang-tools-extra/clangd/ClangdLSPServer.cpp | 3 +-
clang-tools-extra/clangd/ClangdServer.cpp | 58 ++++++++++----------
clang-tools-extra/clangd/ClangdServer.h | 3 +-
clang-tools-extra/clangd/Protocol.cpp | 15 +++--
clang-tools-extra/clangd/XRefs.cpp | 1 -
5 files changed, 40 insertions(+), 40 deletions(-)
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 5cdf615edd8cd..e064b03fea8ae 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -856,8 +856,7 @@ void ClangdLSPServer::onCommandApplyRename(const RenameParams &R,
void ClangdLSPServer::onMethodSearchAST(const SearchASTArgs &Args,
Callback<llvm::json::Value> Reply) {
Server->findAST(Args, [Reply = std::move(Reply)](
- llvm::Expected<BoundASTNodes>
- BoundNodes) mutable {
+ llvm::Expected<BoundASTNodes> BoundNodes) mutable {
if (!BoundNodes)
return Reply(BoundNodes.takeError());
return Reply(*BoundNodes);
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 03f220af16610..e2473dd7d3084 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -812,35 +812,35 @@ void ClangdServer::locateSymbolAt(PathRef File, Position Pos,
void ClangdServer::findAST(SearchASTArgs const &Args,
Callback<BoundASTNodes> CB) {
- auto Action =
- [Args, CB = std::move(CB)](llvm::Expected<InputsAndAST> InpAST) mutable {
- if (!InpAST)
- return CB(InpAST.takeError());
- auto BoundNodes = clangd::locateASTQuery(InpAST->AST, Args);
- if (!BoundNodes)
- return CB(BoundNodes.takeError());
- if (BoundNodes->empty())
- return CB(error("No matching AST nodes found"));
-
- auto &&AST = InpAST->AST;
- // Convert BoundNodes to a vector of vectors to ASTNode's.
- BoundASTNodes Result;
- Result.reserve(BoundNodes->size());
- for (auto &&BN : *BoundNodes) {
- auto &&Map = BN.getMap();
- BoundASTNodes::value_type BAN;
- for (const auto &[Key, Value] : Map) {
- BAN.emplace(Key, dumpAST(Value, AST.getTokens(), AST.getASTContext()));
- }
- if (BAN.empty())
- continue;
- Result.push_back(std::move(BAN));
- }
- if (Result.empty()) {
- return CB(error("No AST nodes found for the query"));
- }
- CB(std::move(Result));
- };
+ auto Action = [Args, CB = std::move(CB)](
+ llvm::Expected<InputsAndAST> InpAST) mutable {
+ if (!InpAST)
+ return CB(InpAST.takeError());
+ auto BoundNodes = clangd::locateASTQuery(InpAST->AST, Args);
+ if (!BoundNodes)
+ return CB(BoundNodes.takeError());
+ if (BoundNodes->empty())
+ return CB(error("No matching AST nodes found"));
+
+ auto &&AST = InpAST->AST;
+ // Convert BoundNodes to a vector of vectors to ASTNode's.
+ BoundASTNodes Result;
+ Result.reserve(BoundNodes->size());
+ for (auto &&BN : *BoundNodes) {
+ auto &&Map = BN.getMap();
+ BoundASTNodes::value_type BAN;
+ for (const auto &[Key, Value] : Map) {
+ BAN.emplace(Key, dumpAST(Value, AST.getTokens(), AST.getASTContext()));
+ }
+ if (BAN.empty())
+ continue;
+ Result.push_back(std::move(BAN));
+ }
+ if (Result.empty()) {
+ return CB(error("No AST nodes found for the query"));
+ }
+ CB(std::move(Result));
+ };
WorkScheduler->runWithAST("Definitions", Args.textDocument.uri.file(),
std::move(Action));
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 7a299f89c3cd6..0fd3f15b93674 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -263,8 +263,7 @@ class ClangdServer {
void locateSymbolAt(PathRef File, Position Pos,
Callback<std::vector<LocatedSymbol>> CB);
- void findAST(const SearchASTArgs &Args,
- Callback<BoundASTNodes> CB);
+ void findAST(const SearchASTArgs &Args, Callback<BoundASTNodes> CB);
/// Switch to a corresponding source file when given a header file, and vice
/// versa.
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 5012dc7edf584..ff946298b0c2a 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -1651,13 +1651,16 @@ bool fromJSON(const llvm::json::Value &Params, SelectionRangeParams &S,
O.map("positions", S.positions);
}
-bool fromJSON(const llvm::json::Value &Params, SearchASTArgs &Args, llvm::json::Path P) {
+bool fromJSON(const llvm::json::Value &Params, SearchASTArgs &Args,
+ llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
- return O && O.map("query", Args.searchQuery)
- && O.map("textDocument", Args.textDocument)
- // && O.map("bindRoot", Args.bindRoot); TODO: add bindRoot to extend this feature
- // && O.map("traversalKind", Args.tk); TODO: add traversalKind to extend this feature
- ;
+ return O && O.map("query", Args.searchQuery) &&
+ O.map("textDocument", Args.textDocument)
+ // && O.map("bindRoot", Args.bindRoot); TODO: add bindRoot to extend this
+ // feature
+ // && O.map("traversalKind", Args.tk); TODO: add traversalKind to extend
+ // this feature
+ ;
}
llvm::json::Value toJSON(const SelectionRange &Out) {
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 0c0c093398d97..ea9da2adf8287 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -799,7 +799,6 @@ auto locateASTQuery(ParsedAST &AST, SearchASTArgs const &Query)
return error("Not a valid top-level matcher: {}.", Diag.toString());
}
-
struct CollectBoundNodes : MatchFinder::MatchCallback {
std::vector<BoundNodes> *Bindings;
CollectBoundNodes(std::vector<BoundNodes> &Bindings)
>From de0fa1048ef37b03103dbe89b8b751e3e6b1c7c6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <fabian_kessler at gmx.de>
Date: Thu, 4 Sep 2025 18:52:56 +0200
Subject: [PATCH 5/5] Add test, add astSearchProvider to capabilities
---
clang-tools-extra/clangd/ClangdLSPServer.cpp | 1 +
.../clangd/test/find-in-ast.test | 37 +++++++++++++++++++
2 files changed, 38 insertions(+)
create mode 100644 clang-tools-extra/clangd/test/find-in-ast.test
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index e064b03fea8ae..d7f1f683dd71b 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -639,6 +639,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
{"workspaceSymbolProvider", true},
{"referencesProvider", true},
{"astProvider", true}, // clangd extension
+ {"astSearchProvider", llvm::json::Object{{"search", true},{"replace", false}}}, // clangd extension
{"typeHierarchyProvider", true},
// Unfortunately our extension made use of the same capability name as the
// standard. Advertise this capability to tell clients that implement our
diff --git a/clang-tools-extra/clangd/test/find-in-ast.test b/clang-tools-extra/clangd/test/find-in-ast.test
new file mode 100644
index 0000000000000..6031ad10de1b3
--- /dev/null
+++ b/clang-tools-extra/clangd/test/find-in-ast.test
@@ -0,0 +1,37 @@
+# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace --dump-input always %s
+void bob();
+void f() {
+ bob();
+}
+---
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument": {"foldingRange": {"lineFoldingOnly": true}}},"trace":"off"}}
+---
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"languageId":"cpp","text":"void bob();\nvoid f() {\n bob();\n}\n","uri":"test:///foo.cpp","version":1}}}
+---
+{"id":1,"jsonrpc":"2.0","method":"textDocument/searchAST","params":{"textDocument":{"uri":"test:///foo.cpp"},"query":"declRefExpr(to(namedDecl(hasName(\"bob\"))))"}}
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "root": {
+# CHECK-NEXT: "arcana": "DeclRefExpr {{.*}} 'void ()' lvalue Function {{.*}} 'bob' 'void ()'",
+# CHECK-NEXT: "detail": "bob",
+# CHECK-NEXT: "kind": "DeclRef",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 5,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 2,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "role": "expression"
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+---
+{"jsonrpc":"2.0","id":5,"method":"shutdown"}
+---
+{"jsonrpc":"2.0","method":"exit"}
More information about the llvm-commits
mailing list