From 6ed1d5446c11b5d3436d71fa1eb58648633bcb9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= 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 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 Reply) { + Server->findAST(Args, [Reply = std::move(Reply)]( + llvm::Expected>> + 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 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); void onCommandApplyTweak(const TweakArgs &, Callback); void onCommandApplyRename(const RenameParams &, Callback); + void onCommandSearchAST(const SearchASTArgs &, Callback); /// Outgoing LSP calls. LSPBinder::OutgoingMethodrunWithAST("Definitions", File, std::move(Action)); } +void ClangdServer::findAST(SearchASTArgs const &Args, + Callback>> CB) { + auto Action = + [Args, CB = std::move(CB)](llvm::Expected 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> Result; + Result.reserve(BoundNodes.size()); + for (auto &&BN : BoundNodes) { + auto &&Map = BN.getMap(); + std::vector 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> 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> CB); + void findAST(const SearchASTArgs &Args, + Callback>> 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 #include #include #include @@ -773,6 +781,61 @@ const syntax::Token *findNearbyIdentifier(const SpelledWord &Word, return BestTok; } +auto locateASTQuery(ParsedAST &AST, SearchASTArgs const &Query) + -> std::vector { + 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 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 &Bindings; + CollectBoundNodes(std::vector &Bindings) : Bindings(Bindings) {} + void run(const MatchFinder::MatchResult &Result) override { + Bindings.push_back(Result.Nodes); + } + }; + + MatchFinder Finder; + std::vector Matches; + DynTypedMatcher MaybeBoundMatcher = *Matcher; + if (Query.BindRoot) { + std::optional 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 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 @@ -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; + // 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?= 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 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 Reply) { +void ClangdLSPServer::onMethodSearchAST(const SearchASTArgs &Args, + Callback Reply) { Server->findAST(Args, [Reply = std::move(Reply)]( llvm::Expected>> 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); void onCommandApplyTweak(const TweakArgs &, Callback); void onCommandApplyRename(const RenameParams &, Callback); - void onCommandSearchAST(const SearchASTArgs &, Callback); + void onMethodSearchAST(const SearchASTArgs &, Callback); /// Outgoing LSP calls. LSPBinder::OutgoingMethodAST, 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> Result; - Result.reserve(BoundNodes.size()); - for (auto &&BN : BoundNodes) { + Result.reserve(BoundNodes->size()); + for (auto &&BN : *BoundNodes) { auto &&Map = BN.getMap(); std::vector 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 #include #include #include @@ -782,57 +783,54 @@ const syntax::Token *findNearbyIdentifier(const SpelledWord &Word, } auto locateASTQuery(ParsedAST &AST, SearchASTArgs const &Query) - -> std::vector { + -> llvm::Expected> { 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 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 &Bindings; - CollectBoundNodes(std::vector &Bindings) : Bindings(Bindings) {} + std::vector *Bindings; + CollectBoundNodes(std::vector &Bindings) + : Bindings(&Bindings) {} void run(const MatchFinder::MatchResult &Result) override { - Bindings.push_back(Result.Nodes); + Bindings->push_back(Result.Nodes); } }; - MatchFinder Finder; - std::vector Matches; DynTypedMatcher MaybeBoundMatcher = *Matcher; if (Query.BindRoot) { std::optional M = Matcher->tryBind("root"); if (M) MaybeBoundMatcher = *M; } + std::vector 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; + -> llvm::Expected>; // 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?= 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 Reply) { Server->findAST(Args, [Reply = std::move(Reply)]( - llvm::Expected>> + llvm::Expected 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>> CB) { + Callback CB) { auto Action = [Args, CB = std::move(CB)](llvm::Expected 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> Result; + BoundASTNodes Result; Result.reserve(BoundNodes->size()); for (auto &&BN : *BoundNodes) { auto &&Map = BN.getMap(); - std::vector 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 +#include #include #include #include @@ -263,7 +264,7 @@ class ClangdServer { Callback> CB); void findAST(const SearchASTArgs &Args, - Callback>> CB); + Callback 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>; 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 Properties); + template 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?= 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 Reply) { Server->findAST(Args, [Reply = std::move(Reply)]( - llvm::Expected - BoundNodes) mutable { + llvm::Expected 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 CB) { - auto Action = - [Args, CB = std::move(CB)](llvm::Expected 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 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> CB); - void findAST(const SearchASTArgs &Args, - Callback CB); + void findAST(const SearchASTArgs &Args, Callback 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 *Bindings; CollectBoundNodes(std::vector &Bindings) From 3422d2b39528cbb2814df68551a0b386fe159bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= 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 | 3 ++ .../clangd/test/find-in-ast.test | 37 +++++++++++++++++++ .../clangd/test/initialize-params.test | 4 ++ 3 files changed, 44 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..b632f142e5fc0 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -639,6 +639,9 @@ 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"} diff --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test index d976b7d19fd0e..54d5b64ff2972 100644 --- a/clang-tools-extra/clangd/test/initialize-params.test +++ b/clang-tools-extra/clangd/test/initialize-params.test @@ -6,6 +6,10 @@ # CHECK-NEXT: "result": { # CHECK-NEXT: "capabilities": { # CHECK-NEXT: "astProvider": true, +# CHECK-NEXT: "astSearchProvider": { +# CHECK-NEXT: "replace": false, +# CHECK-NEXT: "search": true +# CHECK-NEXT: }, # CHECK-NEXT: "callHierarchyProvider": true, # CHECK-NEXT: "clangdInlayHintsProvider": true, # CHECK-NEXT: "codeActionProvider": true,