simark created this revision. Herald added subscribers: cfe-commits, kadircet, arphaman, jkorous, ioeric, ilya-biryukov.
This is a work in progress patch to support an eventual "get type hierarchy" request that does not exist in the LSP today. I only plan to support getting parent types (i.e. base classes) for now, because it doesn't require touching the index. Finding child classes would come later. Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D51311 Files: clangd/ClangdLSPServer.cpp clangd/ClangdLSPServer.h clangd/ClangdServer.cpp clangd/ClangdServer.h clangd/Protocol.cpp clangd/Protocol.h clangd/ProtocolHandlers.cpp clangd/ProtocolHandlers.h clangd/XRefs.cpp clangd/XRefs.h unittests/clangd/XRefsTests.cpp
Index: unittests/clangd/XRefsTests.cpp =================================================================== --- unittests/clangd/XRefsTests.cpp +++ unittests/clangd/XRefsTests.cpp @@ -27,6 +27,7 @@ namespace { using testing::ElementsAre; +using testing::Eq; using testing::Field; using testing::IsEmpty; using testing::Matcher; @@ -1068,6 +1069,166 @@ ElementsAre(Location{FooCppUri, FooWithoutHeader.range()})); } +TEST(TypeHierarchy, SimpleInheritanceOnTypeOrVariable) { + Annotations Source(R"cpp( +$ParentDef^struct Parent +{ + int a; +}; + +$Child1Def^struct Child1 : Parent +{ + int b; +}; + +struct Ch$p1^ild2 : Child1 +{ + int c; +}; + +struct Child3 : Child2 +{ + int d; +}; + +int main() +{ + Ch$p2^ild2 ch$p3^ild2; + + parent.a = 1; + ch$p4^ild2.c = 1; +} +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + llvm::Optional<TypeHierarchy> Result; + + TypeHierarchy ExpectedResult{{TypeHierarchyResult{ + "Child1", + Source.point("Child1Def"), + false, + {TypeHierarchyResult{"Parent", Source.point("ParentDef"), false, {}}}}}}; + + for (auto Pt : {"p1", "p2", "p3", "p4"}) { + Result = getTypeHierarchy(AST, Source.point(Pt)); + ASSERT_TRUE(bool(Result)); + EXPECT_THAT(*Result, Eq(ExpectedResult)); + } +} + +TEST(TypeHierarchy, MultipleInheritanceOnTypeOrVariable) { + Annotations Source(R"cpp( +$Parent1Def^struct Parent1 +{ + int a; +}; + +$Parent2Def^struct Parent2 +{ + int b; +}; + +$Parent3Def^struct Parent3 : Parent2 +{ + int c; +}; + +struct Ch$c1^ild : Parent1, Parent3 +{ + int d; +}; + +int main() +{ + Ch$c2^ild ch$c3^ild; + + ch$c4^ild.a = 1; +} +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + llvm::Optional<TypeHierarchy> Result; + TypeHierarchy ExpectedResult{{ + TypeHierarchyResult{"Parent1", Source.point("Parent1Def"), false, {}}, + TypeHierarchyResult{ + "Parent3", + Source.point("Parent3Def"), + false, + {TypeHierarchyResult{ + "Parent2", Source.point("Parent2Def"), false, {}}}}, + }}; + + for (auto Pt : {"c1", "c2", "c3", "c4"}) { + Result = getTypeHierarchy(AST, Source.point(Pt)); + ASSERT_TRUE(bool(Result)); + EXPECT_THAT(*Result, Eq(ExpectedResult)); + } +} + +TEST(TypeHierarchy, OnMethod) { + Annotations Source(R"cpp( +$ParentDef^struct Parent +{ + void method (); + void method () const; + void method (int x); + void method (char x); +}; + +$Child1Def^struct Child1 : Parent +{ + void method (); + void method (char x); +}; + +struct Child2 : Child1 +{ + void met$p1^hod (); + void met$p2^hod (int x); +}; + +struct Child3 : Child2 +{ + void method (int x); +}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + ASSERT_TRUE(AST.getDiagnostics().empty()); + + { + TypeHierarchy ExpectedResult{{TypeHierarchyResult{ + "Child1", + Source.point("Child1Def"), + true, + {TypeHierarchyResult{"Parent", Source.point("ParentDef"), true, {}}}}}}; + + llvm::Optional<TypeHierarchy> Result = + getTypeHierarchy(AST, Source.point("p1")); + ASSERT_TRUE(bool(Result)); + EXPECT_THAT(*Result, Eq(ExpectedResult)); + } + + { + TypeHierarchy ExpectedResult{{TypeHierarchyResult{ + "Child1", + Source.point("Child1Def"), + false, + {TypeHierarchyResult{"Parent", Source.point("ParentDef"), true, {}}}}}}; + + llvm::Optional<TypeHierarchy> Result = + getTypeHierarchy(AST, Source.point("p2")); + ASSERT_TRUE(bool(Result)); + EXPECT_THAT(*Result, Eq(ExpectedResult)); + } +} + } // namespace } // namespace clangd } // namespace clang Index: clangd/XRefs.h =================================================================== --- clangd/XRefs.h +++ clangd/XRefs.h @@ -34,6 +34,9 @@ /// Get the hover information when hovering at \p Pos. llvm::Optional<Hover> getHover(ParsedAST &AST, Position Pos); +/// Get the type hierarchy information at \p Pos. +llvm::Optional<TypeHierarchy> getTypeHierarchy(ParsedAST &AST, Position Pos); + } // namespace clangd } // namespace clang Index: clangd/XRefs.cpp =================================================================== --- clangd/XRefs.cpp +++ clangd/XRefs.cpp @@ -17,6 +17,10 @@ #include "clang/Index/IndexingAction.h" #include "clang/Index/USRGeneration.h" #include "llvm/Support/Path.h" + +// temporary +#include <iostream> + namespace clang { namespace clangd { using namespace llvm; @@ -660,5 +664,84 @@ return None; } +static bool MethodMatches(const CXXMethodDecl *Method, + const CXXMethodDecl *Candidate) { + // FIXME: How do I determine if Method overrides Candidate? + + return true; +} + +static void getTypeHierarchyParents(const CXXRecordDecl *CXXRD, + const CXXMethodDecl *Method, + const SourceManager &SourceMgr, + std::vector<TypeHierarchyResult> *Result) { + for (auto It = CXXRD->bases_begin(); It != CXXRD->bases_end(); It++) { + const RecordType *ParentType = + It->getType().getTypePtr()->getAs<RecordType>(); + if (!ParentType) + continue; + + const CXXRecordDecl *ParentDecl = ParentType->getAsCXXRecordDecl(); + if (!ParentDecl) + continue; + + StringRef Name = ParentDecl->getName(); + SourceLocation Loc = ParentDecl->getBeginLoc(); + Position Pos = sourceLocToPosition(SourceMgr, Loc); + Result->emplace_back(TypeHierarchyResult{Name, Pos, false, {}}); + + std::cerr << " >>> A parent is " << ParentDecl->getName().str() + << std::endl; + + for (const auto &CandidateMethod : ParentDecl->methods()) { + if (MethodMatches(Method, CandidateMethod)) { + Result->back().DeclaresMethod = true; + break; + } + } + + getTypeHierarchyParents(ParentDecl, Method, SourceMgr, + &Result->back().Parents); + } +} + +static TypeHierarchy getTypeHierarchy(const CXXRecordDecl *CXXRD, + const CXXMethodDecl *Method, + const SourceManager &SourceMgr) { + TypeHierarchy Result; + getTypeHierarchyParents(CXXRD, Method, SourceMgr, &Result.Parents); + return Result; +} + +Optional<TypeHierarchy> getTypeHierarchy(ParsedAST &AST, Position Pos) { + const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); + SourceLocation SourceLocationBeg = + getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID()); + // Identified symbols at a specific position. + auto Symbols = getSymbolAtPosition(AST, SourceLocationBeg); + + if (Symbols.Decls.empty()) + return {}; + + const Decl *D = Symbols.Decls[0]; + const CXXRecordDecl *CXXRD; + + const CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(D); + if (const VarDecl *VD = dyn_cast<VarDecl>(D)) { + // If this is a variable, use the type of the variable. + CXXRD = VD->getType().getTypePtr()->getAsCXXRecordDecl(); + } else if (Method) { + // If this is a method, use the type of the class, but + CXXRD = Method->getParent(); + } else { + CXXRD = dyn_cast<CXXRecordDecl>(D); + } + + if (CXXRD) + return getTypeHierarchy(CXXRD, Method, SourceMgr); + + return {}; +} + } // namespace clangd } // namespace clang Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ clangd/ProtocolHandlers.h @@ -54,6 +54,7 @@ virtual void onRename(RenameParams &Parames) = 0; virtual void onDocumentHighlight(TextDocumentPositionParams &Params) = 0; virtual void onHover(TextDocumentPositionParams &Params) = 0; + virtual void onTypeHierarchy(TextDocumentPositionParams &Params) = 0; virtual void onChangeConfiguration(DidChangeConfigurationParams &Params) = 0; virtual void onCancelRequest(CancelParams &Params) = 0; }; Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -67,6 +67,7 @@ &ProtocolCallbacks::onSwitchSourceHeader); Register("textDocument/rename", &ProtocolCallbacks::onRename); Register("textDocument/hover", &ProtocolCallbacks::onHover); + Register("textDocument/typeHierarchy", &ProtocolCallbacks::onTypeHierarchy); Register("textDocument/documentSymbol", &ProtocolCallbacks::onDocumentSymbol); Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent); Register("workspace/executeCommand", &ProtocolCallbacks::onCommand); Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -881,6 +881,36 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &, const CancelParams &); bool fromJSON(const llvm::json::Value &, CancelParams &); +struct TypeHierarchyResult { + std::string Name; + Position Pos; + + // Does this node implement the method targeted by the request? + bool DeclaresMethod; + + std::vector<TypeHierarchyResult> Parents; + + friend bool operator==(const TypeHierarchyResult &lhs, + const TypeHierarchyResult &rhs) { + return lhs.Name == rhs.Name && lhs.Pos == rhs.Pos && + lhs.DeclaresMethod == rhs.DeclaresMethod && + lhs.Parents == rhs.Parents; + } +}; + +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TypeHierarchyResult &); + +struct TypeHierarchy { + std::vector<TypeHierarchyResult> Parents; + + friend bool operator==(const TypeHierarchy &lhs, const TypeHierarchy &rhs) { + return lhs.Parents == rhs.Parents; + } +}; + +llvm::json::Value toJSON(const TypeHierarchy &DH); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TypeHierarchy &); + /// Param can be either of type string or number. Returns the result as a /// string. llvm::Optional<std::string> parseNumberOrString(const llvm::json::Value *Param); Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -597,6 +597,53 @@ return O; } +json::Value toJSON(const TypeHierarchyResult &THR) { + json::Object Result; + + Result["name"] = THR.Name; + Result["position"] = toJSON(THR.Pos); + // FIXME: write the rest + + return Result; +} + +static llvm::raw_ostream & +operator<<(llvm::raw_ostream &O, const std::vector<TypeHierarchyResult> &V) { + O << '{'; + + for (size_t i = 0; i < V.size(); i++) { + O << V[i]; + if (i != 0) { + O << " ,"; + } + } + + O << '}'; + + return O; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &O, + const TypeHierarchyResult &V) { + O << "{Name=" << V.Name << ", Pos=" << V.Pos + << ", DeclaresMethod=" << V.DeclaresMethod << ", Parents=" << V.Parents + << "}"; + return O; +} + +json::Value toJSON(const TypeHierarchy &TH) { + json::Object Result; + + Result["parents"] = json::Array(TH.Parents); + + return Result; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &O, const TypeHierarchy &V) { + O << "Parents=" << V.Parents; + return O; +} + bool fromJSON(const json::Value &Params, DidChangeConfigurationParams &CCP) { json::ObjectMapper O(Params); return O && O.map("settings", CCP.settings); Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -149,6 +149,10 @@ void findHover(PathRef File, Position Pos, Callback<llvm::Optional<Hover>> CB); + /// Get type hierarchy information for a given position. + void findTypeHierarchy(PathRef File, Position Pos, + Callback<llvm::Optional<TypeHierarchy>> CB); + /// Retrieve the top symbols from the workspace matching a query. void workspaceSymbols(StringRef Query, int Limit, Callback<std::vector<SymbolInformation>> CB); Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -476,6 +476,18 @@ WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB))); } +void ClangdServer::findTypeHierarchy( + PathRef File, Position Pos, Callback<llvm::Optional<TypeHierarchy>> CB) { + auto Action = [Pos](Callback<llvm::Optional<TypeHierarchy>> CB, + llvm::Expected<InputsAndAST> InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::getTypeHierarchy(InpAST->AST, Pos)); + }; + + WorkScheduler.runWithAST("Type Hierarchy", File, Bind(Action, std::move(CB))); +} + void ClangdServer::consumeDiagnostics(PathRef File, DocVersion Version, std::vector<Diag> Diags) { // We need to serialize access to resulting diagnostics to avoid calling Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -74,6 +74,7 @@ void onWorkspaceSymbol(WorkspaceSymbolParams &Params) override; void onRename(RenameParams &Parames) override; void onHover(TextDocumentPositionParams &Params) override; + void onTypeHierarchy(TextDocumentPositionParams &Params) override; void onChangeConfiguration(DidChangeConfigurationParams &Params) override; void onCancelRequest(CancelParams &Params) override; Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -414,6 +414,19 @@ }); } +void ClangdLSPServer::onTypeHierarchy(TextDocumentPositionParams &Params) { + Server.findTypeHierarchy(Params.textDocument.uri.file(), Params.position, + [](llvm::Expected<llvm::Optional<TypeHierarchy>> H) { + if (!H) { + replyError(ErrorCode::InternalError, + llvm::toString(H.takeError())); + return; + } + + reply(*H); + }); +} + void ClangdLSPServer::applyConfiguration( const ClangdConfigurationParamsChange &Settings) { // Compilation database change.
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits