diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -114,6 +114,8 @@ Callback>); void onGoToDefinition(const TextDocumentPositionParams &, Callback>); + void onImplementation(const ImplementationParams &, + Callback>); void onReference(const ReferenceParams &, Callback>); void onSwitchSourceHeader(const TextDocumentIdentifier &, Callback>); diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -612,6 +612,7 @@ {"selectionRangeProvider", true}, {"documentSymbolProvider", true}, {"workspaceSymbolProvider", true}, + {"implementationProvider", true}, {"referencesProvider", true}, {"executeCommandProvider", llvm::json::Object{ @@ -1288,6 +1289,18 @@ }); } +void ClangdLSPServer::onImplementation(const ImplementationParams &Params, + Callback> Reply) { + Server->findImplementations( + Params.textDocument.uri.file(), Params.position, + [Reply = + std::move(Reply)](llvm::Expected Impls) mutable { + if (!Impls) + return Reply(Impls.takeError()); + return Reply(std::move(Impls->References)); + }); +} + void ClangdLSPServer::onSymbolInfo(const TextDocumentPositionParams &Params, Callback> Reply) { Server->symbolInfo(Params.textDocument.uri.file(), Params.position, @@ -1424,6 +1437,7 @@ MsgHandler->bind("textDocument/definition", &ClangdLSPServer::onGoToDefinition); MsgHandler->bind("textDocument/declaration", &ClangdLSPServer::onGoToDeclaration); MsgHandler->bind("textDocument/references", &ClangdLSPServer::onReference); + MsgHandler->bind("textDocument/implementation", &ClangdLSPServer::onImplementation); MsgHandler->bind("textDocument/switchSourceHeader", &ClangdLSPServer::onSwitchSourceHeader); MsgHandler->bind("textDocument/prepareRename", &ClangdLSPServer::onPrepareRename); MsgHandler->bind("textDocument/rename", &ClangdLSPServer::onRename); diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -253,6 +253,10 @@ /// Retrieve ranges that can be used to fold code within the specified file. void foldingRanges(StringRef File, Callback> CB); + /// Retrieve implementations for virtual method. + void findImplementations(PathRef File, Position Pos, + Callback CB); + /// Retrieve locations for symbol references. void findReferences(PathRef File, Position Pos, uint32_t Limit, Callback CB); diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -717,6 +717,18 @@ TUScheduler::InvalidateOnUpdate); } +void ClangdServer::findImplementations(PathRef File, Position Pos, + Callback CB) { + auto Action = [Pos, CB = std::move(CB), + this](llvm::Expected InpAST) mutable { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::findImplementations(InpAST->AST, Pos, Index)); + }; + + WorkScheduler.runWithAST("Implementations", File, std::move(Action)); +} + void ClangdServer::findReferences(PathRef File, Position Pos, uint32_t Limit, Callback CB) { auto Action = [Pos, Limit, CB = std::move(CB), diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -1402,6 +1402,10 @@ }; bool fromJSON(const llvm::json::Value &, ReferenceParams &, llvm::json::Path); +struct ImplementationParams : public TextDocumentPositionParams {}; +bool fromJSON(const llvm::json::Value &, ImplementationParams &, + llvm::json::Path); + /// Clangd extension: indicates the current state of the file in clangd, /// sent from server via the `textDocument/clangd.fileStatus` notification. struct FileStatus { diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -1209,6 +1209,12 @@ return fromJSON(Params, Base, P); } +bool fromJSON(const llvm::json::Value &Params, ImplementationParams &R, + llvm::json::Path P) { + TextDocumentPositionParams &Base = R; + return fromJSON(Params, Base, P); +} + static const char *toString(OffsetEncoding OE) { switch (OE) { case OffsetEncoding::UTF8: diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h --- a/clang-tools-extra/clangd/XRefs.h +++ b/clang-tools-extra/clangd/XRefs.h @@ -82,6 +82,11 @@ std::vector References; bool HasMore = false; }; + +/// Returns implementations of the virtual function at a specified \p Pos. +ReferencesResult findImplementations(ParsedAST &AST, Position Pos, + const SymbolIndex *Index = nullptr); + /// Returns references of the symbol at a specified \p Pos. /// \p Limit limits the number of results returned (0 means no limit). ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit, diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -1124,6 +1124,40 @@ return Result; } +ReferencesResult findImplementations(ParsedAST &AST, Position Pos, + const SymbolIndex *Index) { + ReferencesResult Results; + // We rely on index to find the implementations in subclasses. + if (!Index) + return Results; + const SourceManager &SM = AST.getSourceManager(); + auto MainFilePath = + getCanonicalPath(SM.getFileEntryForID(SM.getMainFileID()), SM); + if (!MainFilePath) { + elog("Failed to get a path for the main file, so no implementations."); + return Results; + } + auto CurLoc = sourceLocationInMainFile(SM, Pos); + DeclRelationSet Relations = + DeclRelation::TemplatePattern | DeclRelation::Alias; + std::vector Decls = + getDeclAtPosition(AST, *CurLoc, Relations); + + const auto *CMD = llvm::dyn_cast(Decls[0]); + if (!CMD) + return Results; + + SymbolID ID = getSymbolID(CMD); + RelationsRequest Req; + Req.Subjects.insert(ID); + Req.Predicate = RelationKind::OverriddenBy; + Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) { + if (auto Loc = symbolToLocation(Object, *MainFilePath)) + Results.References.push_back(*Loc); + }); + return Results; +} + ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit, const SymbolIndex *Index) { if (!Limit) diff --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test --- a/clang-tools-extra/clangd/test/initialize-params.test +++ b/clang-tools-extra/clangd/test/initialize-params.test @@ -66,7 +66,8 @@ # CHECK-NEXT: ] # CHECK-NEXT: }, # CHECK-NEXT: "hoverProvider": true, -# CHECK-NEXT: "memoryUsageProvider": true +# CHECK-NEXT: "implementationProvider": true, +# CHECK-NEXT: "memoryUsageProvider": true, # CHECK-NEXT: "referencesProvider": true, # CHECK-NEXT: "renameProvider": true, # CHECK-NEXT: "selectionRangeProvider": true, diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -41,6 +41,7 @@ using ::testing::Eq; using ::testing::IsEmpty; using ::testing::Matcher; +using ::testing::Not; using ::testing::UnorderedElementsAre; using ::testing::UnorderedElementsAreArray; @@ -1161,7 +1162,7 @@ )cpp", }; - for (const auto* Case : Tests) { + for (const auto *Case : Tests) { SCOPED_TRACE(Case); auto T = Annotations(Case); auto AST = TestTU::withCode(T.code()).build(); @@ -1464,6 +1465,44 @@ } } +TEST(FindImplementations, Inheritance) { + llvm::StringRef Test = R"cpp( + struct Base { + virtual void F$1^oo(); + }; + struct Child1 : Base { + void $1[[Fo$3^o]]() override; + virtual void B$2^ar(); + }; + struct Child2 : Child1 { + void $3[[Foo]]() override; + void $2[[Bar]]() override; + }; + void FromReference() { + Base* B; + B->Fo$1^o(); + &Base::Fo$1^o; + Child1 * C1; + C1->B$2^ar(); + C1->Fo$3^o(); + } + )cpp"; + + Annotations Code(Test); + auto TU = TestTU::withCode(Code.code()); + auto AST = TU.build(); + for (const std::string &Label : {"1", "2", "3"}) { + std::vector> ExpectedLocations; + for (const auto &R : Code.ranges(Label)) + ExpectedLocations.push_back(RangeIs(R)); + for (const auto &Point : Code.points(Label)) { + EXPECT_THAT(findImplementations(AST, Point, TU.index().get()).References, + UnorderedElementsAreArray(ExpectedLocations)) + << Code.code() << " at " << Point; + } + } +} + TEST(FindReferences, WithinAST) { const char *Tests[] = { R"cpp(// Local variable