Index: clang-tools-extra/clangd/ClangdLSPServer.h =================================================================== --- clang-tools-extra/clangd/ClangdLSPServer.h +++ clang-tools-extra/clangd/ClangdLSPServer.h @@ -91,6 +91,8 @@ void onRename(const RenameParams &, Callback); void onHover(const TextDocumentPositionParams &, Callback>); + void onSuperTypes(const TextDocumentPositionParams &, + Callback>); void onChangeConfiguration(const DidChangeConfigurationParams &); void onSymbolInfo(const TextDocumentPositionParams &, Callback>); Index: clang-tools-extra/clangd/ClangdLSPServer.cpp =================================================================== --- clang-tools-extra/clangd/ClangdLSPServer.cpp +++ clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -338,6 +338,7 @@ json::Object{ {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}}, }}, + {"typeHierarchy", true}, }}}}); } @@ -677,6 +678,12 @@ std::move(Reply)); } +void ClangdLSPServer::onSuperTypes(const TextDocumentPositionParams &Params, + Callback> Reply) { + Server->findSuperTypes(Params.textDocument.uri.file(), Params.position, + std::move(Reply)); +} + void ClangdLSPServer::applyConfiguration( const ConfigurationSettings &Settings) { // Per-file update to the compilation database. @@ -753,6 +760,7 @@ MsgHandler->bind("workspace/didChangeWatchedFiles", &ClangdLSPServer::onFileEvent); MsgHandler->bind("workspace/didChangeConfiguration", &ClangdLSPServer::onChangeConfiguration); MsgHandler->bind("textDocument/symbolInfo", &ClangdLSPServer::onSymbolInfo); + MsgHandler->bind("textDocument/superTypes", &ClangdLSPServer::onSuperTypes); // clang-format on } Index: clang-tools-extra/clangd/ClangdServer.h =================================================================== --- clang-tools-extra/clangd/ClangdServer.h +++ clang-tools-extra/clangd/ClangdServer.h @@ -172,6 +172,10 @@ void findHover(PathRef File, Position Pos, Callback> CB); + /// Get type hierarchy information for a given position. + void findSuperTypes(PathRef File, Position Pos, + Callback> CB); + /// Retrieve the top symbols from the workspace matching a query. void workspaceSymbols(StringRef Query, int Limit, Callback> CB); Index: clang-tools-extra/clangd/ClangdServer.cpp =================================================================== --- clang-tools-extra/clangd/ClangdServer.cpp +++ clang-tools-extra/clangd/ClangdServer.cpp @@ -456,6 +456,18 @@ WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB))); } +void ClangdServer::findSuperTypes(PathRef File, Position Pos, + Callback> CB) { + auto Action = [Pos](Callback> CB, + Expected InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::getSuperTypes(InpAST->AST, Pos)); + }; + + WorkScheduler.runWithAST("Super Types", File, Bind(Action, std::move(CB))); +} + tooling::CompileCommand ClangdServer::getCompileCommand(PathRef File) { trace::Span Span("GetCompileCommand"); Optional C = CDB.getCompileCommand(File); Index: clang-tools-extra/clangd/FindSymbols.h =================================================================== --- clang-tools-extra/clangd/FindSymbols.h +++ clang-tools-extra/clangd/FindSymbols.h @@ -17,10 +17,16 @@ #include "llvm/ADT/StringRef.h" namespace clang { +class ASTContext; +class NamedDecl; + namespace clangd { class ParsedAST; class SymbolIndex; +llvm::Optional declToSym(ASTContext &Ctx, const NamedDecl &ND); +llvm::Optional declToSymExt(ASTContext &Ctx, const NamedDecl &ND); + /// Searches for the symbols matching \p Query. The syntax of \p Query can be /// the non-qualified name or fully qualified of a symbol. For example, "vector" /// will match the symbol std::vector and "std::vector" would also match it. Index: clang-tools-extra/clangd/FindSymbols.cpp =================================================================== --- clang-tools-extra/clangd/FindSymbols.cpp +++ clang-tools-extra/clangd/FindSymbols.cpp @@ -14,7 +14,9 @@ #include "Logger.h" #include "Quality.h" #include "SourceCode.h" +#include "XRefs.h" #include "index/Index.h" +#include "index/SymbolCollector.h" #include "clang/AST/DeclTemplate.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexSymbol.h" @@ -181,7 +183,6 @@ return Result; } -namespace { llvm::Optional declToSym(ASTContext &Ctx, const NamedDecl &ND) { auto &SM = Ctx.getSourceManager(); @@ -223,6 +224,59 @@ return SI; } +// TODO: Factor out some of the code shared by declToSym() and declToSymExt(). +llvm::Optional declToSymExt(ASTContext &Ctx, const NamedDecl &ND) { + auto &SM = Ctx.getSourceManager(); + + SourceLocation NameLoc = findNameLoc(&ND); + // getFileLoc is a good choice for us, but we also need to make sure + // sourceLocToPosition won't switch files, so we call getSpellingLoc on top of + // that to make sure it does not switch files. + // FIXME: sourceLocToPosition should not switch files! + SourceLocation BeginLoc = SM.getSpellingLoc(SM.getFileLoc(ND.getBeginLoc())); + SourceLocation EndLoc = SM.getSpellingLoc(SM.getFileLoc(ND.getEndLoc())); + if (NameLoc.isInvalid() || BeginLoc.isInvalid() || EndLoc.isInvalid()) + return llvm::None; + + Position NameBegin = sourceLocToPosition(SM, NameLoc); + Position NameEnd = sourceLocToPosition( + SM, Lexer::getLocForEndOfToken(NameLoc, 0, SM, Ctx.getLangOpts())); + + index::SymbolInfo SymInfo = index::getSymbolInfo(&ND); + // FIXME: this is not classifying constructors, destructors and operators + // correctly (they're all "methods"). + SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind); + + DocumentSymbolExt SI; + SI.name = printName(Ctx, ND); + SI.kind = SK; + SI.deprecated = ND.isDeprecated(); + SI.range = + Range{sourceLocToPosition(SM, BeginLoc), sourceLocToPosition(SM, EndLoc)}; + SI.selectionRange = Range{NameBegin, NameEnd}; + if (!SI.range.contains(SI.selectionRange)) { + // 'selectionRange' must be contained in 'range', so in cases where clang + // reports unrelated ranges we need to reconcile somehow. + SI.range = SI.selectionRange; + } + + StringRef Filename = SM.getFilename(BeginLoc); + std::string FileURI = toURI(SM, Filename, {}); + std::string TUPath; + const FileEntry *FE = + SM.getFileEntryForID(SM.getMainFileID()); + if (auto Path = getCanonicalPath(FE, SM)) + TUPath = *Path; + if (auto U = getURIForFile(FileURI, TUPath)) { + SI.uri = std::move(*U); + } else { + return llvm::None; // Not useful without a uri. + } + + return SI; +} + +namespace { /// A helper class to build an outline for the parse AST. It traverse the AST /// directly instead of using RecursiveASTVisitor (RAV) for three main reasons: /// - there is no way to keep RAV from traversing subtrees we're not Index: clang-tools-extra/clangd/Protocol.h =================================================================== --- clang-tools-extra/clangd/Protocol.h +++ clang-tools-extra/clangd/Protocol.h @@ -716,6 +716,41 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &O, const DocumentSymbol &S); llvm::json::Value toJSON(const DocumentSymbol &S); +// An extended version of DocumentSymbol that also contains a 'uri' field. +// TODO: Is there a way to reduce duplication between DocumentSymbol and DocumentSymbolExt? +struct DocumentSymbolExt { + /// The name of this symbol. + std::string name; + + /// More detail for this symbol, e.g the signature of a function. + std::string detail; + + /// The kind of this symbol. + SymbolKind kind; + + /// Indicates if this symbol is deprecated. + bool deprecated; + + /// The range enclosing this symbol not including leading/trailing whitespace + /// but everything else like comments. This information is typically used to + /// determine if the clients cursor is inside the symbol to reveal in the + /// symbol in the UI. + Range range; + + /// The range that should be selected and revealed when this symbol is being + /// picked, e.g the name of a function. Must be contained by the `range`. + Range selectionRange; + + /// Children of this symbol, e.g. properties of a class. + std::vector children; + + /// File in which the symbol is contained. + URIForFile uri; +}; + +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const DocumentSymbolExt &); +llvm::json::Value toJSON(const DocumentSymbolExt &S); + /// Represents information about programming constructs like variables, classes, /// interfaces etc. struct SymbolInformation { Index: clang-tools-extra/clangd/Protocol.cpp =================================================================== --- clang-tools-extra/clangd/Protocol.cpp +++ clang-tools-extra/clangd/Protocol.cpp @@ -531,6 +531,27 @@ return json::Value(std::move(Result)); } +llvm::raw_ostream &operator<<(llvm::raw_ostream & O, const DocumentSymbolExt & S) { + return O << S.name << " - " << toJSON(S); +} + +json::Value toJSON(const DocumentSymbolExt& S) { + json::Object Result{{"name", S.name}, + {"kind", static_cast(S.kind)}, + {"range", S.range}, + {"selectionRange", S.selectionRange}, + {"uri", S.uri}}; + + if (!S.detail.empty()) + Result["detail"] = S.detail; + if (!S.children.empty()) + Result["children"] = S.children; + if (S.deprecated) + Result["deprecated"] = true; + // Older gcc cannot compile 'return Result', even though it is legal. + return json::Value(std::move(Result)); +} + json::Value toJSON(const WorkspaceEdit &WE) { if (!WE.changes) return json::Object{}; Index: clang-tools-extra/clangd/XRefs.h =================================================================== --- clang-tools-extra/clangd/XRefs.h +++ clang-tools-extra/clangd/XRefs.h @@ -41,6 +41,12 @@ /// Get info about symbols at \p Pos. std::vector getSymbolInfo(ParsedAST &AST, Position Pos); +/// Get the super types information at \p Pos. +llvm::Optional getSuperTypes(ParsedAST &AST, Position Pos); + +// TODO: Document +Optional getURIForFile(StringRef FileURI, StringRef TUPath); + } // namespace clangd } // namespace clang Index: clang-tools-extra/clangd/XRefs.cpp =================================================================== --- clang-tools-extra/clangd/XRefs.cpp +++ clang-tools-extra/clangd/XRefs.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "XRefs.h" #include "AST.h" +#include "FindSymbols.h" #include "Logger.h" #include "SourceCode.h" #include "URI.h" @@ -21,6 +22,23 @@ using namespace llvm; namespace clang { namespace clangd { + +Optional getURIForFile(StringRef FileURI, + StringRef TUPath) { + auto Uri = URI::parse(FileURI); + if (!Uri) { + elog("Could not parse URI {0}: {1}", FileURI, Uri.takeError()); + return None; + } + auto U = URIForFile::fromURI(*Uri, TUPath); + if (!U) { + elog("Could not resolve URI {0}: {1}", FileURI, U.takeError()); + return None; + } + + return *U; +} + namespace { // Get the definition from a given declaration `D`. @@ -49,16 +67,10 @@ Optional toLSPLocation(const SymbolLocation &Loc, StringRef TUPath) { if (!Loc) return None; - auto Uri = URI::parse(Loc.FileURI); - if (!Uri) { - elog("Could not parse URI {0}: {1}", Loc.FileURI, Uri.takeError()); - return None; - } - auto U = URIForFile::fromURI(*Uri, TUPath); - if (!U) { - elog("Could not resolve URI {0}: {1}", Loc.FileURI, U.takeError()); + + auto U = getURIForFile(Loc.FileURI, TUPath); + if (!U) return None; - } Location LSPLoc; LSPLoc.uri = std::move(*U); @@ -799,5 +811,58 @@ return Results; } +static Optional getSuperTypes(const CXXRecordDecl &CXXRD, + ASTContext &ASTCtx) { + Optional Result = declToSymExt(ASTCtx, CXXRD); + if (Result) { + for (auto It = CXXRD.bases_begin(); It != CXXRD.bases_end(); It++) { + const RecordType *ParentType = + It->getType().getTypePtr()->getAs(); + if (!ParentType) + continue; + + const CXXRecordDecl *ParentDecl = ParentType->getAsCXXRecordDecl(); + if (!ParentDecl) + continue; + + if (Optional ParentSym = + getSuperTypes(*ParentDecl, ASTCtx)) { + Result->children.emplace_back(std::move(*ParentSym)); + } + } + } + return Result; +} + +Optional getSuperTypes(ParsedAST &AST, Position Pos) { + ASTContext &ASTCtx = AST.getASTContext(); + const SourceManager &SourceMgr = ASTCtx.getSourceManager(); + SourceLocation SourceLocationBeg = + getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID()); + // Identified symbols at a specific position. + IdentifiedSymbol Symbols = getSymbolAtPosition(AST, SourceLocationBeg); + + if (Symbols.Decls.empty()) + return {}; + + const Decl *D = Symbols.Decls[0].D; + const CXXRecordDecl *CXXRD; + + if (const VarDecl *VD = dyn_cast(D)) { + // If this is a variable, use the type of the variable. + CXXRD = VD->getType().getTypePtr()->getAsCXXRecordDecl(); + } else if (const CXXMethodDecl *Method = dyn_cast(D)) { + // If this is a method, use the type of the class. + CXXRD = Method->getParent(); + } else { + CXXRD = dyn_cast(D); + } + + if (CXXRD) + return getSuperTypes(*CXXRD, ASTCtx); + + return {}; +} + } // namespace clangd } // namespace clang Index: clang-tools-extra/clangd/index/SymbolCollector.h =================================================================== --- clang-tools-extra/clangd/index/SymbolCollector.h +++ clang-tools-extra/clangd/index/SymbolCollector.h @@ -134,6 +134,18 @@ llvm::DenseMap FilesToIndexCache; }; +// Returns a URI of \p Path. Firstly, this makes the \p Path absolute using the +// current working directory of the given SourceManager if the Path is not an +// absolute path. If failed, this resolves relative paths against \p FallbackDir +// to get an absolute path. Then, this tries creating an URI for the absolute +// path with schemes specified in \p Opts. This returns an URI with the first +// working scheme, if there is any; otherwise, this returns None. +// +// The Path can be a path relative to the build directory, or retrieved from +// the SourceManager. +std::string toURI(const SourceManager &SM, llvm::StringRef Path, + llvm::StringRef FallbackDir); + } // namespace clangd } // namespace clang #endif Index: clang-tools-extra/clangd/index/SymbolCollector.cpp =================================================================== --- clang-tools-extra/clangd/index/SymbolCollector.cpp +++ clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -32,27 +32,9 @@ using namespace llvm; namespace clang { namespace clangd { -namespace { - -/// If \p ND is a template specialization, returns the described template. -/// Otherwise, returns \p ND. -const NamedDecl &getTemplateOrThis(const NamedDecl &ND) { - if (auto T = ND.getDescribedTemplate()) - return *T; - return ND; -} -// Returns a URI of \p Path. Firstly, this makes the \p Path absolute using the -// current working directory of the given SourceManager if the Path is not an -// absolute path. If failed, this resolves relative paths against \p FallbackDir -// to get an absolute path. Then, this tries creating an URI for the absolute -// path with schemes specified in \p Opts. This returns an URI with the first -// working scheme, if there is any; otherwise, this returns None. -// -// The Path can be a path relative to the build directory, or retrieved from -// the SourceManager. std::string toURI(const SourceManager &SM, llvm::StringRef Path, - const SymbolCollector::Options &Opts) { + llvm::StringRef FallbackDir) { llvm::SmallString<128> AbsolutePath(Path); if (auto CanonPath = getCanonicalPath(SM.getFileManager().getFile(Path), SM)) { @@ -60,12 +42,22 @@ } // We don't perform is_absolute check in an else branch because makeAbsolute // might return a relative path on some InMemoryFileSystems. - if (!sys::path::is_absolute(AbsolutePath) && !Opts.FallbackDir.empty()) - sys::fs::make_absolute(Opts.FallbackDir, AbsolutePath); + if (!sys::path::is_absolute(AbsolutePath) && !FallbackDir.empty()) + sys::fs::make_absolute(FallbackDir, AbsolutePath); sys::path::remove_dots(AbsolutePath, /*remove_dot_dot=*/true); return URI::create(AbsolutePath).toString(); } +namespace { + +/// If \p ND is a template specialization, returns the described template. +/// Otherwise, returns \p ND. +const NamedDecl &getTemplateOrThis(const NamedDecl &ND) { + if (auto T = ND.getDescribedTemplate()) + return *T; + return ND; +} + // All proto generated headers should start with this line. static const char *PROTO_HEADER_COMMENT = "// Generated by the protocol buffer compiler. DO NOT EDIT!"; @@ -154,7 +146,7 @@ if (Header.startswith("<") || Header.startswith("\"")) return Header.str(); } - return toURI(SM, Header, Opts); + return toURI(SM, Header, Opts.FallbackDir); } // Return the symbol range of the token at \p TokLoc. @@ -194,7 +186,7 @@ auto Path = SM.getFilename(TokLoc); if (Path.empty()) return None; - FileURIStorage = toURI(SM, Path, Opts); + FileURIStorage = toURI(SM, Path, Opts.FallbackDir); SymbolLocation Result; Result.FileURI = FileURIStorage.c_str(); auto Range = getTokenRange(TokLoc, SM, LangOpts); @@ -466,7 +458,7 @@ auto Found = URICache.find(FID); if (Found == URICache.end()) { if (auto *FileEntry = SM.getFileEntryForID(FID)) { - auto FileURI = toURI(SM, FileEntry->getName(), Opts); + auto FileURI = toURI(SM, FileEntry->getName(), Opts.FallbackDir); Found = URICache.insert({FID, FileURI}).first; } else { // Ignore cases where we can not find a corresponding file entry Index: clang-tools-extra/unittests/clangd/XRefsTests.cpp =================================================================== --- clang-tools-extra/unittests/clangd/XRefsTests.cpp +++ clang-tools-extra/unittests/clangd/XRefsTests.cpp @@ -27,6 +27,7 @@ namespace { using testing::ElementsAre; +using testing::Eq; using testing::Field; using testing::IsEmpty; using testing::Matcher; @@ -41,6 +42,15 @@ return Location{URIForFile::canonicalize(File, testRoot()), Range} == arg; } +// GMock helpers for matching DocumentSymbolExt. +MATCHER_P(WithName, N, "") { return arg.name == N; } +MATCHER_P(WithKind, Kind, "") { return arg.kind == Kind; } +MATCHER_P(SelectionRangeIs, R, "") { return arg.selectionRange == R; } +template +testing::Matcher Children(ChildMatchers... ChildrenM) { + return Field(&DocumentSymbolExt::children, ElementsAre(ChildrenM...)); +} + // Extracts ranges from an annotated example, and constructs a matcher for a // highlight set. Ranges should be named $read/$write as appropriate. Matcher &> @@ -1337,6 +1347,155 @@ } } +TEST(SuperTypes, SimpleInheritanceOnTypeOrVariable) { + Annotations Source(R"cpp( +struct $ParentDef[[Parent]] +{ + int a; +}; + +struct $Child1Def[[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(); + + for (auto Pt : {"p1", "p2", "p3", "p4"}) { + llvm::Optional Result = getSuperTypes(AST, Source.point(Pt)); + ASSERT_TRUE(bool(Result)); + EXPECT_THAT(*Result, + AllOf(WithName("Child2"), WithKind(SymbolKind::Struct), + Children(AllOf(WithName("Child1"), + WithKind(SymbolKind::Struct), + SelectionRangeIs(Source.range("Child1Def")), + Children(AllOf(WithName("Parent"), + WithKind(SymbolKind::Struct), + SelectionRangeIs(Source.range("ParentDef")), + Children())))))); + } +} + +TEST(SuperTypes, MultipleInheritanceOnTypeOrVariable) { + Annotations Source(R"cpp( +struct $Parent1Def[[Parent1]] +{ + int a; +}; + +struct $Parent2Def[[Parent2]] +{ + int b; +}; + +struct $Parent3Def[[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(); + + for (auto Pt : {"c1", "c2", "c3", "c4"}) { + llvm::Optional Result = getSuperTypes(AST, Source.point(Pt)); + ASSERT_TRUE(bool(Result)); + EXPECT_THAT(*Result, + AllOf(WithName("Child"), WithKind(SymbolKind::Struct), + Children(AllOf(WithName("Parent1"), + WithKind(SymbolKind::Struct), + SelectionRangeIs(Source.range("Parent1Def")), + Children()), + AllOf(WithName("Parent3"), + WithKind(SymbolKind::Struct), + SelectionRangeIs(Source.range("Parent3Def")), + Children(AllOf(WithName("Parent2"), + WithKind(SymbolKind::Struct), + SelectionRangeIs(Source.range("Parent2Def")), + Children())))))); + } +} + +TEST(SuperTypes, OnMethod) { + Annotations Source(R"cpp( +struct $ParentDef[[Parent]] +{ + void method (); + void method () const; + void method (int x); + void method (char x); +}; + +struct $Child1Def[[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()); + + for (auto Pt : {"p1", "p2"}) { + llvm::Optional Result = getSuperTypes(AST, Source.point(Pt)); + ASSERT_TRUE(bool(Result)); + EXPECT_THAT(*Result, + AllOf(WithName("Child2"), WithKind(SymbolKind::Struct), + Children(AllOf(WithName("Child1"), + WithKind(SymbolKind::Struct), + SelectionRangeIs(Source.range("Child1Def")), + Children(AllOf(WithName("Parent"), + WithKind(SymbolKind::Struct), + SelectionRangeIs(Source.range("ParentDef")), + Children())))))); + } +} + } // namespace } // namespace clangd } // namespace clang