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 @@ -23,6 +23,7 @@ #include #include #include +#include namespace clang { namespace clangd { @@ -132,10 +133,16 @@ void onRename(const RenameParams &, Callback); void onHover(const TextDocumentPositionParams &, Callback>); - void onTypeHierarchy(const TypeHierarchyParams &, - Callback>); + void onPrepareTypeHierarchy(const TypeHierarchyPrepareParams &, + Callback>); + void onSuperTypes(const ResolveTypeHierarchyItemParams &, + Callback>>); + void onSubTypes(const ResolveTypeHierarchyItemParams &, + Callback>); + void onTypeHierarchy(const TypeHierarchyPrepareParams &, + Callback); void onResolveTypeHierarchy(const ResolveTypeHierarchyItemParams &, - Callback>); + Callback); void onPrepareCallHierarchy(const CallHierarchyPrepareParams &, Callback>); void onCallHierarchyIncomingCalls( 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 @@ -26,6 +26,8 @@ #include "support/Trace.h" #include "clang/Tooling/Core/Replacement.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/FunctionExtras.h" +#include "llvm/ADT/None.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/StringRef.h" @@ -571,8 +573,12 @@ {"referencesProvider", true}, {"astProvider", true}, // clangd extension {"typeHierarchyProvider", true}, - {"memoryUsageProvider", true}, // clangd extension - {"compilationDatabase", // clangd extension + // Unfortunately our extension made use of the same capability name as the + // standard. Advertise this capability to tell clients that implement our + // extension we really have support for the standardized one as well. + {"standardTypeHierarchyProvider", true}, // clangd extension + {"memoryUsageProvider", true}, // clangd extension + {"compilationDatabase", // clangd extension llvm::json::Object{{"automaticReload", true}}}, {"callHierarchyProvider", true}, {"clangdInlayHintsProvider", true}, @@ -1183,18 +1189,94 @@ }); } -void ClangdLSPServer::onTypeHierarchy( - const TypeHierarchyParams &Params, - Callback> Reply) { +// Our extension has a different representation on the wire than the standard. +// https://clangd.llvm.org/extensions#type-hierarchy +llvm::json::Value serializeTHIForExtension(TypeHierarchyItem THI) { + llvm::json::Object Result{{ + {"name", std::move(THI.name)}, + {"kind", static_cast(THI.kind)}, + {"uri", std::move(THI.uri)}, + {"range", THI.range}, + {"selectionRange", THI.selectionRange}, + {"data", std::move(THI.data)}, + }}; + if (THI.deprecated) + Result["deprecated"] = THI.deprecated; + if (THI.detail) + Result["detail"] = std::move(*THI.detail); + + if (THI.parents) { + llvm::json::Array Parents; + for (auto &Parent : *THI.parents) + Parents.emplace_back(serializeTHIForExtension(std::move(Parent))); + Result["parents"] = std::move(Parents); + } + + if (THI.children) { + llvm::json::Array Children; + for (auto &child : *THI.children) + Children.emplace_back(serializeTHIForExtension(std::move(child))); + Result["children"] = std::move(Children); + } + return Result; +} + +void ClangdLSPServer::onTypeHierarchy(const TypeHierarchyPrepareParams &Params, + Callback Reply) { + auto Serialize = + [Reply = std::move(Reply)]( + llvm::Expected> Resp) mutable { + if (!Resp) { + Reply(Resp.takeError()); + return; + } + if (Resp->empty()) { + Reply(nullptr); + return; + } + Reply(serializeTHIForExtension(std::move(Resp->front()))); + }; Server->typeHierarchy(Params.textDocument.uri.file(), Params.position, - Params.resolve, Params.direction, std::move(Reply)); + Params.resolve, Params.direction, std::move(Serialize)); } void ClangdLSPServer::onResolveTypeHierarchy( const ResolveTypeHierarchyItemParams &Params, - Callback> Reply) { + Callback Reply) { + auto Serialize = + [Reply = std::move(Reply)]( + llvm::Expected> Resp) mutable { + if (!Resp) { + Reply(Resp.takeError()); + return; + } + if (!*Resp) { + Reply(std::move(*Resp)); + return; + } + Reply(serializeTHIForExtension(std::move(**Resp))); + }; Server->resolveTypeHierarchy(Params.item, Params.resolve, Params.direction, - std::move(Reply)); + std::move(Serialize)); +} + +void ClangdLSPServer::onPrepareTypeHierarchy( + const TypeHierarchyPrepareParams &Params, + Callback> Reply) { + Server->typeHierarchy(Params.textDocument.uri.file(), Params.position, + Params.resolve, Params.direction, std::move(Reply)); +} + +void ClangdLSPServer::onSuperTypes( + const ResolveTypeHierarchyItemParams &Params, + Callback>> Reply) { + Server->superTypes(Params.item, std::move(Reply)); +} + +void ClangdLSPServer::onSubTypes( + const ResolveTypeHierarchyItemParams &Params, + Callback> Reply) { + Server->subTypes(Params.item, std::move(Reply)); } void ClangdLSPServer::onPrepareCallHierarchy( @@ -1523,6 +1605,9 @@ Bind.method("textDocument/symbolInfo", this, &ClangdLSPServer::onSymbolInfo); Bind.method("textDocument/typeHierarchy", this, &ClangdLSPServer::onTypeHierarchy); Bind.method("typeHierarchy/resolve", this, &ClangdLSPServer::onResolveTypeHierarchy); + Bind.method("textDocument/prepareTypeHierarchy", this, &ClangdLSPServer::onPrepareTypeHierarchy); + Bind.method("typeHierarchy/supertypes", this, &ClangdLSPServer::onSuperTypes); + Bind.method("typeHierarchy/subtypes", this, &ClangdLSPServer::onSubTypes); Bind.method("textDocument/prepareCallHierarchy", this, &ClangdLSPServer::onPrepareCallHierarchy); Bind.method("callHierarchy/incomingCalls", this, &ClangdLSPServer::onCallHierarchyIncomingCalls); Bind.method("textDocument/selectionRange", this, &ClangdLSPServer::onSelectionRange); 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 @@ -253,7 +253,13 @@ /// Get information about type hierarchy for a given position. void typeHierarchy(PathRef File, Position Pos, int Resolve, TypeHierarchyDirection Direction, - Callback> CB); + Callback> CB); + /// Get direct parents of a type hierarchy item. + void superTypes(const TypeHierarchyItem &Item, + Callback>> CB); + /// Get direct children of a type hierarchy item. + void subTypes(const TypeHierarchyItem &Item, + Callback> CB); /// Resolve type hierarchy item in the given direction. void resolveTypeHierarchy(TypeHierarchyItem Item, int Resolve, 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 @@ -751,7 +751,7 @@ void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve, TypeHierarchyDirection Direction, - Callback> CB) { + Callback> CB) { auto Action = [File = File.str(), Pos, Resolve, Direction, CB = std::move(CB), this](Expected InpAST) mutable { if (!InpAST) @@ -763,6 +763,22 @@ WorkScheduler->runWithAST("TypeHierarchy", File, std::move(Action)); } +void ClangdServer::superTypes( + const TypeHierarchyItem &Item, + Callback>> CB) { + WorkScheduler->run("typeHierarchy/superTypes", /*Path=*/"", + [=, CB = std::move(CB)]() mutable { + CB(clangd::superTypes(Item, Index)); + }); +} + +void ClangdServer::subTypes(const TypeHierarchyItem &Item, + Callback> CB) { + WorkScheduler->run( + "typeHierarchy/subTypes", /*Path=*/"", + [=, CB = std::move(CB)]() mutable { CB(clangd::subTypes(Item, Index)); }); +} + void ClangdServer::resolveTypeHierarchy( TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction, Callback> CB) { 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 @@ -77,6 +77,9 @@ } }; +bool fromJSON(const llvm::json::Value &, SymbolID &, llvm::json::Path); +llvm::json::Value toJSON(const SymbolID &); + // URI in "file" scheme for a file. struct URIForFile { URIForFile() = default; @@ -1379,58 +1382,66 @@ /// 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 { +struct TypeHierarchyPrepareParams : public TextDocumentPositionParams { /// The hierarchy levels to resolve. `0` indicates no level. + /// This is a clangd extension. int resolve = 0; /// The direction of the hierarchy levels to resolve. + /// This is a clangd extension. TypeHierarchyDirection direction = TypeHierarchyDirection::Parents; }; -bool fromJSON(const llvm::json::Value &, TypeHierarchyParams &, +bool fromJSON(const llvm::json::Value &, TypeHierarchyPrepareParams &, llvm::json::Path); struct TypeHierarchyItem { - /// The human readable name of the hierarchy item. + /// The name of this 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. + /// The kind of this item. SymbolKind kind; - /// `true` if the hierarchy item is deprecated. Otherwise, `false`. - bool deprecated = false; + /// More detail for this item, e.g. the signature of a function. + llvm::Optional detail; - /// The URI of the text document where this type hierarchy item belongs to. + /// The resource identifier of this item. 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. + /// The range enclosing this symbol not including leading/trailing whitespace + /// but everything else, e.g. comments and code. 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`. + /// 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; - /// 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. + /// Used to resolve a client provided item back. + struct ResolveParams { + SymbolID symbolID; + /// None means parents aren't resolved and empty is no parents. + llvm::Optional> parents; + }; + /// A data entry field that is preserved between a type hierarchy prepare and + /// supertypes or subtypes requests. It could also be used to identify the + /// type hierarchy in the server, helping improve the performance on resolving + /// supertypes and subtypes. + ResolveParams data; + + /// `true` if the hierarchy item is deprecated. Otherwise, `false`. + /// This is a clangd exntesion. + bool deprecated = false; + + /// This is a clangd exntesion. 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. + /// This is a clangd exntesion. llvm::Optional> children; - - /// An optional 'data' field, which can be used to identify a type hierarchy - /// item in a resolve request. - llvm::Optional data; }; +llvm::json::Value toJSON(const TypeHierarchyItem::ResolveParams &); +bool fromJSON(const TypeHierarchyItem::ResolveParams &); llvm::json::Value toJSON(const TypeHierarchyItem &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TypeHierarchyItem &); bool fromJSON(const llvm::json::Value &, TypeHierarchyItem &, llvm::json::Path); 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 @@ -1207,12 +1207,13 @@ return true; } -bool fromJSON(const llvm::json::Value &Params, TypeHierarchyParams &R, +bool fromJSON(const llvm::json::Value &Params, TypeHierarchyPrepareParams &R, llvm::json::Path P) { llvm::json::ObjectMapper O(Params, P); return O && O.map("textDocument", R.textDocument) && - O.map("position", R.position) && O.map("resolve", R.resolve) && - O.map("direction", R.direction); + O.map("position", R.position) && + mapOptOrNull(Params, "resolve", R.resolve, P) && + mapOptOrNull(Params, "direction", R.direction, P); } llvm::raw_ostream &operator<<(llvm::raw_ostream &O, @@ -1220,23 +1221,28 @@ return O << I.name << " - " << toJSON(I); } +llvm::json::Value toJSON(const TypeHierarchyItem::ResolveParams &RP) { + llvm::json::Object Result{{"symbolID", RP.symbolID}}; + if (RP.parents) + Result["parents"] = RP.parents; + return std::move(Result); +} +bool fromJSON(const llvm::json::Value &Params, + TypeHierarchyItem::ResolveParams &RP, llvm::json::Path P) { + llvm::json::ObjectMapper O(Params, P); + return O && O.map("symbolID", RP.symbolID) && + mapOptOrNull(Params, "parents", RP.parents, P); +} + 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}}; + llvm::json::Object Result{ + {"name", I.name}, {"kind", static_cast(I.kind)}, + {"range", I.range}, {"selectionRange", I.selectionRange}, + {"uri", I.uri}, {"data", I.data}, + }; 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; - if (I.data) - Result["data"] = I.data; return std::move(Result); } @@ -1258,8 +1264,9 @@ bool fromJSON(const llvm::json::Value &Params, ResolveTypeHierarchyItemParams &R, llvm::json::Path P) { llvm::json::ObjectMapper O(Params, P); - return O && O.map("item", R.item) && O.map("resolve", R.resolve) && - O.map("direction", R.direction); + return O && O.map("item", R.item) && + mapOptOrNull(Params, "resolve", R.resolve, P) && + mapOptOrNull(Params, "direction", R.direction, P); } bool fromJSON(const llvm::json::Value &Params, ReferenceContext &R, @@ -1510,5 +1517,22 @@ return OS; } +bool fromJSON(const llvm::json::Value &E, SymbolID &S, llvm::json::Path P) { + auto Str = E.getAsString(); + if (!Str) { + P.report("expected a string"); + return false; + } + auto ID = SymbolID::fromStr(*Str); + if (!ID) { + elog("Malformed symbolid: {0}", ID.takeError()); + P.report("malformed symbolid"); + return false; + } + S = *ID; + return true; +} +llvm::json::Value toJSON(const SymbolID &S) { return S.str(); } + } // namespace clangd } // namespace clang 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 @@ -117,17 +117,26 @@ /// 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); +/// Find the record types referenced at \p Pos. +std::vector 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( +std::vector getTypeHierarchy( ParsedAST &AST, Position Pos, int Resolve, TypeHierarchyDirection Direction, const SymbolIndex *Index = nullptr, PathRef TUPath = PathRef{}); +/// Returns direct parents of a TypeHierarchyItem using SymbolIDs stored inside +/// the item. +llvm::Optional> +superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index); +/// Returns direct children of a TypeHierarchyItem. +std::vector subTypes(const TypeHierarchyItem &Item, + const SymbolIndex *Index); + void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels, TypeHierarchyDirection Direction, const SymbolIndex *Index); 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 @@ -51,6 +51,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SmallSet.h" @@ -60,6 +61,7 @@ #include "llvm/Support/Error.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" +#include namespace clang { namespace clangd { @@ -864,7 +866,8 @@ }; ReferenceFinder(const ParsedAST &AST, - const llvm::ArrayRef Targets, bool PerToken) + const llvm::ArrayRef Targets, + bool PerToken) : PerToken(PerToken), AST(AST) { for (const NamedDecl *ND : Targets) { const Decl *CD = ND->getCanonicalDecl(); @@ -937,7 +940,7 @@ }; std::vector -findRefs(const llvm::ArrayRef TargetDecls, ParsedAST &AST, +findRefs(const llvm::ArrayRef TargetDecls, ParsedAST &AST, bool PerToken) { ReferenceFinder RefFinder(AST, TargetDecls, PerToken); index::IndexingOptions IndexOpts; @@ -1225,8 +1228,8 @@ if (const SelectionTree::Node *N = ST.commonAncestor()) { DeclRelationSet Relations = DeclRelation::TemplatePattern | DeclRelation::Alias; - auto TargetDecls= - targetDecl(N->ASTNode, Relations, AST.getHeuristicResolver()); + auto TargetDecls = + targetDecl(N->ASTNode, Relations, AST.getHeuristicResolver()); if (!TargetDecls.empty()) { // FIXME: we may get multiple DocumentHighlights with the same location // and different kinds, deduplicate them. @@ -1595,29 +1598,32 @@ HI.uri = URIForFile::canonicalize(*FilePath, TUPath); - // Compute the SymbolID and store it in the 'data' field. - // This allows typeHierarchy/resolve to be used to - // resolve children of items returned in a previous request - // for parents. - if (auto ID = getSymbolID(&ND)) - HI.data = ID.str(); - return HI; } static llvm::Optional declToTypeHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) { auto Result = declToHierarchyItem(ND, TUPath); - if (Result) + if (Result) { Result->deprecated = ND.isDeprecated(); + // Compute the SymbolID and store it in the 'data' field. + // This allows typeHierarchy/resolve to be used to + // resolve children of items returned in a previous request + // for parents. + Result->data.symbolID = getSymbolID(&ND); + } return Result; } static llvm::Optional declToCallHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) { auto Result = declToHierarchyItem(ND, TUPath); - if (Result && ND.isDeprecated()) + if (!Result) + return Result; + if (ND.isDeprecated()) Result->tags.push_back(SymbolTag::Deprecated); + if (auto ID = getSymbolID(&ND)) + Result->data = ID.str(); return Result; } @@ -1637,10 +1643,6 @@ // (https://github.com/clangd/clangd/issues/59). HI.range = HI.selectionRange; HI.uri = Loc->uri; - // Store the SymbolID in the 'data' field. The client will - // send this back in requests to resolve additional levels - // of the hierarchy. - HI.data = S.ID.str(); return HI; } @@ -1648,15 +1650,20 @@ static llvm::Optional symbolToTypeHierarchyItem(const Symbol &S, PathRef TUPath) { auto Result = symbolToHierarchyItem(S, TUPath); - if (Result) + if (Result) { Result->deprecated = (S.Flags & Symbol::Deprecated); + Result->data.symbolID = S.ID; + } return Result; } static llvm::Optional symbolToCallHierarchyItem(const Symbol &S, PathRef TUPath) { auto Result = symbolToHierarchyItem(S, TUPath); - if (Result && (S.Flags & Symbol::Deprecated)) + if (!Result) + return Result; + Result->data = S.ID.str(); + if (S.Flags & Symbol::Deprecated) Result->tags.push_back(SymbolTag::Deprecated); return Result; } @@ -1681,9 +1688,12 @@ using RecursionProtectionSet = llvm::SmallSet; +// Extracts parents from AST and populates the type hierarchy item. static void fillSuperTypes(const CXXRecordDecl &CXXRD, llvm::StringRef TUPath, - std::vector &SuperTypes, + TypeHierarchyItem &Item, RecursionProtectionSet &RPSet) { + Item.parents.emplace(); + Item.data.parents.emplace(); // typeParents() will replace dependent template specializations // with their class template, so to avoid infinite recursion for // certain types of hierarchies, keep the templates encountered @@ -1699,9 +1709,9 @@ for (const CXXRecordDecl *ParentDecl : typeParents(&CXXRD)) { if (Optional ParentSym = declToTypeHierarchyItem(*ParentDecl, TUPath)) { - ParentSym->parents.emplace(); - fillSuperTypes(*ParentDecl, TUPath, *ParentSym->parents, RPSet); - SuperTypes.emplace_back(std::move(*ParentSym)); + fillSuperTypes(*ParentDecl, TUPath, *ParentSym, RPSet); + Item.data.parents->emplace_back(ParentSym->data); + Item.parents->emplace_back(std::move(*ParentSym)); } } @@ -1710,11 +1720,12 @@ } } -const CXXRecordDecl *findRecordTypeAt(ParsedAST &AST, Position Pos) { - auto RecordFromNode = - [&AST](const SelectionTree::Node *N) -> const CXXRecordDecl * { +std::vector findRecordTypeAt(ParsedAST &AST, + Position Pos) { + auto RecordFromNode = [&AST](const SelectionTree::Node *N) { + std::vector Records; if (!N) - return nullptr; + return Records; // Note: explicitReferenceTargets() will search for both template // instantiations and template patterns, and prefer the former if available @@ -1722,30 +1733,32 @@ // class template). auto Decls = explicitReferenceTargets(N->ASTNode, DeclRelation::Underlying, AST.getHeuristicResolver()); - if (Decls.empty()) - return nullptr; - - const NamedDecl *D = Decls[0]; + for (const NamedDecl *D : Decls) { - 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 VarDecl *VD = dyn_cast(D)) { + // If this is a variable, use the type of the variable. + Records.push_back(VD->getType().getTypePtr()->getAsCXXRecordDecl()); + continue; + } - if (const CXXMethodDecl *Method = dyn_cast(D)) { - // If this is a method, use the type of the class. - return Method->getParent(); - } + if (const CXXMethodDecl *Method = dyn_cast(D)) { + // If this is a method, use the type of the class. + Records.push_back(Method->getParent()); + continue; + } - // 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). + // 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); + if (auto *RD = dyn_cast(D)) + Records.push_back(RD); + } + return Records; }; const SourceManager &SM = AST.getSourceManager(); - const CXXRecordDecl *Result = nullptr; + std::vector Result; auto Offset = positionToOffset(SM.getBufferData(SM.getMainFileID()), Pos); if (!Offset) { llvm::consumeError(Offset.takeError()); @@ -1754,7 +1767,7 @@ SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), *Offset, *Offset, [&](SelectionTree ST) { Result = RecordFromNode(ST.commonAncestor()); - return Result != nullptr; + return !Result.empty(); }); return Result; } @@ -1998,53 +2011,79 @@ return Result; } -llvm::Optional +std::vector getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels, TypeHierarchyDirection Direction, const SymbolIndex *Index, PathRef TUPath) { - const CXXRecordDecl *CXXRD = findRecordTypeAt(AST, Pos); - if (!CXXRD) - return llvm::None; + std::vector Results; + for (const auto *CXXRD : findRecordTypeAt(AST, Pos)) { + + bool WantChildren = Direction == TypeHierarchyDirection::Children || + Direction == TypeHierarchyDirection::Both; + + // If we're looking for children, we're doing the lookup in the index. + // The index does not store relationships between implicit + // specializations, so if we have one, use the template pattern instead. + // Note that this needs to be done before the declToTypeHierarchyItem(), + // otherwise the type hierarchy item would misleadingly contain the + // specialization parameters, while the children would involve classes + // that derive from other specializations of the template. + if (WantChildren) { + if (auto *CTSD = dyn_cast(CXXRD)) + CXXRD = CTSD->getTemplateInstantiationPattern(); + } - bool WantParents = Direction == TypeHierarchyDirection::Parents || - Direction == TypeHierarchyDirection::Both; - bool WantChildren = Direction == TypeHierarchyDirection::Children || - Direction == TypeHierarchyDirection::Both; - - // If we're looking for children, we're doing the lookup in the index. - // The index does not store relationships between implicit - // specializations, so if we have one, use the template pattern instead. - // Note that this needs to be done before the declToTypeHierarchyItem(), - // otherwise the type hierarchy item would misleadingly contain the - // specialization parameters, while the children would involve classes - // that derive from other specializations of the template. - if (WantChildren) { - if (auto *CTSD = dyn_cast(CXXRD)) - CXXRD = CTSD->getTemplateInstantiationPattern(); - } + Optional Result = + declToTypeHierarchyItem(*CXXRD, AST.tuPath()); + if (!Result) + continue; - Optional Result = - declToTypeHierarchyItem(*CXXRD, AST.tuPath()); - if (!Result) - return Result; + RecursionProtectionSet RPSet; + fillSuperTypes(*CXXRD, AST.tuPath(), *Result, RPSet); - if (WantParents) { - Result->parents.emplace(); + if (WantChildren && ResolveLevels > 0) { + Result->children.emplace(); - RecursionProtectionSet RPSet; - fillSuperTypes(*CXXRD, AST.tuPath(), *Result->parents, RPSet); + if (Index) { + if (auto ID = getSymbolID(CXXRD)) + fillSubTypes(ID, *Result->children, Index, ResolveLevels, TUPath); + } + } + Results.emplace_back(std::move(*Result)); } - if (WantChildren && ResolveLevels > 0) { - Result->children.emplace(); + return Results; +} - if (Index) { - if (auto ID = getSymbolID(CXXRD)) - fillSubTypes(ID, *Result->children, Index, ResolveLevels, TUPath); - } +llvm::Optional> +superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) { + std::vector Results; + if (!Item.data.parents) + return llvm::None; + if (Item.data.parents->empty()) + return Results; + LookupRequest Req; + llvm::DenseMap IDToData; + for (const auto &Parent : *Item.data.parents) { + Req.IDs.insert(Parent.symbolID); + IDToData[Parent.symbolID] = &Parent; } + Index->lookup(Req, [&Item, &Results, &IDToData](const Symbol &S) { + if (auto THI = symbolToTypeHierarchyItem(S, Item.uri.file())) { + THI->data = *IDToData.lookup(S.ID); + Results.emplace_back(std::move(*THI)); + } + }); + return Results; +} - return Result; +std::vector subTypes(const TypeHierarchyItem &Item, + const SymbolIndex *Index) { + std::vector Results; + fillSubTypes(Item.data.symbolID, Results, Index, 1, Item.uri.file()); + for (auto &ChildSym : Results) + ChildSym.data.parents = {Item.data}; + return Results; } void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels, @@ -2052,18 +2091,13 @@ const SymbolIndex *Index) { // We only support typeHierarchy/resolve for children, because for parents // we ignore ResolveLevels and return all levels of parents eagerly. - if (Direction == TypeHierarchyDirection::Parents || ResolveLevels == 0) + if (!Index || Direction == TypeHierarchyDirection::Parents || + ResolveLevels == 0) return; Item.children.emplace(); - - if (Index && Item.data) { - // We store the item's SymbolID in the 'data' field, and the client - // passes it back to us in typeHierarchy/resolve. - if (Expected ID = SymbolID::fromStr(*Item.data)) { - fillSubTypes(*ID, *Item.children, Index, ResolveLevels, Item.uri.file()); - } - } + fillSubTypes(Item.data.symbolID, *Item.children, Index, ResolveLevels, + Item.uri.file()); } std::vector diff --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test --- a/clang-tools-extra/clangd/test/initialize-params.test +++ b/clang-tools-extra/clangd/test/initialize-params.test @@ -88,6 +88,7 @@ # CHECK-NEXT: "," # CHECK-NEXT: ] # CHECK-NEXT: }, +# CHECK-NEXT: "standardTypeHierarchyProvider": true, # CHECK-NEXT: "textDocumentSync": { # CHECK-NEXT: "change": 2, # CHECK-NEXT: "openClose": true, diff --git a/clang-tools-extra/clangd/test/type-hierarchy.test b/clang-tools-extra/clangd/test/type-hierarchy-ext.test copy from clang-tools-extra/clangd/test/type-hierarchy.test copy to clang-tools-extra/clangd/test/type-hierarchy-ext.test --- a/clang-tools-extra/clangd/test/type-hierarchy.test +++ b/clang-tools-extra/clangd/test/type-hierarchy-ext.test @@ -9,7 +9,9 @@ # CHECK-NEXT: "result": { # CHECK-NEXT: "children": [ # CHECK-NEXT: { -# CHECK-NEXT: "data": "A6576FE083F2949A", +# CHECK-NEXT: "data": { +# CHECK-NEXT: "symbolID": "A6576FE083F2949A" +# CHECK-NEXT: }, # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Child3", # CHECK-NEXT: "range": { @@ -35,17 +37,41 @@ # CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" # CHECK-NEXT: } # CHECK-NEXT: ], -# CHECK-NEXT: "data": "8A991335E4E67D08", +# CHECK-NEXT: "data": { +# CHECK-NEXT: "parents": [ +# CHECK-NEXT: { +# CHECK-NEXT: "parents": [ +# CHECK-NEXT: { +# CHECK-NEXT: "parents": [], +# CHECK-NEXT: "symbolID": "FE546E7B648D69A7" +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "symbolID": "ECDC0C46D75120F4" +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "symbolID": "8A991335E4E67D08" +# CHECK-NEXT: }, # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Child2", # CHECK-NEXT: "parents": [ # CHECK-NEXT: { -# CHECK-NEXT: "data": "ECDC0C46D75120F4", +# CHECK-NEXT: "data": { +# CHECK-NEXT: "parents": [ +# CHECK-NEXT: { +# CHECK-NEXT: "parents": [], +# CHECK-NEXT: "symbolID": "FE546E7B648D69A7" +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "symbolID": "ECDC0C46D75120F4" +# CHECK-NEXT: }, # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Child1", # CHECK-NEXT: "parents": [ # CHECK-NEXT: { -# CHECK-NEXT: "data": "FE546E7B648D69A7", +# CHECK-NEXT: "data": { +# CHECK-NEXT: "parents": [], +# CHECK-NEXT: "symbolID": "FE546E7B648D69A7" +# CHECK-NEXT: }, # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Parent", # CHECK-NEXT: "parents": [], @@ -118,13 +144,15 @@ # CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" # CHECK-NEXT: } --- -{"jsonrpc":"2.0","id":2,"method":"typeHierarchy/resolve","params":{"item":{"uri":"test:///main.cpp","data":"A6576FE083F2949A","name":"Child3","kind":23,"range":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}},"selectionRange":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}}},"direction":0,"resolve":1}} +{"jsonrpc":"2.0","id":2,"method":"typeHierarchy/resolve","params":{"item":{"uri":"test:///main.cpp","data":{"symbolID":"A6576FE083F2949A"},"name":"Child3","kind":23,"range":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}},"selectionRange":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}}},"direction":0,"resolve":1}} # CHECK: "id": 2 # CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "result": { # CHECK-NEXT: "children": [ # CHECK-NEXT: { -# CHECK-NEXT: "data": "5705B382DFC77CBC", +# CHECK-NEXT: "data": { +# CHECK-NEXT: "symbolID": "5705B382DFC77CBC" +# CHECK-NEXT: }, # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Child4", # CHECK-NEXT: "range": { @@ -150,7 +178,9 @@ # CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" # CHECK-NEXT: } # CHECK-NEXT: ], -# CHECK-NEXT: "data": "A6576FE083F2949A", +# CHECK-NEXT: "data": { +# CHECK-NEXT: "symbolID": "A6576FE083F2949A" +# CHECK-NEXT: }, # CHECK-NEXT: "kind": 23, # CHECK-NEXT: "name": "Child3", # CHECK-NEXT: "range": { diff --git a/clang-tools-extra/clangd/test/type-hierarchy.test b/clang-tools-extra/clangd/test/type-hierarchy.test --- a/clang-tools-extra/clangd/test/type-hierarchy.test +++ b/clang-tools-extra/clangd/test/type-hierarchy.test @@ -3,178 +3,140 @@ --- {"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 {};\nstruct Child3 : Child2 {};\nstruct Child4 : Child3 {};"}}} --- -{"jsonrpc":"2.0","id":1,"method":"textDocument/typeHierarchy","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":11},"direction":2,"resolve":1}} +{"jsonrpc":"2.0","id":1,"method":"textDocument/prepareTypeHierarchy","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":11},"direction":2,"resolve":1}} # CHECK: "id": 1 # CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "children": [ -# CHECK-NEXT: { -# CHECK-NEXT: "data": "A6576FE083F2949A", -# CHECK-NEXT: "kind": 23, -# CHECK-NEXT: "name": "Child3", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 13, -# CHECK-NEXT: "line": 3 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, -# CHECK-NEXT: "line": 3 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "selectionRange": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 13, -# CHECK-NEXT: "line": 3 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, -# CHECK-NEXT: "line": 3 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "data": "8A991335E4E67D08", -# CHECK-NEXT: "kind": 23, -# CHECK-NEXT: "name": "Child2", -# CHECK-NEXT: "parents": [ -# CHECK-NEXT: { -# CHECK-NEXT: "data": "ECDC0C46D75120F4", -# CHECK-NEXT: "kind": 23, -# CHECK-NEXT: "name": "Child1", -# CHECK-NEXT: "parents": [ +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "data": { +# CHECK-NEXT: "parents": [ # CHECK-NEXT: { -# CHECK-NEXT: "data": "FE546E7B648D69A7", -# CHECK-NEXT: "kind": 23, -# CHECK-NEXT: "name": "Parent", -# CHECK-NEXT: "parents": [], -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 16, -# 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: "parents": [ +# CHECK-NEXT: { +# CHECK-NEXT: "parents": [], +# CHECK-NEXT: "symbolID": "FE546E7B648D69A7" +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "symbolID": "ECDC0C46D75120F4" # CHECK-NEXT: } # CHECK-NEXT: ], -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 25, -# 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": 25, -# 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: } +# CHECK-NEXT: "symbolID": "8A991335E4E67D08" +# CHECK-NEXT: }, +# CHECK-NEXT: "kind": 23, +# CHECK-NEXT: "name": "Child2", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 25, +# 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: } +# CHECK-NEXT: ] --- -{"jsonrpc":"2.0","id":2,"method":"typeHierarchy/resolve","params":{"item":{"uri":"test:///main.cpp","data":"A6576FE083F2949A","name":"Child3","kind":23,"range":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}},"selectionRange":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}}},"direction":0,"resolve":1}} +{"jsonrpc":"2.0","id":2,"method":"typeHierarchy/supertypes","params":{"item":{"uri":"test:///main.cpp","data":{"parents":[{"parents":[{"parents":[],"symbolID":"FE546E7B648D69A7"}],"symbolID":"ECDC0C46D75120F4"}],"symbolID":"8A991335E4E67D08"},"name":"Child2","kind":23,"range":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}},"selectionRange":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}}}}} # CHECK: "id": 2 # CHECK-NEXT: "jsonrpc": "2.0", -# CHECK-NEXT: "result": { -# CHECK-NEXT: "children": [ -# CHECK-NEXT: { -# CHECK-NEXT: "data": "5705B382DFC77CBC", -# CHECK-NEXT: "kind": 23, -# CHECK-NEXT: "name": "Child4", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 13, -# CHECK-NEXT: "line": 4 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, -# CHECK-NEXT: "line": 4 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "selectionRange": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 13, -# CHECK-NEXT: "line": 4 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, -# CHECK-NEXT: "line": 4 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" -# CHECK-NEXT: } -# CHECK-NEXT: ], -# CHECK-NEXT: "data": "A6576FE083F2949A", -# CHECK-NEXT: "kind": 23, -# CHECK-NEXT: "name": "Child3", -# CHECK-NEXT: "range": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 13, -# CHECK-NEXT: "line": 3 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, -# CHECK-NEXT: "line": 3 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "selectionRange": { -# CHECK-NEXT: "end": { -# CHECK-NEXT: "character": 13, -# CHECK-NEXT: "line": 3 -# CHECK-NEXT: }, -# CHECK-NEXT: "start": { -# CHECK-NEXT: "character": 7, -# CHECK-NEXT: "line": 3 -# CHECK-NEXT: } -# CHECK-NEXT: }, -# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" -# CHECK-NEXT: } +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "data": { +# CHECK-NEXT: "parents": [ +# CHECK-NEXT: { +# CHECK-NEXT: "parents": [], +# CHECK-NEXT: "symbolID": "FE546E7B648D69A7" +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "symbolID": "ECDC0C46D75120F4" +# CHECK-NEXT: }, +# CHECK-NEXT: "kind": 23, +# CHECK-NEXT: "name": "Child1", +# CHECK-NEXT: "range": { +# 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: "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: ] +--- +{"jsonrpc":"2.0","id":2,"method":"typeHierarchy/subtypes","params":{"item":{"uri":"test:///main.cpp","data":{"parents":[{"parents":[{"parents":[],"symbolID":"FE546E7B648D69A7"}],"symbolID":"ECDC0C46D75120F4"}],"symbolID":"8A991335E4E67D08"},"name":"Child2","kind":23,"range":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}},"selectionRange":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}}}}} +# CHECK: "id": 2 +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "data": { +# CHECK-NEXT: "parents": [ +# CHECK-NEXT: { +# CHECK-NEXT: "parents": [ +# CHECK-NEXT: { +# CHECK-NEXT: "parents": [ +# CHECK-NEXT: { +# CHECK-NEXT: "parents": [], +# CHECK-NEXT: "symbolID": "FE546E7B648D69A7" +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "symbolID": "ECDC0C46D75120F4" +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "symbolID": "8A991335E4E67D08" +# CHECK-NEXT: } +# CHECK-NEXT: ], +# CHECK-NEXT: "symbolID": "A6576FE083F2949A" +# CHECK-NEXT: }, +# CHECK-NEXT: "kind": 23, +# CHECK-NEXT: "name": "Child3", +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "line": 3 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "line": 3 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "selectionRange": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": 13, +# CHECK-NEXT: "line": 3 +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": 7, +# CHECK-NEXT: "line": 3 +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" +# CHECK-NEXT: } +# CHECK-NEXT: ] --- {"jsonrpc":"2.0","id":3,"method":"shutdown"} --- diff --git a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp --- a/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp +++ b/clang-tools-extra/clangd/unittests/TypeHierarchyTests.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// +#include "AST.h" #include "Annotations.h" #include "Matchers.h" #include "ParsedAST.h" @@ -16,6 +17,7 @@ #include "llvm/Support/Path.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +#include namespace clang { namespace clangd { @@ -26,6 +28,7 @@ using ::testing::Field; using ::testing::IsEmpty; using ::testing::Matcher; +using ::testing::SizeIs; using ::testing::UnorderedElementsAre; // GMock helpers for matching TypeHierarchyItem. @@ -45,6 +48,10 @@ // Note: "not resolved" is different from "resolved but empty"! MATCHER(parentsNotResolved, "") { return !arg.parents; } MATCHER(childrenNotResolved, "") { return !arg.children; } +MATCHER_P(withResolveID, SID, "") { return arg.symbolID.str() == SID; } +MATCHER_P(withResolveParents, M, "") { + return testing::ExplainMatchResult(M, arg.data.parents, result_listener); +} TEST(FindRecordTypeAt, TypeOrVariable) { Annotations Source(R"cpp( @@ -64,8 +71,10 @@ auto AST = TU.build(); for (Position Pt : Source.points()) { - const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt); - EXPECT_EQ(&findDecl(AST, "Child2"), static_cast(RD)); + auto Records = findRecordTypeAt(AST, Pt); + ASSERT_THAT(Records, SizeIs(1)); + EXPECT_EQ(&findDecl(AST, "Child2"), + static_cast(Records.front())); } } @@ -86,8 +95,10 @@ auto AST = TU.build(); for (Position Pt : Source.points()) { - const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt); - EXPECT_EQ(&findDecl(AST, "Child2"), static_cast(RD)); + auto Records = findRecordTypeAt(AST, Pt); + ASSERT_THAT(Records, SizeIs(1)); + EXPECT_EQ(&findDecl(AST, "Child2"), + static_cast(Records.front())); } } @@ -107,11 +118,10 @@ auto AST = TU.build(); 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); + EXPECT_THAT(findRecordTypeAt(AST, Pt), SizeIs(0)); } } @@ -359,11 +369,11 @@ 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)); + auto Result = getTypeHierarchy(AST, Pt, /*ResolveLevels=*/0, + TypeHierarchyDirection::Parents); + ASSERT_THAT(Result, SizeIs(1)); EXPECT_THAT( - *Result, + Result.front(), AllOf( withName("Child"), withKind(SymbolKind::Struct), parents(AllOf(withName("Parent1"), withKind(SymbolKind::Struct), @@ -398,11 +408,11 @@ // The parent is reported as "S" because "S<0>" is an invalid instantiation. // We then iterate once more and find "S" again before detecting the // recursion. - llvm::Optional Result = getTypeHierarchy( - AST, Source.points()[0], 0, TypeHierarchyDirection::Parents); - ASSERT_TRUE(bool(Result)); + auto Result = getTypeHierarchy(AST, Source.points()[0], 0, + TypeHierarchyDirection::Parents); + ASSERT_THAT(Result, SizeIs(1)); EXPECT_THAT( - *Result, + Result.front(), AllOf(withName("S<0>"), withKind(SymbolKind::Struct), parents( AllOf(withName("S"), withKind(SymbolKind::Struct), @@ -432,11 +442,11 @@ // Make sure getTypeHierarchy() doesn't get into an infinite recursion // for either a concrete starting point or a dependent starting point. - llvm::Optional Result = getTypeHierarchy( - AST, Source.point("SRefConcrete"), 0, TypeHierarchyDirection::Parents); - ASSERT_TRUE(bool(Result)); + auto Result = getTypeHierarchy(AST, Source.point("SRefConcrete"), 0, + TypeHierarchyDirection::Parents); + ASSERT_THAT(Result, SizeIs(1)); EXPECT_THAT( - *Result, + Result.front(), AllOf(withName("S<2>"), withKind(SymbolKind::Struct), parents(AllOf( withName("S<1>"), withKind(SymbolKind::Struct), @@ -445,9 +455,9 @@ parents())))))); Result = getTypeHierarchy(AST, Source.point("SRefDependent"), 0, TypeHierarchyDirection::Parents); - ASSERT_TRUE(bool(Result)); + ASSERT_THAT(Result, SizeIs(1)); EXPECT_THAT( - *Result, + Result.front(), AllOf(withName("S"), withKind(SymbolKind::Struct), parents(AllOf(withName("S"), withKind(SymbolKind::Struct), selectionRangeIs(Source.range("SDef")), parents())))); @@ -469,11 +479,11 @@ auto AST = TU.build(); auto Index = TU.index(); - llvm::Optional Result = getTypeHierarchy( - AST, Source.points()[0], 2, TypeHierarchyDirection::Children, Index.get(), - testPath(TU.Filename)); - ASSERT_TRUE(bool(Result)); - EXPECT_THAT(*Result, + auto Result = getTypeHierarchy(AST, Source.points()[0], 2, + TypeHierarchyDirection::Children, Index.get(), + testPath(TU.Filename)); + ASSERT_THAT(Result, SizeIs(1)); + EXPECT_THAT(Result.front(), AllOf(withName("Parent"), withKind(SymbolKind::Struct), children(AllOf(withName("Child1"), withKind(SymbolKind::Struct), children()), @@ -495,12 +505,12 @@ auto AST = TU.build(); auto Index = TU.index(); - llvm::Optional Result = getTypeHierarchy( - AST, Source.points()[0], 2, TypeHierarchyDirection::Children, Index.get(), - testPath(TU.Filename)); - ASSERT_TRUE(bool(Result)); - EXPECT_THAT(*Result, AllOf(withName("Parent"), withKind(SymbolKind::Struct), - children())); + auto Result = getTypeHierarchy(AST, Source.points()[0], 2, + TypeHierarchyDirection::Children, Index.get(), + testPath(TU.Filename)); + ASSERT_THAT(Result, SizeIs(1)); + EXPECT_THAT(Result.front(), AllOf(withName("Parent"), + withKind(SymbolKind::Struct), children())); } TEST(TypeHierarchy, DeriveFromTemplate) { @@ -521,11 +531,11 @@ // FIXME: We'd like this to show the implicit specializations Parent // and Child, but currently libIndex does not expose relationships // between implicit specializations. - llvm::Optional Result = getTypeHierarchy( - AST, Source.points()[0], 2, TypeHierarchyDirection::Children, Index.get(), - testPath(TU.Filename)); - ASSERT_TRUE(bool(Result)); - EXPECT_THAT(*Result, + auto Result = getTypeHierarchy(AST, Source.points()[0], 2, + TypeHierarchyDirection::Children, Index.get(), + testPath(TU.Filename)); + ASSERT_THAT(Result, SizeIs(1)); + EXPECT_THAT(Result.front(), AllOf(withName("Parent"), withKind(SymbolKind::Struct), children(AllOf(withName("Child"), withKind(SymbolKind::Struct), children())))); @@ -546,12 +556,12 @@ TU.HeaderCode = HeaderInPreambleAnnotations.code().str(); auto AST = TU.build(); - llvm::Optional Result = getTypeHierarchy( + std::vector Result = getTypeHierarchy( AST, SourceAnnotations.point(), 1, TypeHierarchyDirection::Parents); - ASSERT_TRUE(Result); + ASSERT_THAT(Result, SizeIs(1)); EXPECT_THAT( - *Result, + Result.front(), AllOf(withName("Child"), parents(AllOf(withName("Parent"), selectionRangeIs(HeaderInPreambleAnnotations.range()), @@ -722,22 +732,21 @@ auto AST = TU.build(); auto Index = TU.index(); - llvm::Optional Result = getTypeHierarchy( - AST, Source.point(), /*ResolveLevels=*/1, - TypeHierarchyDirection::Children, Index.get(), testPath(TU.Filename)); - ASSERT_TRUE(bool(Result)); + auto Result = getTypeHierarchy(AST, Source.point(), /*ResolveLevels=*/1, + TypeHierarchyDirection::Children, Index.get(), + testPath(TU.Filename)); + ASSERT_THAT(Result, SizeIs(1)); EXPECT_THAT( - *Result, - AllOf(withName("Parent"), withKind(SymbolKind::Struct), - parentsNotResolved(), + Result.front(), + AllOf(withName("Parent"), withKind(SymbolKind::Struct), parents(), children(AllOf(withName("Child1"), withKind(SymbolKind::Struct), parentsNotResolved(), childrenNotResolved())))); - resolveTypeHierarchy((*Result->children)[0], /*ResolveLevels=*/1, + resolveTypeHierarchy((*Result.front().children)[0], /*ResolveLevels=*/1, TypeHierarchyDirection::Children, Index.get()); EXPECT_THAT( - (*Result->children)[0], + (*Result.front().children)[0], AllOf(withName("Child1"), withKind(SymbolKind::Struct), parentsNotResolved(), children(AllOf(withName("Child2a"), withKind(SymbolKind::Struct), @@ -746,6 +755,53 @@ parentsNotResolved(), childrenNotResolved())))); } +TEST(Standard, SubTypes) { + Annotations Source(R"cpp( +struct Pare^nt1 {}; +struct Parent2 {}; +struct Child : Parent1, Parent2 {}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + auto Index = TU.index(); + + auto Result = getTypeHierarchy(AST, Source.point(), /*ResolveLevels=*/1, + TypeHierarchyDirection::Children, Index.get(), + testPath(TU.Filename)); + ASSERT_THAT(Result, SizeIs(1)); + auto Children = subTypes(Result.front(), Index.get()); + + // Make sure parents are populated when getting children. + // FIXME: This is partial. + EXPECT_THAT( + Children, + UnorderedElementsAre( + AllOf(withName("Child"), + withResolveParents(HasValue(UnorderedElementsAre(withResolveID( + getSymbolID(&findDecl(AST, "Parent1")).str()))))))); +} + +TEST(Standard, SuperTypes) { + Annotations Source(R"cpp( +struct Parent {}; +struct Chil^d : Parent {}; +)cpp"); + + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + auto Index = TU.index(); + + auto Result = getTypeHierarchy(AST, Source.point(), /*ResolveLevels=*/1, + TypeHierarchyDirection::Children, Index.get(), + testPath(TU.Filename)); + ASSERT_THAT(Result, SizeIs(1)); + auto Parents = superTypes(Result.front(), Index.get()); + + EXPECT_THAT(Parents, HasValue(UnorderedElementsAre( + AllOf(withName("Parent"), + withResolveParents(HasValue(IsEmpty())))))); +} } // namespace } // namespace clangd } // namespace clang