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 @@ -340,6 +340,7 @@ llvm::json::Object{ {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}}, }}, + {"typeHierarchy", true}, }}}}); } @@ -679,6 +680,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. @@ -755,6 +762,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 information about super types 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 @@ -457,6 +457,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"); llvm::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,21 @@ #include "llvm/ADT/StringRef.h" namespace clang { +class ASTContext; +class NamedDecl; + namespace clangd { class ParsedAST; class SymbolIndex; +/// Collect information about a symbol named by \p ND and return it in the form +/// of a DocumentSymbol object. If \p IncludeURI is false, the declaration is +/// required to be written in the main file, and the 'uri' field of the result +/// will be empty. If \p IncludeURI is true, the declaration may be in a +/// different file, and the 'uri' field will be populated to indicate the file. +llvm::Optional declToSym(ASTContext &Ctx, const NamedDecl &ND, + bool IncludeURI = false); + /// 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" @@ -180,8 +182,8 @@ return Result; } -namespace { -llvm::Optional declToSym(ASTContext &Ctx, const NamedDecl &ND) { +llvm::Optional declToSym(ASTContext &Ctx, const NamedDecl &ND, + bool IncludeURI) { auto &SM = Ctx.getSourceManager(); SourceLocation NameLoc = findNameLoc(&ND); @@ -194,9 +196,11 @@ if (NameLoc.isInvalid() || BeginLoc.isInvalid() || EndLoc.isInvalid()) return llvm::None; - if (!SM.isWrittenInMainFile(NameLoc) || !SM.isWrittenInMainFile(BeginLoc) || - !SM.isWrittenInMainFile(EndLoc)) - return llvm::None; + if (!IncludeURI) { + if (!SM.isWrittenInMainFile(NameLoc) || !SM.isWrittenInMainFile(BeginLoc) || + !SM.isWrittenInMainFile(EndLoc)) + return llvm::None; + } Position NameBegin = sourceLocToPosition(SM, NameLoc); Position NameEnd = sourceLocToPosition( @@ -219,9 +223,26 @@ // reports unrelated ranges we need to reconcile somehow. SI.range = SI.selectionRange; } + + if (IncludeURI) { + 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 @@ -712,6 +712,10 @@ /// Children of this symbol, e.g. properties of a class. std::vector children; + + /// File in which the symbol is contained. + /// Used for queries where the result may not be in the main file. + llvm::Optional uri; }; llvm::raw_ostream &operator<<(llvm::raw_ostream &O, const DocumentSymbol &S); llvm::json::Value toJSON(const DocumentSymbol &S); Index: clang-tools-extra/clangd/Protocol.cpp =================================================================== --- clang-tools-extra/clangd/Protocol.cpp +++ clang-tools-extra/clangd/Protocol.cpp @@ -532,6 +532,8 @@ Result["children"] = S.children; if (S.deprecated) Result["deprecated"] = true; + if (S.uri) + Result["uri"] = *S.uri; // Older gcc cannot compile 'return Result', even though it is legal. return llvm::json::Value(std::move(Result)); } Index: clang-tools-extra/clangd/XRefs.h =================================================================== --- clang-tools-extra/clangd/XRefs.h +++ clang-tools-extra/clangd/XRefs.h @@ -43,6 +43,14 @@ /// 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); + +/// Convert a a URI (such as that returned by toURI()) into a form suitable +/// for use in protocol replies (e.g. Location.uri, DocumentSymbol.uri). +/// TUPath is used to resolve the path of URI. +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" @@ -20,6 +21,23 @@ 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 @@ llvm::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); @@ -805,5 +817,57 @@ return Results; } +static Optional getSuperTypes(const CXXRecordDecl &CXXRD, + ASTContext &ASTCtx) { + Optional Result = declToSym(ASTCtx, CXXRD, /* IncludeURI = */ true); + 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()); + 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 @@ -138,6 +138,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 @@ -31,27 +31,9 @@ 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)) { @@ -59,12 +41,22 @@ } // We don't perform is_absolute check in an else branch because makeAbsolute // might return a relative path on some InMemoryFileSystems. - if (!llvm::sys::path::is_absolute(AbsolutePath) && !Opts.FallbackDir.empty()) - llvm::sys::fs::make_absolute(Opts.FallbackDir, AbsolutePath); + if (!llvm::sys::path::is_absolute(AbsolutePath) && !FallbackDir.empty()) + llvm::sys::fs::make_absolute(FallbackDir, AbsolutePath); llvm::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!"; @@ -153,7 +145,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. @@ -193,7 +185,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); @@ -465,7 +457,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 @@ -26,6 +26,7 @@ namespace { using testing::ElementsAre; +using testing::Eq; using testing::Field; using testing::IsEmpty; using testing::Matcher; @@ -40,6 +41,15 @@ return Location{URIForFile::canonicalize(File, testRoot()), Range} == arg; } +// GMock helpers for matching DocumentSymbol. +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(&DocumentSymbol::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 &> @@ -1340,6 +1350,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