Index: clang-tools-extra/trunk/clangd/ClangdLSPServer.h =================================================================== --- clang-tools-extra/trunk/clangd/ClangdLSPServer.h +++ clang-tools-extra/trunk/clangd/ClangdLSPServer.h @@ -93,6 +93,8 @@ void onRename(const RenameParams &, Callback); void onHover(const TextDocumentPositionParams &, Callback>); + void onTypeHierarchy(const TypeHierarchyParams &, + Callback>); void onChangeConfiguration(const DidChangeConfigurationParams &); void onSymbolInfo(const TextDocumentPositionParams &, Callback>); Index: clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp =================================================================== --- clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp +++ clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp @@ -368,6 +368,7 @@ {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND, ExecuteCommandParams::CLANGD_APPLY_TWEAK}}, }}, + {"typeHierarchyProvider", true}, }}}}); } @@ -806,6 +807,13 @@ std::move(Reply)); } +void ClangdLSPServer::onTypeHierarchy( + const TypeHierarchyParams &Params, + Callback> Reply) { + Server->typeHierarchy(Params.textDocument.uri.file(), Params.position, + Params.resolve, Params.direction, std::move(Reply)); +} + void ClangdLSPServer::applyConfiguration( const ConfigurationSettings &Settings) { // Per-file update to the compilation database. @@ -885,6 +893,7 @@ MsgHandler->bind("workspace/didChangeWatchedFiles", &ClangdLSPServer::onFileEvent); MsgHandler->bind("workspace/didChangeConfiguration", &ClangdLSPServer::onChangeConfiguration); MsgHandler->bind("textDocument/symbolInfo", &ClangdLSPServer::onSymbolInfo); + MsgHandler->bind("textDocument/typeHierarchy", &ClangdLSPServer::onTypeHierarchy); // clang-format on } Index: clang-tools-extra/trunk/clangd/ClangdServer.h =================================================================== --- clang-tools-extra/trunk/clangd/ClangdServer.h +++ clang-tools-extra/trunk/clangd/ClangdServer.h @@ -184,6 +184,11 @@ void findHover(PathRef File, Position Pos, Callback> CB); + /// Get information about type hierarchy for a given position. + void typeHierarchy(PathRef File, Position Pos, int Resolve, + TypeHierarchyDirection Direction, + Callback> CB); + /// Retrieve the top symbols from the workspace matching a query. void workspaceSymbols(StringRef Query, int Limit, Callback> CB); Index: clang-tools-extra/trunk/clangd/ClangdServer.cpp =================================================================== --- clang-tools-extra/trunk/clangd/ClangdServer.cpp +++ clang-tools-extra/trunk/clangd/ClangdServer.cpp @@ -362,9 +362,8 @@ void ClangdServer::applyTweak(PathRef File, Range Sel, StringRef TweakID, Callback CB) { - auto Action = [Sel](decltype(CB) CB, std::string File, - std::string TweakID, - Expected InpAST) { + auto Action = [Sel](decltype(CB) CB, std::string File, std::string TweakID, + Expected InpAST) { if (!InpAST) return CB(InpAST.takeError()); auto Selection = tweakSelection(Sel, *InpAST); @@ -523,6 +522,19 @@ WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB))); } +void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve, + TypeHierarchyDirection Direction, + Callback> CB) { + auto Action = [Pos, Resolve, Direction](decltype(CB) 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))); +} + tooling::CompileCommand ClangdServer::getCompileCommand(PathRef File) { trace::Span Span("GetCompileCommand"); llvm::Optional C = CDB.getCompileCommand(File); Index: clang-tools-extra/trunk/clangd/FindSymbols.h =================================================================== --- clang-tools-extra/trunk/clangd/FindSymbols.h +++ clang-tools-extra/trunk/clangd/FindSymbols.h @@ -21,9 +21,10 @@ class SymbolIndex; /// 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). Index: clang-tools-extra/trunk/clangd/FindSymbols.cpp =================================================================== --- clang-tools-extra/trunk/clangd/FindSymbols.cpp +++ clang-tools-extra/trunk/clangd/FindSymbols.cpp @@ -26,67 +26,8 @@ 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: - return SymbolKind::Variable; - case index::SymbolKind::Module: - return SymbolKind::Module; - case index::SymbolKind::Namespace: - return SymbolKind::Namespace; - case index::SymbolKind::NamespaceAlias: - return SymbolKind::Namespace; - case index::SymbolKind::Macro: - return SymbolKind::String; - case index::SymbolKind::Enum: - return SymbolKind::Enum; - case index::SymbolKind::Struct: - return SymbolKind::Struct; - case index::SymbolKind::Class: - return SymbolKind::Class; - case index::SymbolKind::Protocol: - return SymbolKind::Interface; - case index::SymbolKind::Extension: - return SymbolKind::Interface; - case index::SymbolKind::Union: - return SymbolKind::Class; - case index::SymbolKind::TypeAlias: - return SymbolKind::Class; - case index::SymbolKind::Function: - return SymbolKind::Function; - case index::SymbolKind::Variable: - return SymbolKind::Variable; - case index::SymbolKind::Field: - return SymbolKind::Field; - case index::SymbolKind::EnumConstant: - return SymbolKind::EnumMember; - case index::SymbolKind::InstanceMethod: - case index::SymbolKind::ClassMethod: - case index::SymbolKind::StaticMethod: - return SymbolKind::Method; - case index::SymbolKind::InstanceProperty: - case index::SymbolKind::ClassProperty: - case index::SymbolKind::StaticProperty: - return SymbolKind::Property; - case index::SymbolKind::Constructor: - case index::SymbolKind::Destructor: - return SymbolKind::Method; - case index::SymbolKind::ConversionFunction: - return SymbolKind::Function; - case index::SymbolKind::Parameter: - return SymbolKind::Variable; - case index::SymbolKind::Using: - return SymbolKind::Namespace; - } - llvm_unreachable("invalid symbol kind"); -} +namespace { using ScoredSymbolInfo = std::pair; struct ScoredSymbolGreater { bool operator()(const ScoredSymbolInfo &L, const ScoredSymbolInfo &R) { Index: clang-tools-extra/trunk/clangd/Protocol.h =================================================================== --- clang-tools-extra/trunk/clangd/Protocol.h +++ clang-tools-extra/trunk/clangd/Protocol.h @@ -25,6 +25,7 @@ #include "URI.h" #include "index/SymbolID.h" +#include "clang/Index/IndexSymbol.h" #include "llvm/ADT/Optional.h" #include "llvm/Support/JSON.h" #include @@ -331,6 +332,12 @@ SymbolKind adjustKindToCapability(SymbolKind Kind, SymbolKindBitset &supportedSymbolKinds); +// 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); + // This struct doesn't mirror LSP! // The protocol defines deeply nested structures for client capabilities. // Instead of mapping them all, this just parses out the bits we care about. @@ -1014,6 +1021,67 @@ 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. + int resolve = 0; + + /// The direction of the hierarchy levels to resolve. + TypeHierarchyDirection direction = TypeHierarchyDirection::Parents; +}; +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 deprecated. Otherwise, `false`. + bool 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 &); + struct ReferenceParams : public TextDocumentPositionParams { // For now, no options like context.includeDeclaration are supported. }; Index: clang-tools-extra/trunk/clangd/Protocol.cpp =================================================================== --- clang-tools-extra/trunk/clangd/Protocol.cpp +++ clang-tools-extra/trunk/clangd/Protocol.cpp @@ -211,6 +211,61 @@ } } +SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) { + switch (Kind) { + case index::SymbolKind::Unknown: + return SymbolKind::Variable; + case index::SymbolKind::Module: + return SymbolKind::Module; + case index::SymbolKind::Namespace: + return SymbolKind::Namespace; + case index::SymbolKind::NamespaceAlias: + return SymbolKind::Namespace; + case index::SymbolKind::Macro: + return SymbolKind::String; + case index::SymbolKind::Enum: + return SymbolKind::Enum; + case index::SymbolKind::Struct: + return SymbolKind::Struct; + case index::SymbolKind::Class: + return SymbolKind::Class; + case index::SymbolKind::Protocol: + return SymbolKind::Interface; + case index::SymbolKind::Extension: + return SymbolKind::Interface; + case index::SymbolKind::Union: + return SymbolKind::Class; + case index::SymbolKind::TypeAlias: + return SymbolKind::Class; + case index::SymbolKind::Function: + return SymbolKind::Function; + case index::SymbolKind::Variable: + return SymbolKind::Variable; + case index::SymbolKind::Field: + return SymbolKind::Field; + case index::SymbolKind::EnumConstant: + return SymbolKind::EnumMember; + case index::SymbolKind::InstanceMethod: + case index::SymbolKind::ClassMethod: + case index::SymbolKind::StaticMethod: + return SymbolKind::Method; + case index::SymbolKind::InstanceProperty: + case index::SymbolKind::ClassProperty: + case index::SymbolKind::StaticProperty: + return SymbolKind::Property; + case index::SymbolKind::Constructor: + case index::SymbolKind::Destructor: + return SymbolKind::Method; + case index::SymbolKind::ConversionFunction: + return SymbolKind::Function; + case index::SymbolKind::Parameter: + return SymbolKind::Variable; + case index::SymbolKind::Using: + return SymbolKind::Namespace; + } + llvm_unreachable("invalid symbol kind"); +} + bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R) { const llvm::json::Object *O = Params.getAsObject(); if (!O) @@ -812,6 +867,66 @@ return true; } +bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out) { + auto T = E.getAsInteger(); + if (!T) + return false; + if (*T < static_cast(TypeHierarchyDirection::Children) || + *T > static_cast(TypeHierarchyDirection::Both)) + return false; + Out = static_cast(*T); + return true; +} + +bool fromJSON(const llvm::json::Value &Params, TypeHierarchyParams &R) { + llvm::json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument) && + O.map("position", R.position) && O.map("resolve", R.resolve) && + O.map("direction", R.direction); +} + +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; + return std::move(Result); +} + +bool fromJSON(const llvm::json::Value &Params, TypeHierarchyItem &I) { + llvm::json::ObjectMapper O(Params); + + // Required fields. + if (!(O && O.map("name", I.name) && O.map("kind", I.kind) && + O.map("uri", I.uri) && O.map("range", I.range) && + O.map("selectionRange", I.selectionRange))) { + return false; + } + + // Optional fields. + O.map("detail", I.detail); + O.map("deprecated", I.deprecated); + O.map("parents", I.parents); + O.map("children", I.children); + + return true; +} + bool fromJSON(const llvm::json::Value &Params, ReferenceParams &R) { TextDocumentPositionParams &Base = R; return fromJSON(Params, Base); Index: clang-tools-extra/trunk/clangd/XRefs.h =================================================================== --- clang-tools-extra/trunk/clangd/XRefs.h +++ clang-tools-extra/trunk/clangd/XRefs.h @@ -58,6 +58,17 @@ /// Get info about symbols at \p Pos. std::vector getSymbolInfo(ParsedAST &AST, Position Pos); +/// Find the record type references at \p Pos. +const CXXRecordDecl *findRecordTypeAt(ParsedAST &AST, Position Pos); + +/// Given a record type declaration, find its base (parent) types. +std::vector typeParents(const CXXRecordDecl *CXXRD); + +/// Get type hierarchy information at \p Pos. +llvm::Optional +getTypeHierarchy(ParsedAST &AST, Position Pos, int Resolve, + TypeHierarchyDirection Direction); + } // namespace clangd } // namespace clang Index: clang-tools-extra/trunk/clangd/XRefs.cpp =================================================================== --- clang-tools-extra/trunk/clangd/XRefs.cpp +++ clang-tools-extra/trunk/clangd/XRefs.cpp @@ -7,13 +7,16 @@ //===----------------------------------------------------------------------===// #include "XRefs.h" #include "AST.h" +#include "FindSymbols.h" #include "Logger.h" #include "SourceCode.h" #include "URI.h" #include "index/Merge.h" +#include "index/SymbolCollector.h" #include "index/SymbolLocation.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/Type.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexSymbol.h" #include "clang/Index/IndexingAction.h" @@ -826,5 +829,135 @@ return OS; } +// FIXME(nridge): 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; + } + + auto FilePath = + getCanonicalPath(SM.getFileEntryForID(SM.getFileID(BeginLoc)), SM); + auto TUPath = getCanonicalPath(SM.getFileEntryForID(SM.getMainFileID()), SM); + if (!FilePath || !TUPath) + return llvm::None; // Not useful without a uri. + THI.uri = URIForFile::canonicalize(*FilePath, *TUPath); + + return THI; +} + +static Optional getTypeAncestors(const CXXRecordDecl &CXXRD, + ASTContext &ASTCtx) { + Optional Result = declToTypeHierarchyItem(ASTCtx, CXXRD); + if (!Result) + return Result; + + Result->parents.emplace(); + + for (const CXXRecordDecl *ParentDecl : typeParents(&CXXRD)) { + if (Optional ParentSym = + getTypeAncestors(*ParentDecl, ASTCtx)) { + Result->parents->emplace_back(std::move(*ParentSym)); + } + } + + return Result; +} + +const CXXRecordDecl *findRecordTypeAt(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 nullptr; + + const Decl *D = Symbols.Decls[0]; + + if (const VarDecl *VD = dyn_cast(D)) { + // If this is a variable, use the type of the variable. + return VD->getType().getTypePtr()->getAsCXXRecordDecl(); + } + + if (const CXXMethodDecl *Method = dyn_cast(D)) { + // If this is a method, use the type of the class. + return Method->getParent(); + } + + // We don't handle FieldDecl because it's not clear what behaviour + // the user would expect: the enclosing class type (as with a + // method), or the field's type (as with a variable). + + return dyn_cast(D); +} + +std::vector typeParents(const CXXRecordDecl *CXXRD) { + std::vector Result; + + for (auto Base : CXXRD->bases()) { + const CXXRecordDecl *ParentDecl = nullptr; + + const Type *Type = Base.getType().getTypePtr(); + if (const RecordType *RT = Type->getAs()) { + ParentDecl = RT->getAsCXXRecordDecl(); + } + + // For now, do not handle dependent bases such as "Base". + // We would like to handle them by heuristically choosing the + // primary template declaration, but we need to take care to + // avoid infinite recursion. + + if (ParentDecl) + Result.push_back(ParentDecl); + } + + return Result; +} + +llvm::Optional +getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels, + TypeHierarchyDirection Direction) { + const CXXRecordDecl *CXXRD = findRecordTypeAt(AST, Pos); + if (!CXXRD) + return llvm::None; + + Optional Result = + getTypeAncestors(*CXXRD, AST.getASTContext()); + // FIXME(nridge): Resolve type descendants if direction is Children or Both, + // and ResolveLevels > 0. + return Result; +} + } // namespace clangd } // namespace clang Index: clang-tools-extra/trunk/clangd/index/SymbolCollector.h =================================================================== --- clang-tools-extra/trunk/clangd/index/SymbolCollector.h +++ clang-tools-extra/trunk/clangd/index/SymbolCollector.h @@ -113,7 +113,8 @@ void finish() override; private: - const Symbol *addDeclaration(const NamedDecl &, SymbolID, bool IsMainFileSymbol); + const Symbol *addDeclaration(const NamedDecl &, SymbolID, + bool IsMainFileSymbol); void addDefinition(const NamedDecl &, const Symbol &DeclSymbol); // All Symbols collected from the AST. Index: clang-tools-extra/trunk/clangd/index/SymbolCollector.cpp =================================================================== --- clang-tools-extra/trunk/clangd/index/SymbolCollector.cpp +++ clang-tools-extra/trunk/clangd/index/SymbolCollector.cpp @@ -326,8 +326,8 @@ // ND is the canonical (i.e. first) declaration. If it's in the main file, // then no public declaration was visible, so assume it's main-file only. - bool IsMainFileOnly = SM.isWrittenInMainFile(SM.getExpansionLoc( - ND->getBeginLoc())); + bool IsMainFileOnly = + SM.isWrittenInMainFile(SM.getExpansionLoc(ND->getBeginLoc())); if (!shouldCollectSymbol(*ND, *ASTCtx, Opts, IsMainFileOnly)) return true; // Do not store references to main-file symbols. @@ -516,8 +516,7 @@ FilesToIndexCache.clear(); } -const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, - SymbolID ID, +const Symbol *SymbolCollector::addDeclaration(const NamedDecl &ND, SymbolID ID, bool IsMainFileOnly) { auto &Ctx = ND.getASTContext(); auto &SM = Ctx.getSourceManager(); Index: clang-tools-extra/trunk/test/clangd/initialize-params.test =================================================================== --- clang-tools-extra/trunk/test/clangd/initialize-params.test +++ clang-tools-extra/trunk/test/clangd/initialize-params.test @@ -40,6 +40,7 @@ # CHECK-NEXT: ] # CHECK-NEXT: }, # CHECK-NEXT: "textDocumentSync": 2, +# CHECK-NEXT: "typeHierarchyProvider": true # CHECK-NEXT: "workspaceSymbolProvider": true # CHECK-NEXT: } # CHECK-NEXT: } Index: clang-tools-extra/trunk/test/clangd/type-hierarchy.test =================================================================== --- clang-tools-extra/trunk/test/clangd/type-hierarchy.test +++ clang-tools-extra/trunk/test/clangd/type-hierarchy.test @@ -0,0 +1,92 @@ +# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"struct Parent {};\nstruct Child1 : Parent {};\nstruct Child2 : Child1 {};"}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/typeHierarchy","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":11},"direction":1,"resolve":1}} +# CHECK: "id": 1 +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": { +# CHECK-NEXT: "kind": 23, +# CHECK-NEXT: "name": "Child2", +# CHECK-NEXT: "parents": [ +# CHECK-NEXT: { +# CHECK-NEXT: "kind": 23, +# CHECK-NEXT: "name": "Child1", +# CHECK-NEXT: "parents": [ +# CHECK-NEXT: { +# CHECK-NEXT: "kind": 23, +# CHECK-NEXT: "name": "Parent", +# CHECK-NEXT: "parents": [], +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 15, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "selectionRange": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "line": 0 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///clangd-test/main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 24, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "selectionRange": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "line": 1 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///clangd-test/main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 24, +# CHECK-NEXT: "line": 2 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 0, +# CHECK-NEXT: "line": 2 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "selectionRange": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "line": 2 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "line": 2 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file:///clangd-test/main.cpp" +# CHECK-NEXT: } +--- +{"jsonrpc":"2.0","id":2,"method":"shutdown"} +--- +{"jsonrpc":"2.0","method":"exit"} Index: clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt +++ clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt @@ -46,6 +46,7 @@ TestTU.cpp ThreadingTests.cpp TraceTests.cpp + TypeHierarchyTests.cpp TweakTests.cpp URITests.cpp XRefsTests.cpp Index: clang-tools-extra/trunk/unittests/clangd/Matchers.h =================================================================== --- clang-tools-extra/trunk/unittests/clangd/Matchers.h +++ clang-tools-extra/trunk/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 Index: clang-tools-extra/trunk/unittests/clangd/TypeHierarchyTests.cpp =================================================================== --- clang-tools-extra/trunk/unittests/clangd/TypeHierarchyTests.cpp +++ clang-tools-extra/trunk/unittests/clangd/TypeHierarchyTests.cpp @@ -0,0 +1,462 @@ +//===-- TypeHierarchyTests.cpp ---------------------------*- C++ -*-------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "Annotations.h" +#include "ClangdUnit.h" +#include "Compiler.h" +#include "Matchers.h" +#include "SyncAPI.h" +#include "TestFS.h" +#include "TestTU.h" +#include "XRefs.h" +#include "index/FileIndex.h" +#include "index/SymbolCollector.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/Index/IndexingAction.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/ScopedPrinter.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +using testing::AllOf; +using testing::ElementsAre; +using testing::Eq; +using testing::Field; +using testing::IsEmpty; +using testing::Matcher; +using testing::Pointee; +using testing::UnorderedElementsAreArray; + +// 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...))); +} + +TEST(FindRecordTypeAt, TypeOrVariable) { + Annotations Source(R"cpp( +struct Ch^ild2 { + int c; +}; + +int main() { + Ch^ild2 ch^ild2; + ch^ild2.c = 1; +} +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + ASSERT_TRUE(AST.getDiagnostics().empty()); + + for (Position Pt : Source.points()) { + const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt); + EXPECT_EQ(&findDecl(AST, "Child2"), static_cast(RD)); + } +} + +TEST(FindRecordTypeAt, Method) { + Annotations Source(R"cpp( +struct Child2 { + void met^hod (); + void met^hod (int x); +}; + +int main() { + Child2 child2; + child2.met^hod(5); +} +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + ASSERT_TRUE(AST.getDiagnostics().empty()); + + for (Position Pt : Source.points()) { + const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt); + EXPECT_EQ(&findDecl(AST, "Child2"), static_cast(RD)); + } +} + +TEST(FindRecordTypeAt, Field) { + Annotations Source(R"cpp( +struct Child2 { + int fi^eld; +}; + +int main() { + Child2 child2; + child2.fi^eld = 5; +} +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + ASSERT_TRUE(AST.getDiagnostics().empty()); + + for (Position Pt : Source.points()) { + const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt); + // A field does not unambiguously specify a record type + // (possible associated reocrd types could be the field's type, + // or the type of the record that the field is a member of). + EXPECT_EQ(nullptr, RD); + } +} + +TEST(TypeParents, SimpleInheritance) { + Annotations Source(R"cpp( +struct Parent { + int a; +}; + +struct Child1 : Parent { + int b; +}; + +struct Child2 : Child1 { + int c; +}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + ASSERT_TRUE(AST.getDiagnostics().empty()); + + const CXXRecordDecl *Parent = + dyn_cast(&findDecl(AST, "Parent")); + const CXXRecordDecl *Child1 = + dyn_cast(&findDecl(AST, "Child1")); + const CXXRecordDecl *Child2 = + dyn_cast(&findDecl(AST, "Child2")); + + EXPECT_THAT(typeParents(Parent), ElementsAre()); + EXPECT_THAT(typeParents(Child1), ElementsAre(Parent)); + EXPECT_THAT(typeParents(Child2), ElementsAre(Child1)); +} + +TEST(TypeParents, MultipleInheritance) { + Annotations Source(R"cpp( +struct Parent1 { + int a; +}; + +struct Parent2 { + int b; +}; + +struct Parent3 : Parent2 { + int c; +}; + +struct Child : Parent1, Parent3 { + int d; +}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + ASSERT_TRUE(AST.getDiagnostics().empty()); + + const CXXRecordDecl *Parent1 = + dyn_cast(&findDecl(AST, "Parent1")); + const CXXRecordDecl *Parent2 = + dyn_cast(&findDecl(AST, "Parent2")); + const CXXRecordDecl *Parent3 = + dyn_cast(&findDecl(AST, "Parent3")); + const CXXRecordDecl *Child = dyn_cast(&findDecl(AST, "Child")); + + EXPECT_THAT(typeParents(Parent1), ElementsAre()); + EXPECT_THAT(typeParents(Parent2), ElementsAre()); + EXPECT_THAT(typeParents(Parent3), ElementsAre(Parent2)); + EXPECT_THAT(typeParents(Child), ElementsAre(Parent1, Parent3)); +} + +TEST(TypeParents, ClassTemplate) { + Annotations Source(R"cpp( +struct Parent {}; + +template +struct Child : Parent {}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + ASSERT_TRUE(AST.getDiagnostics().empty()); + + const CXXRecordDecl *Parent = + dyn_cast(&findDecl(AST, "Parent")); + const CXXRecordDecl *Child = + dyn_cast(&findDecl(AST, "Child"))->getTemplatedDecl(); + + EXPECT_THAT(typeParents(Child), ElementsAre(Parent)); +} + +MATCHER_P(ImplicitSpecOf, ClassTemplate, "") { + const ClassTemplateSpecializationDecl *CTS = + dyn_cast(arg); + return CTS && + CTS->getSpecializedTemplate()->getTemplatedDecl() == ClassTemplate && + CTS->getSpecializationKind() == TSK_ImplicitInstantiation; +} + +// This is similar to findDecl(AST, QName), but supports using +// a template-id as a query. +const NamedDecl &findDeclWithTemplateArgs(ParsedAST &AST, + llvm::StringRef Query) { + return findDecl(AST, [&Query](const NamedDecl &ND) { + std::string QName; + llvm::raw_string_ostream OS(QName); + PrintingPolicy Policy(ND.getASTContext().getLangOpts()); + // Use getNameForDiagnostic() which includes the template + // arguments in the printed name. + ND.getNameForDiagnostic(OS, Policy, /*Qualified=*/true); + OS.flush(); + return QName == Query; + }); +} + +TEST(TypeParents, TemplateSpec1) { + Annotations Source(R"cpp( +template +struct Parent {}; + +template <> +struct Parent {}; + +struct Child1 : Parent {}; + +struct Child2 : Parent {}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + ASSERT_TRUE(AST.getDiagnostics().empty()); + + const CXXRecordDecl *Parent = + dyn_cast(&findDecl(AST, "Parent"))->getTemplatedDecl(); + const CXXRecordDecl *ParentSpec = + dyn_cast(&findDeclWithTemplateArgs(AST, "Parent")); + const CXXRecordDecl *Child1 = + dyn_cast(&findDecl(AST, "Child1")); + const CXXRecordDecl *Child2 = + dyn_cast(&findDecl(AST, "Child2")); + + EXPECT_THAT(typeParents(Child1), ElementsAre(ImplicitSpecOf(Parent))); + EXPECT_THAT(typeParents(Child2), ElementsAre(ParentSpec)); +} + +TEST(TypeParents, TemplateSpec2) { + Annotations Source(R"cpp( +struct Parent {}; + +template +struct Child {}; + +template <> +struct Child : Parent {}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + ASSERT_TRUE(AST.getDiagnostics().empty()); + + const CXXRecordDecl *Parent = + dyn_cast(&findDecl(AST, "Parent")); + const CXXRecordDecl *Child = + dyn_cast(&findDecl(AST, "Child"))->getTemplatedDecl(); + const CXXRecordDecl *ChildSpec = + dyn_cast(&findDeclWithTemplateArgs(AST, "Child")); + + EXPECT_THAT(typeParents(Child), ElementsAre()); + EXPECT_THAT(typeParents(ChildSpec), ElementsAre(Parent)); +} + +// This is disabled for now, because support for dependent bases +// requires additional measures to avoid infinite recursion. +TEST(DISABLED_TypeParents, DependentBase) { + Annotations Source(R"cpp( +template +struct Parent {}; + +template +struct Child1 : Parent {}; + +template +struct Child2 : Parent::Type {}; + +template +struct Child3 : T {}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + ASSERT_TRUE(AST.getDiagnostics().empty()); + + const CXXRecordDecl *Parent = + dyn_cast(&findDecl(AST, "Parent"))->getTemplatedDecl(); + const CXXRecordDecl *Child1 = + dyn_cast(&findDecl(AST, "Child1"))->getTemplatedDecl(); + const CXXRecordDecl *Child2 = + dyn_cast(&findDecl(AST, "Child2"))->getTemplatedDecl(); + const CXXRecordDecl *Child3 = + dyn_cast(&findDecl(AST, "Child3"))->getTemplatedDecl(); + + // For "Parent", use the primary template as a best-effort guess. + EXPECT_THAT(typeParents(Child1), ElementsAre(Parent)); + // For "Parent::Type", there is nothing we can do. + EXPECT_THAT(typeParents(Child2), ElementsAre()); + // Likewise for "T". + EXPECT_THAT(typeParents(Child3), ElementsAre()); +} + +// Parts of getTypeHierarchy() are tested in more detail by the +// FindRecordTypeAt.* and TypeParents.* tests above. This test exercises the +// entire operation. +TEST(TypeHierarchy, Parents) { + Annotations Source(R"cpp( +struct $Parent1Def[[Parent1]] { + int a; +}; + +struct $Parent2Def[[Parent2]] { + int b; +}; + +struct $Parent3Def[[Parent3]] : Parent2 { + int c; +}; + +struct Ch^ild : Parent1, Parent3 { + int d; +}; + +int main() { + Ch^ild ch^ild; + + ch^ild.a = 1; +} +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + for (Position Pt : Source.points()) { + // Set ResolveLevels to 0 because it's only used for Children; + // for Parents, getTypeHierarchy() always returns all levels. + llvm::Optional Result = getTypeHierarchy( + AST, Pt, /*ResolveLevels=*/0, 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(TypeHierarchy, RecursiveHierarchy1) { + Annotations Source(R"cpp( + template + struct S : S {}; + + S^<0> s; + )cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + // The compiler should produce a diagnostic for hitting the + // template instantiation depth. + ASSERT_TRUE(!AST.getDiagnostics().empty()); + + // Make sure getTypeHierarchy() doesn't get into an infinite recursion. + llvm::Optional Result = getTypeHierarchy( + AST, Source.points()[0], 0, TypeHierarchyDirection::Parents); + ASSERT_TRUE(bool(Result)); + EXPECT_THAT(*Result, + AllOf(WithName("S"), WithKind(SymbolKind::Struct), Parents())); +} + +TEST(TypeHierarchy, RecursiveHierarchy2) { + Annotations Source(R"cpp( + template + struct S : S {}; + + template <> + struct S<0>{}; + + S^<2> s; + )cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + ASSERT_TRUE(AST.getDiagnostics().empty()); + + // Make sure getTypeHierarchy() doesn't get into an infinite recursion. + llvm::Optional Result = getTypeHierarchy( + AST, Source.points()[0], 0, TypeHierarchyDirection::Parents); + ASSERT_TRUE(bool(Result)); + EXPECT_THAT(*Result, + AllOf(WithName("S"), WithKind(SymbolKind::Struct), Parents())); +} + +TEST(TypeHierarchy, RecursiveHierarchy3) { + Annotations Source(R"cpp( + template + struct S : S {}; + + template <> + struct S<0>{}; + + template + struct Foo { + S^ s; + }; + )cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + + ASSERT_TRUE(AST.getDiagnostics().empty()); + + // Make sure getTypeHierarchy() doesn't get into an infinite recursion. + llvm::Optional Result = getTypeHierarchy( + AST, Source.points()[0], 0, TypeHierarchyDirection::Parents); + ASSERT_TRUE(bool(Result)); + EXPECT_THAT(*Result, + AllOf(WithName("S"), WithKind(SymbolKind::Struct), Parents())); +} + +} // namespace +} // namespace clangd +} // namespace clang