https://github.com/Febbe updated https://github.com/llvm/llvm-project/pull/156090
From 3d41a8cce0d25173c7ce9eb64d278c59b36e8615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <[email protected]> Date: Wed, 2 Oct 2024 16:33:22 +0200 Subject: [PATCH 1/6] Add AST Search Capabilities to clangd - utilizes clang_query to find nodes in the AST - searches and returns the bound node names - added astSearchProvider to capabilities - added tests --- clang-tools-extra/clangd/CMakeLists.txt | 2 + clang-tools-extra/clangd/ClangdLSPServer.cpp | 15 +++++ clang-tools-extra/clangd/ClangdLSPServer.h | 1 + clang-tools-extra/clangd/ClangdServer.cpp | 36 +++++++++++ clang-tools-extra/clangd/ClangdServer.h | 2 + clang-tools-extra/clangd/Protocol.cpp | 13 ++++ clang-tools-extra/clangd/Protocol.h | 14 +++++ clang-tools-extra/clangd/XRefs.cpp | 59 +++++++++++++++++++ clang-tools-extra/clangd/XRefs.h | 10 ++++ .../clangd/test/find-in-ast.test | 37 ++++++++++++ .../clangd/test/initialize-params.test | 4 ++ llvm/include/llvm/Support/JSON.h | 4 ++ 12 files changed, 197 insertions(+) create mode 100644 clang-tools-extra/clangd/test/find-in-ast.test diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt index 9fa7d92d8374c..cfdfd61a31329 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 @@ -186,6 +187,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 43e8b35e45c89..5e0b474e47651 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -75,6 +75,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 SearchASTMethod = "textDocument/searchAST"; CodeAction toCodeAction(const ClangdServer::CodeActionResult::Rename &R, const URIForFile &File) { @@ -637,6 +638,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 @@ -851,6 +855,16 @@ 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 { + if (!BoundNodes) + return Reply(BoundNodes.takeError()); + return Reply(*BoundNodes); + }); +} + void ClangdLSPServer::applyEdit(WorkspaceEdit WE, llvm::json::Value Success, Callback<llvm::json::Value> Reply) { ApplyWorkspaceEditParams Edit; @@ -1733,6 +1747,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.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 6ada3fd9e6e47..8d7f4ccd67eea 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 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 37eb82116f3a9..ebb2297d2b7c8 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -815,6 +815,42 @@ void ClangdServer::locateSymbolAt(PathRef File, Position Pos, WorkScheduler->runWithAST("Definitions", File, std::move(Action)); } +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)); + }; + + 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 264ab7437c248..9a4ff39a2ad3e 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -268,6 +268,8 @@ class ClangdServer { void locateSymbolAt(PathRef File, Position Pos, Callback<std::vector<LocatedSymbol>> CB); + void findAST(const SearchASTArgs &Args, Callback<BoundASTNodes> 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 d5c50cc1cfa35..8b73529a94b88 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" @@ -1674,6 +1675,18 @@ 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 b511ca495e47f..8cbab565828fc 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" @@ -1507,6 +1508,19 @@ 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); +using BoundASTNodes = std::vector<std::map<std::string, struct ASTNode>>; + 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 6886d235811d5..fd1b4af288979 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 "clang-include-cleaner/Analysis.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/SourceLocation.h" #include "clang/Basic/SourceManager.h" @@ -50,6 +56,7 @@ #include "clang/Index/IndexingAction.h" #include "clang/Index/IndexingOptions.h" #include "clang/Lex/Lexer.h" +#include "clang/Parse/Parser.h" #include "clang/Sema/HeuristicResolver.h" #include "clang/Tooling/Syntax/Tokens.h" #include "clang/UnifiedSymbolResolution/USRGeneration.h" @@ -66,6 +73,7 @@ #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include <algorithm> +#include <cmath> #include <optional> #include <string> #include <vector> @@ -822,6 +830,57 @@ const syntax::Token *findNearbyIdentifier(const SpelledWord &Word, return BestTok; } +auto locateASTQuery(ParsedAST &AST, SearchASTArgs const &Query) + -> 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(); + + 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) { + return error("Not a valid top-level matcher: {}.", Diag.toString()); + } + + 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); + } + }; + + 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)) { + 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; +} + 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..d6c0e3b99941b 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 &) + -> llvm::Expected<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) 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, diff --git a/llvm/include/llvm/Support/JSON.h b/llvm/include/llvm/Support/JSON.h index 3b145a69c74cb..74066e4efcd3c 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 7ab24e572d819fa5cd94cd43b7dde618eb4b9e87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <[email protected]> Date: Sat, 15 Aug 2026 22:38:48 +0200 Subject: [PATCH 2/6] Add capabilities to complete a matcher expression - adds a clang/completeASTMatcher method to the clangd LSP server, which returns a list of all available AST matcher expressions and the text to complete the current expression. - adds a test for the new method, which checks that the returned completions return the completion. - uses already existing code from ast-matchers. - mirrors behaviour of clang-query --- clang-tools-extra/clangd/ClangdLSPServer.cpp | 15 ++++++++++++ clang-tools-extra/clangd/ClangdLSPServer.h | 2 ++ clang-tools-extra/clangd/ClangdServer.cpp | 21 +++++++++++++++++ clang-tools-extra/clangd/ClangdServer.h | 3 +++ clang-tools-extra/clangd/Protocol.cpp | 19 +++++++++++++++ clang-tools-extra/clangd/Protocol.h | 19 +++++++++++++++ .../clangd/test/completion-matcher.test | 23 +++++++++++++++++++ 7 files changed, 102 insertions(+) create mode 100644 clang-tools-extra/clangd/test/completion-matcher.test diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 5e0b474e47651..ce9e264f9bd9b 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -76,6 +76,8 @@ const llvm::StringLiteral ApplyFixCommand = "clangd.applyFix"; const llvm::StringLiteral ApplyTweakCommand = "clangd.applyTweak"; const llvm::StringLiteral ApplyRenameCommand = "clangd.applyRename"; constexpr llvm::StringLiteral SearchASTMethod = "textDocument/searchAST"; +constexpr llvm::StringLiteral CompleteASTMatcherMethod = + "clangd/completeASTMatcher"; CodeAction toCodeAction(const ClangdServer::CodeActionResult::Rename &R, const URIForFile &File) { @@ -855,6 +857,18 @@ void ClangdLSPServer::onCommandApplyRename(const RenameParams &R, }); } +void ClangdLSPServer::onMethodCompleteASTMatcher( + const CompleteASTMatcherArgs &Args, Callback<llvm::json::Value> Reply) { + Server->completeASTMatcher( + Args, + [Reply = std::move(Reply)]( + llvm::Expected<std::vector<ASTMatcherCompletion>> ToolTips) mutable { + if (!ToolTips) + return Reply(ToolTips.takeError()); + return Reply(*ToolTips); + }); +} + void ClangdLSPServer::onMethodSearchAST(const SearchASTArgs &Args, Callback<llvm::json::Value> Reply) { Server->findAST(Args, [Reply = std::move(Reply)]( @@ -1747,6 +1761,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.method(CompleteASTMatcherMethod, this, &ClangdLSPServer::onMethodCompleteASTMatcher); Bind.method(SearchASTMethod, this, &ClangdLSPServer::onMethodSearchAST); ApplyWorkspaceEdit = Bind.outgoingMethod("workspace/applyEdit"); diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h index 8d7f4ccd67eea..07a4b16a71e07 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -186,6 +186,8 @@ 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 onMethodCompleteASTMatcher(const CompleteASTMatcherArgs &, + Callback<llvm::json::Value>); void onMethodSearchAST(const SearchASTArgs &, Callback<llvm::json::Value>); /// Outgoing LSP calls. diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index ebb2297d2b7c8..fb4592bd57db8 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -35,6 +35,8 @@ #include "support/MemoryTree.h" #include "support/ThreadsafeFS.h" #include "support/Trace.h" +#include "clang/ASTMatchers/Dynamic/Parser.h" +#include "clang/ASTMatchers/Dynamic/Registry.h" #include "clang/Basic/Stack.h" #include "clang/Format/Format.h" #include "clang/Lex/Preprocessor.h" @@ -815,6 +817,25 @@ void ClangdServer::locateSymbolAt(PathRef File, Position Pos, WorkScheduler->runWithAST("Definitions", File, std::move(Action)); } +void ClangdServer::completeASTMatcher( + const CompleteASTMatcherArgs &Args, + Callback<std::vector<ASTMatcherCompletion>> CB) { + auto Action = [Args, CB = std::move(CB)]() mutable -> void { + using ::clang::ast_matchers::dynamic::Parser; + using ::clang::ast_matchers::dynamic::MatcherCompletion; + + std::vector<ASTMatcherCompletion> ToolTips; + auto QueryRef = StringRef(Args.searchQuery); + std::vector<MatcherCompletion> Comps = + Parser::completeExpression(QueryRef, Args.offset); + for (auto I = Comps.begin(), E = Comps.end(); I != E; ++I) { + ToolTips.push_back(ASTMatcherCompletion{I->TypedText, I->MatcherDecl}); + } + return CB(std::move(ToolTips)); + }; + WorkScheduler->runQuick("ASTMatcherCompletion", "", std::move(Action)); +} + void ClangdServer::findAST(SearchASTArgs const &Args, Callback<BoundASTNodes> CB) { auto Action = [Args, CB = std::move(CB)]( diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index 9a4ff39a2ad3e..73241b0c055cd 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -270,6 +270,9 @@ class ClangdServer { void findAST(const SearchASTArgs &Args, Callback<BoundASTNodes> CB); + void completeASTMatcher(const CompleteASTMatcherArgs &Args, + Callback<std::vector<ASTMatcherCompletion>> 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 8b73529a94b88..f65807ca8f3bd 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -1675,6 +1675,13 @@ bool fromJSON(const llvm::json::Value &Params, SelectionRangeParams &S, O.map("positions", S.positions); } +bool fromJSON(const llvm::json::Value &Params, CompleteASTMatcherArgs &Args, + llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); + return O && O.map("searchQuery", Args.searchQuery) && + O.map("offset", Args.offset); +} + bool fromJSON(const llvm::json::Value &Params, SearchASTArgs &Args, llvm::json::Path P) { llvm::json::ObjectMapper O(Params, P); @@ -1782,6 +1789,18 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const ASTNode &Root) { return OS; } +llvm::json::Value toJSON(const ASTMatcherCompletion &ASTTT) { + llvm::json::Object Result{{"typedText", ASTTT.typedText}, + {"matcherDecl", ASTTT.matcherDecl}}; + return Result; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const ASTMatcherCompletion &ASTTT) { + OS << ASTTT.typedText << " - " << ASTTT.matcherDecl; + return OS; +} + bool fromJSON(const llvm::json::Value &E, SymbolID &S, llvm::json::Path P) { auto Str = E.getAsString(); if (!Str) { diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index 8cbab565828fc..f710246e45a7f 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -1508,6 +1508,13 @@ struct RenameParams { bool fromJSON(const llvm::json::Value &, RenameParams &, llvm::json::Path); llvm::json::Value toJSON(const RenameParams &); +struct CompleteASTMatcherArgs { + std::string searchQuery; + size_t offset; +}; +bool fromJSON(const llvm::json::Value &, CompleteASTMatcherArgs &, + llvm::json::Path); + struct SearchASTArgs { std::string searchQuery; TextDocumentIdentifier textDocument; @@ -2115,6 +2122,18 @@ struct ASTNode { llvm::json::Value toJSON(const ASTNode &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const ASTNode &); +/// The query completion tooltip. +struct ASTMatcherCompletion { + /// Text to complete the matcher. + std::string typedText; + /// Full Matcher declaration including type and parameters + std::string matcherDecl; +}; + +llvm::json::Value toJSON(const ASTMatcherCompletion &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, + const ASTMatcherCompletion &); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/test/completion-matcher.test b/clang-tools-extra/clangd/test/completion-matcher.test new file mode 100644 index 0000000000000..1ff751ef33a4b --- /dev/null +++ b/clang-tools-extra/clangd/test/completion-matcher.test @@ -0,0 +1,23 @@ +# 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":"clangd/completeASTMatcher","params":{"query":"declRefE", "offset": 8}} +# CHECK: "id": 1, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "matcherDecl": "Matcher<Stmt> declRefExpr(Matcher<DeclRefExpr>...)", +# CHECK-NEXT: "typedText": "xpr(" +# CHECK-NEXT: } +# CHECK-NEXT: ] +--- +{"jsonrpc":"2.0","id":5,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} From cbc1e12b3c56ffab08ea0533a76c188c4d77515a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <[email protected]> Date: Sat, 15 Aug 2026 23:44:48 +0200 Subject: [PATCH 3/6] fixes for first commit --- clang-tools-extra/clangd/ClangdServer.cpp | 2 +- clang-tools-extra/clangd/XRefs.cpp | 3 --- clang-tools-extra/clangd/XRefs.h | 6 ------ 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index fb4592bd57db8..4817e28d0f75c 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -868,7 +868,7 @@ void ClangdServer::findAST(SearchASTArgs const &Args, CB(std::move(Result)); }; - WorkScheduler->runWithAST("Definitions", Args.textDocument.uri.file(), + WorkScheduler->runWithAST("FindAST", Args.textDocument.uri.file(), std::move(Action)); } diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index fd1b4af288979..6c9fb53dd2722 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -14,8 +14,6 @@ #include "ParsedAST.h" #include "Protocol.h" #include "Quality.h" -#include "Query.h" -#include "QuerySession.h" #include "Selection.h" #include "SourceCode.h" #include "clang-include-cleaner/Analysis.h" @@ -73,7 +71,6 @@ #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include <algorithm> -#include <cmath> #include <optional> #include <string> #include <vector> diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h index d6c0e3b99941b..63e1a72093459 100644 --- a/clang-tools-extra/clangd/XRefs.h +++ b/clang-tools-extra/clangd/XRefs.h @@ -33,12 +33,6 @@ 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 &) -> llvm::Expected<std::vector<ast_matchers::BoundNodes>>; From dae08a52bddc901a5c0544fede790d4898698134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <[email protected]> Date: Sat, 15 Aug 2026 23:44:59 +0200 Subject: [PATCH 4/6] fix for second --- clang-tools-extra/clangd/test/completion-matcher.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/test/completion-matcher.test b/clang-tools-extra/clangd/test/completion-matcher.test index 1ff751ef33a4b..009352538016b 100644 --- a/clang-tools-extra/clangd/test/completion-matcher.test +++ b/clang-tools-extra/clangd/test/completion-matcher.test @@ -8,7 +8,7 @@ void f() { --- {"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":"clangd/completeASTMatcher","params":{"query":"declRefE", "offset": 8}} +{"id":1,"jsonrpc":"2.0","method":"clangd/completeASTMatcher","params":{"searchQuery":"declRefE", "offset": 8}} # CHECK: "id": 1, # CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "result": [ From 9ca70d99c7791496725a8fc10785db674480bee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <[email protected]> Date: Sat, 15 Aug 2026 23:59:52 +0200 Subject: [PATCH 5/6] missing but used map include --- clang-tools-extra/clangd/Protocol.h | 1 + 1 file changed, 1 insertion(+) diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index f710246e45a7f..513b8c62d4087 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -32,6 +32,7 @@ #include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" #include <bitset> +#include <map> #include <memory> #include <optional> #include <string> From 439105d3dc376102842ecb23bd46f05464381e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <[email protected]> Date: Sun, 16 Aug 2026 00:18:36 +0200 Subject: [PATCH 6/6] size_t is not defined for json parsers --- clang-tools-extra/clangd/Protocol.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index f65807ca8f3bd..5ce3b611c82c3 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -1678,8 +1678,11 @@ bool fromJSON(const llvm::json::Value &Params, SelectionRangeParams &S, bool fromJSON(const llvm::json::Value &Params, CompleteASTMatcherArgs &Args, llvm::json::Path P) { llvm::json::ObjectMapper O(Params, P); - return O && O.map("searchQuery", Args.searchQuery) && - O.map("offset", Args.offset); + unsigned Offset{}; + auto Res = + O && O.map("searchQuery", Args.searchQuery) && O.map("offset", Offset); + Args.offset = Offset; + return Res; } bool fromJSON(const llvm::json::Value &Params, SearchASTArgs &Args, _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
