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 @@ -91,6 +91,10 @@ void onRename(const RenameParams &, Callback); void onHover(const TextDocumentPositionParams &, Callback>); + void onTypeHierarchy(const TypeHierarchyParams &, + Callback>); + void onResolveTypeHierarchy(const ResolveTypeHierarchyItemParams &, + Callback>); void onChangeConfiguration(const DidChangeConfigurationParams &); void onSymbolInfo(const TextDocumentPositionParams &, 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 @@ -367,6 +367,7 @@ {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND, ExecuteCommandParams::CLANGD_APPLY_TWEAK}}, }}, + {"typeHierarchyProvider", true}, }}}}); } @@ -750,6 +751,20 @@ std::move(Reply)); } +void ClangdLSPServer::onTypeHierarchy( + const TypeHierarchyParams &Params, + Callback> Reply) { + Server->findTypeHierarchy(Params.textDocument.uri.file(), Params.position, + Params.resolve, Params.direction, std::move(Reply)); +} + +void ClangdLSPServer::onResolveTypeHierarchy( + const ResolveTypeHierarchyItemParams &Params, + Callback> Reply) { + Server->resolveTypeHierarchy(Params.item, Params.resolve, Params.direction, + std::move(Reply)); +} + void ClangdLSPServer::applyConfiguration( const ConfigurationSettings &Settings) { // Per-file update to the compilation database. @@ -828,6 +843,8 @@ MsgHandler->bind("workspace/didChangeWatchedFiles", &ClangdLSPServer::onFileEvent); MsgHandler->bind("workspace/didChangeConfiguration", &ClangdLSPServer::onChangeConfiguration); MsgHandler->bind("textDocument/symbolInfo", &ClangdLSPServer::onSymbolInfo); + MsgHandler->bind("textDocument/typeHierarchy", &ClangdLSPServer::onTypeHierarchy); + MsgHandler->bind("typeHierarchy/resolve", &ClangdLSPServer::onResolveTypeHierarchy); // clang-format on } 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 @@ -183,6 +183,16 @@ void findHover(PathRef File, Position Pos, Callback> CB); + /// Get information about type hierarchy for a given position. + void findTypeHierarchy(PathRef File, Position Pos, llvm::Optional Resolve, + llvm::Optional Direction, + Callback> CB); + + /// Resolve the given type hierarchy item in the given direction. + void resolveTypeHierarchy(TypeHierarchyItem Item, int Resolve, + TypeHierarchyDirection Direction, + Callback> CB); + /// Retrieve the top symbols from the workspace matching a query. void workspaceSymbols(StringRef Query, int 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 @@ -514,6 +514,37 @@ WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB))); } +void ClangdServer::findTypeHierarchy( + PathRef File, Position Pos, llvm::Optional Resolve, + llvm::Optional Direction, + Callback> CB) { + auto Action = [Pos, Resolve, + Direction](Callback> CB, + Expected InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::getTypeHierarchy(InpAST->AST, Pos, Resolve, Direction)); + }; + + WorkScheduler.runWithAST("Type Hierarchy", File, Bind(Action, std::move(CB))); +} + +void ClangdServer::resolveTypeHierarchy( + TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction, + Callback> CB) { + auto Action = [Item, Resolve, + Direction](Callback> CB, + Expected InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::getTypeHierarchy(InpAST->AST, Item.range.start, Resolve, + Direction)); + }; + + WorkScheduler.runWithAST("Type Hierarchy Resolve", Item.uri.file(), + Bind(Action, std::move(CB))); +} + tooling::CompileCommand ClangdServer::getCompileCommand(PathRef File) { trace::Span Span("GetCompileCommand"); llvm::Optional C = CDB.getCompileCommand(File); diff --git a/clang-tools-extra/clangd/FindSymbols.h b/clang-tools-extra/clangd/FindSymbols.h --- a/clang-tools-extra/clangd/FindSymbols.h +++ b/clang-tools-extra/clangd/FindSymbols.h @@ -13,17 +13,28 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_FINDSYMBOLS_H #include "Protocol.h" +#include "index/Index.h" #include "llvm/ADT/StringRef.h" namespace clang { +class ASTContext; +class NamedDecl; + namespace clangd { class ParsedAST; class SymbolIndex; +// Convert a index::SymbolKind to clangd::SymbolKind (LSP) +// Note, some are not perfect matches and should be improved when this LSP +// issue is addressed: +// https://github.com/Microsoft/language-server-protocol/issues/344 +SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind); + /// 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. -/// Direct children of scopes (namepaces, etc) can be listed with a trailing +/// 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. Direct children of scopes (namepaces, etc) can be listed with a +/// trailing /// "::". For example, "std::" will list all children of the std namespace and /// "::" alone will list all children of the global namespace. /// \p Limit limits the number of results returned (0 means no limit). diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp --- a/clang-tools-extra/clangd/FindSymbols.cpp +++ b/clang-tools-extra/clangd/FindSymbols.cpp @@ -13,6 +13,7 @@ #include "Logger.h" #include "Quality.h" #include "SourceCode.h" +#include "XRefs.h" #include "index/Index.h" #include "clang/AST/DeclTemplate.h" #include "clang/Index/IndexDataConsumer.h" @@ -26,12 +27,7 @@ namespace clang { namespace clangd { -namespace { -// Convert a index::SymbolKind to clangd::SymbolKind (LSP) -// Note, some are not perfect matches and should be improved when this LSP -// issue is addressed: -// https://github.com/Microsoft/language-server-protocol/issues/344 SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) { switch (Kind) { case index::SymbolKind::Unknown: @@ -87,6 +83,7 @@ llvm_unreachable("invalid symbol kind"); } +namespace { using ScoredSymbolInfo = std::pair; struct ScoredSymbolGreater { bool operator()(const ScoredSymbolInfo &L, const ScoredSymbolInfo &R) { @@ -218,6 +215,7 @@ // reports unrelated ranges we need to reconcile somehow. SI.range = SI.selectionRange; } + return SI; } 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 @@ -1014,6 +1014,82 @@ llvm::json::Value toJSON(const DocumentHighlight &DH); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const DocumentHighlight &); +enum class TypeHierarchyDirection { Children = 0, Parents = 1, Both = 2 }; +bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out); + +/// The type hierarchy params is an extension of the +/// `TextDocumentPositionsParams` with optional properties which can be used to +/// eagerly resolve the item when requesting from the server. +struct TypeHierarchyParams : public TextDocumentPositionParams { + /// The hierarchy levels to resolve. `0` indicates no level. + /// When not defined, it is treated as `0`. + llvm::Optional resolve; + + /// The direction of the hierarchy levels to resolve. + llvm::Optional direction; +}; +bool fromJSON(const llvm::json::Value &, TypeHierarchyParams &); + +struct TypeHierarchyItem { + /// The human readable name of the hierarchy item. + std::string name; + + /// Optional detail for the hierarchy item. It can be, for instance, the + /// signature of a function or method. + llvm::Optional detail; + + /// The kind of the hierarchy item. For instance, class or interface. + SymbolKind kind; + + /// `true` if the hierarchy item is deprecatd. Otherwise, `false`. + /// It is `false` by default. + llvm::Optional deprecated; + + /// The URI of the text document where this type hierarchy item belongs to. + URIForFile uri; + + /// The range enclosing this type hierarchy item not including + /// leading/trailing whitespace but everything else like comments. This + /// information is typically used to determine if the client's cursor is + /// inside the type hierarch item to reveal in the symbol in the UI. + Range range; + + /// The range that should be selected and revealed when this type hierarchy + /// item is being picked, e.g. the name of a function. Must be contained by + /// the `range`. + Range selectionRange; + + /// If this type hierarchy item is resolved, it contains the direct parents. + /// Could be empty if the item does not have direct parents. If not defined, + /// the parents have not been resolved yet. + llvm::Optional> parents; + + /// If this type hierarchy item is resolved, it contains the direct children + /// of the current item. Could be empty if the item does not have any + /// descendants. If not defined, the children have not been resolved. + llvm::Optional> children; + + /// The protocol has a slot here for an optional 'data' filed, which can + /// be used to identify a type hierarchy item in a resolve request. We don't + /// need this (the item itself is sufficient to identify what to resolve) + /// so don't declare it. +}; +llvm::json::Value toJSON(const TypeHierarchyItem &); +llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TypeHierarchyItem &); + +/// Parameters for the `typeHierarchy/resolve` request. +struct ResolveTypeHierarchyItemParams { + /// The item to resolve. + TypeHierarchyItem item; + + /// The hierarchy levels to resolve. `0` indicates no level. + int resolve; + + /// The direction of the hierarchy levels to resolve. + TypeHierarchyDirection direction; +}; +bool fromJSON(const llvm::json::Value &, ResolveTypeHierarchyItemParams &); + struct ReferenceParams : public TextDocumentPositionParams { // For now, no options like context.includeDeclaration are supported. }; 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 @@ -813,6 +813,81 @@ return true; } +bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out) { + if (auto T = E.getAsInteger()) { + if (*T < static_cast(TypeHierarchyDirection::Children) || + *T > static_cast(TypeHierarchyDirection::Both)) + return false; + Out = static_cast(*T); + return true; + } + return false; +} + +bool fromJSON(const llvm::json::Value &Params, TypeHierarchyParams &R) { + if (!fromJSON(Params, static_cast(R))) + return false; + if (auto *Resolve = Params.getAsObject()->get("resolve")) + if (!fromJSON(*Resolve, R.resolve)) + return false; + if (auto *Direction = Params.getAsObject()->get("direction")) + return fromJSON(*Direction, R.direction); + return true; +} + +llvm::raw_ostream &operator<<(llvm::raw_ostream &O, + const TypeHierarchyItem &I) { + return O << I.name << " - " << toJSON(I); +} + +llvm::json::Value toJSON(const TypeHierarchyItem &I) { + llvm::json::Object Result{{"name", I.name}, + {"kind", static_cast(I.kind)}, + {"range", I.range}, + {"selectionRange", I.selectionRange}, + {"uri", I.uri}}; + + if (I.detail) + Result["detail"] = I.detail; + if (I.deprecated) + Result["deprecated"] = I.deprecated; + if (I.parents) + Result["parents"] = I.parents; + if (I.children) + Result["children"] = I.children; + // Older gcc cannot compile 'return Result', even though it is legal. + return llvm::json::Value(std::move(Result)); +} + +bool fromJSON(const llvm::json::Value &Params, TypeHierarchyItem &I) { + llvm::json::ObjectMapper O(Params); + if (!O) + return true; // 'any' type in LSP. + + O.map("name", I.name); + O.map("detail", I.detail); + O.map("kind", I.kind); + O.map("deprecated", I.kind); + O.map("range", I.range); + O.map("selectionRange", I.selectionRange); + O.map("uri", I.uri); + O.map("parents", I.parents); + O.map("children", I.children); + return true; +} + +bool fromJSON(const llvm::json::Value &Params, + ResolveTypeHierarchyItemParams &R) { + llvm::json::ObjectMapper O(Params); + if (!O) + return true; // 'any' type in LSP. + + O.map("item", R.item); + O.map("resolve", R.resolve); + O.map("direction", R.direction); + return true; +} + bool fromJSON(const llvm::json::Value &Params, ReferenceParams &R) { TextDocumentPositionParams &Base = R; return fromJSON(Params, Base); 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 @@ -42,6 +42,16 @@ /// Get info about symbols at \p Pos. std::vector getSymbolInfo(ParsedAST &AST, Position Pos); +/// Get type hierarchy information at \p Pos. +llvm::Optional +getTypeHierarchy(ParsedAST &AST, Position Pos, llvm::Optional Resolve, + llvm::Optional Direction); + +/// 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 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 @@ -7,9 +7,11 @@ //===----------------------------------------------------------------------===// #include "XRefs.h" #include "AST.h" +#include "FindSymbols.h" #include "Logger.h" #include "SourceCode.h" #include "URI.h" +#include "index/SymbolCollector.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Index/IndexDataConsumer.h" @@ -19,6 +21,22 @@ 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`. @@ -48,16 +66,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,127 @@ return Results; } +// TODO: Reduce duplication between this function and declToSym(). +static llvm::Optional +declToTypeHierarchyItem(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); + + TypeHierarchyItem THI; + THI.name = printName(Ctx, ND); + THI.kind = SK; + THI.deprecated = ND.isDeprecated(); + THI.range = + Range{sourceLocToPosition(SM, BeginLoc), sourceLocToPosition(SM, EndLoc)}; + THI.selectionRange = Range{NameBegin, NameEnd}; + if (!THI.range.contains(THI.selectionRange)) { + // 'selectionRange' must be contained in 'range', so in cases where clang + // reports unrelated ranges we need to reconcile somehow. + THI.range = THI.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)) { + THI.uri = std::move(*U); + } else { + return llvm::None; // Not useful without a uri. + } + + return THI; +} + +static Optional +getTypeHierarchy(const CXXRecordDecl &CXXRD, ASTContext &ASTCtx, int Levels, + TypeHierarchyDirection Direction) { + Optional Result = declToTypeHierarchyItem(ASTCtx, CXXRD); + if (Result && Levels > 0) { + if (Direction == TypeHierarchyDirection::Parents || + Direction == TypeHierarchyDirection::Both) { + Result->parents.emplace(); + 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 = + getTypeHierarchy(*ParentDecl, ASTCtx, Levels - 1, Direction)) { + Result->parents->emplace_back(std::move(*ParentSym)); + } + } + } + + if (Direction == TypeHierarchyDirection::Children || + Direction == TypeHierarchyDirection::Both) { + Result->children.emplace(); + + // TODO: Populate subtypes. + } + } + return Result; +} + +llvm::Optional +getTypeHierarchy(ParsedAST &AST, Position Pos, llvm::Optional Resolve, + llvm::Optional Direction) { + 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) { + int ResolveLevels = Resolve.getValueOr(0); + TypeHierarchyDirection ResolveDirection = + Direction.getValueOr(TypeHierarchyDirection::Parents); + return getTypeHierarchy(*CXXRD, ASTCtx, ResolveLevels, ResolveDirection); + } + + return {}; +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/index/SymbolCollector.h b/clang-tools-extra/clangd/index/SymbolCollector.h --- a/clang-tools-extra/clangd/index/SymbolCollector.h +++ b/clang-tools-extra/clangd/index/SymbolCollector.h @@ -137,6 +137,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 diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -30,27 +30,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)) { @@ -58,12 +40,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!"; @@ -152,7 +144,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. @@ -192,7 +184,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); @@ -481,7 +473,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 diff --git a/clang-tools-extra/unittests/clangd/Matchers.h b/clang-tools-extra/unittests/clangd/Matchers.h --- a/clang-tools-extra/unittests/clangd/Matchers.h +++ b/clang-tools-extra/unittests/clangd/Matchers.h @@ -127,6 +127,73 @@ llvm::consumeError(ComputedValue.takeError()); \ } while (false) +// Implements the HasValue(m) matcher for matching an Optional whose +// value matches matcher m. +template class OptionalMatcher { +public: + explicit OptionalMatcher(const InnerMatcher &matcher) : matcher_(matcher) {} + + // This type conversion operator template allows Optional(m) to be + // used as a matcher for any Optional type whose value type is + // compatible with the inner matcher. + // + // The reason we do this instead of relying on + // MakePolymorphicMatcher() is that the latter is not flexible + // enough for implementing the DescribeTo() method of Optional(). + template operator Matcher() const { + return MakeMatcher(new Impl(matcher_)); + } + +private: + // The monomorphic implementation that works for a particular optional type. + template + class Impl : public ::testing::MatcherInterface { + public: + using Value = typename std::remove_const< + typename std::remove_reference::type>::type::value_type; + + explicit Impl(const InnerMatcher &matcher) + : matcher_(::testing::MatcherCast(matcher)) {} + + virtual void DescribeTo(::std::ostream *os) const { + *os << "has a value that "; + matcher_.DescribeTo(os); + } + + virtual void DescribeNegationTo(::std::ostream *os) const { + *os << "does not have a value that "; + matcher_.DescribeTo(os); + } + + virtual bool + MatchAndExplain(Optional optional, + ::testing::MatchResultListener *listener) const { + if (!optional.hasValue()) + return false; + + *listener << "which has a value "; + return MatchPrintAndExplain(*optional, matcher_, listener); + } + + private: + const Matcher matcher_; + + GTEST_DISALLOW_ASSIGN_(Impl); + }; + + const InnerMatcher matcher_; + + GTEST_DISALLOW_ASSIGN_(OptionalMatcher); +}; + +// Creates a matcher that matches an Optional that has a value +// that matches inner_matcher. +template +inline OptionalMatcher +HasValue(const InnerMatcher &inner_matcher) { + return OptionalMatcher(inner_matcher); +} + } // namespace clangd } // namespace clang #endif diff --git a/clang-tools-extra/unittests/clangd/XRefsTests.cpp b/clang-tools-extra/unittests/clangd/XRefsTests.cpp --- a/clang-tools-extra/unittests/clangd/XRefsTests.cpp +++ b/clang-tools-extra/unittests/clangd/XRefsTests.cpp @@ -25,9 +25,11 @@ namespace { using testing::ElementsAre; +using testing::Eq; using testing::Field; using testing::IsEmpty; using testing::Matcher; +using testing::Pointee; using testing::UnorderedElementsAreArray; class IgnoreDiagnostics : public DiagnosticsConsumer { @@ -39,6 +41,15 @@ return Location{URIForFile::canonicalize(File, testRoot()), Range} == arg; } +// GMock helpers for matching TypeHierarchyItem. +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 Parents(ParentMatchers... ParentsM) { + return Field(&TypeHierarchyItem::parents, HasValue(ElementsAre(ParentsM...))); +} + // Extracts ranges from an annotated example, and constructs a matcher for a // highlight set. Ranges should be named $read/$write as appropriate. Matcher &> @@ -1339,6 +1350,160 @@ } } +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 = getTypeHierarchy( + AST, Source.point(Pt), 10, TypeHierarchyDirection::Parents); + ASSERT_TRUE(bool(Result)); + EXPECT_THAT( + *Result, + AllOf( + WithName("Child2"), WithKind(SymbolKind::Struct), + Parents(AllOf( + WithName("Child1"), WithKind(SymbolKind::Struct), + SelectionRangeIs(Source.range("Child1Def")), + Parents(AllOf(WithName("Parent"), WithKind(SymbolKind::Struct), + SelectionRangeIs(Source.range("ParentDef")), + Parents())))))); + } +} + +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 = getTypeHierarchy( + AST, Source.point(Pt), 10, TypeHierarchyDirection::Parents); + ASSERT_TRUE(bool(Result)); + EXPECT_THAT( + *Result, + AllOf( + WithName("Child"), WithKind(SymbolKind::Struct), + Parents(AllOf(WithName("Parent1"), WithKind(SymbolKind::Struct), + SelectionRangeIs(Source.range("Parent1Def")), + Parents()), + AllOf(WithName("Parent3"), WithKind(SymbolKind::Struct), + SelectionRangeIs(Source.range("Parent3Def")), + Parents(AllOf( + WithName("Parent2"), WithKind(SymbolKind::Struct), + SelectionRangeIs(Source.range("Parent2Def")), + Parents())))))); + } +} + +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 = getTypeHierarchy( + AST, Source.point(Pt), 10, TypeHierarchyDirection::Parents); + ASSERT_TRUE(bool(Result)); + EXPECT_THAT( + *Result, + AllOf( + WithName("Child2"), WithKind(SymbolKind::Struct), + Parents(AllOf( + WithName("Child1"), WithKind(SymbolKind::Struct), + SelectionRangeIs(Source.range("Child1Def")), + Parents(AllOf(WithName("Parent"), WithKind(SymbolKind::Struct), + SelectionRangeIs(Source.range("ParentDef")), + Parents())))))); + } +} + } // namespace } // namespace clangd } // namespace clang