[clang-tools-extra] r356445 - [clangd] Add support for type hierarchy (super types only for now)
Kadir Cetinkaya via cfe-commits
cfe-commits at lists.llvm.org
Tue Mar 19 02:27:04 PDT 2019
Author: kadircet
Date: Tue Mar 19 02:27:04 2019
New Revision: 356445
URL: http://llvm.org/viewvc/llvm-project?rev=356445&view=rev
Log:
[clangd] Add support for type hierarchy (super types only for now)
Summary:
Patch by Nathan Ridge(@nridge)!
This is an LSP extension proposed here:
https://github.com/Microsoft/vscode-languageserver-node/pull/426
An example client implementation can be found here:
https://github.com/theia-ide/theia/pull/3802
Reviewers: kadircet, sammccall
Reviewed By: kadircet
Subscribers: jdoerfert, sammccall, cfe-commits, mgorny, dschaefer, simark, ilya-biryukov, ioeric, MaskRay, jkorous, arphaman, kadircet
Tags: #clang
Differential Revision: https://reviews.llvm.org/D56370
Added:
clang-tools-extra/trunk/test/clangd/type-hierarchy.test
clang-tools-extra/trunk/unittests/clangd/TypeHierarchyTests.cpp
Modified:
clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
clang-tools-extra/trunk/clangd/ClangdLSPServer.h
clang-tools-extra/trunk/clangd/ClangdServer.cpp
clang-tools-extra/trunk/clangd/ClangdServer.h
clang-tools-extra/trunk/clangd/FindSymbols.cpp
clang-tools-extra/trunk/clangd/FindSymbols.h
clang-tools-extra/trunk/clangd/Protocol.cpp
clang-tools-extra/trunk/clangd/Protocol.h
clang-tools-extra/trunk/clangd/XRefs.cpp
clang-tools-extra/trunk/clangd/XRefs.h
clang-tools-extra/trunk/clangd/index/SymbolCollector.cpp
clang-tools-extra/trunk/clangd/index/SymbolCollector.h
clang-tools-extra/trunk/test/clangd/initialize-params.test
clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt
clang-tools-extra/trunk/unittests/clangd/Matchers.h
Modified: clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp?rev=356445&r1=356444&r2=356445&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp (original)
+++ clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp Tue Mar 19 02:27:04 2019
@@ -368,6 +368,7 @@ void ClangdLSPServer::onInitialize(const
{ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND,
ExecuteCommandParams::CLANGD_APPLY_TWEAK}},
}},
+ {"typeHierarchyProvider", true},
}}}});
}
@@ -806,6 +807,13 @@ void ClangdLSPServer::onHover(const Text
std::move(Reply));
}
+void ClangdLSPServer::onTypeHierarchy(
+ const TypeHierarchyParams &Params,
+ Callback<Optional<TypeHierarchyItem>> 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 @@ ClangdLSPServer::ClangdLSPServer(class T
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
}
Modified: clang-tools-extra/trunk/clangd/ClangdLSPServer.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdLSPServer.h?rev=356445&r1=356444&r2=356445&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdLSPServer.h (original)
+++ clang-tools-extra/trunk/clangd/ClangdLSPServer.h Tue Mar 19 02:27:04 2019
@@ -93,6 +93,8 @@ private:
void onRename(const RenameParams &, Callback<WorkspaceEdit>);
void onHover(const TextDocumentPositionParams &,
Callback<llvm::Optional<Hover>>);
+ void onTypeHierarchy(const TypeHierarchyParams &,
+ Callback<llvm::Optional<TypeHierarchyItem>>);
void onChangeConfiguration(const DidChangeConfigurationParams &);
void onSymbolInfo(const TextDocumentPositionParams &,
Callback<std::vector<SymbolDetails>>);
Modified: clang-tools-extra/trunk/clangd/ClangdServer.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdServer.cpp?rev=356445&r1=356444&r2=356445&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdServer.cpp (original)
+++ clang-tools-extra/trunk/clangd/ClangdServer.cpp Tue Mar 19 02:27:04 2019
@@ -362,9 +362,8 @@ void ClangdServer::enumerateTweaks(PathR
void ClangdServer::applyTweak(PathRef File, Range Sel, StringRef TweakID,
Callback<tooling::Replacements> CB) {
- auto Action = [Sel](decltype(CB) CB, std::string File,
- std::string TweakID,
- Expected<InputsAndAST> InpAST) {
+ auto Action = [Sel](decltype(CB) CB, std::string File, std::string TweakID,
+ Expected<InputsAndAST> InpAST) {
if (!InpAST)
return CB(InpAST.takeError());
auto Selection = tweakSelection(Sel, *InpAST);
@@ -523,6 +522,19 @@ void ClangdServer::findHover(PathRef Fil
WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB)));
}
+void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve,
+ TypeHierarchyDirection Direction,
+ Callback<Optional<TypeHierarchyItem>> CB) {
+ auto Action = [Pos, Resolve, Direction](decltype(CB) CB,
+ Expected<InputsAndAST> 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<tooling::CompileCommand> C = CDB.getCompileCommand(File);
Modified: clang-tools-extra/trunk/clangd/ClangdServer.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdServer.h?rev=356445&r1=356444&r2=356445&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdServer.h (original)
+++ clang-tools-extra/trunk/clangd/ClangdServer.h Tue Mar 19 02:27:04 2019
@@ -184,6 +184,11 @@ public:
void findHover(PathRef File, Position Pos,
Callback<llvm::Optional<Hover>> CB);
+ /// Get information about type hierarchy for a given position.
+ void typeHierarchy(PathRef File, Position Pos, int Resolve,
+ TypeHierarchyDirection Direction,
+ Callback<llvm::Optional<TypeHierarchyItem>> CB);
+
/// Retrieve the top symbols from the workspace matching a query.
void workspaceSymbols(StringRef Query, int Limit,
Callback<std::vector<SymbolInformation>> CB);
Modified: clang-tools-extra/trunk/clangd/FindSymbols.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/FindSymbols.cpp?rev=356445&r1=356444&r2=356445&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/FindSymbols.cpp (original)
+++ clang-tools-extra/trunk/clangd/FindSymbols.cpp Tue Mar 19 02:27:04 2019
@@ -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<float, SymbolInformation>;
struct ScoredSymbolGreater {
bool operator()(const ScoredSymbolInfo &L, const ScoredSymbolInfo &R) {
Modified: clang-tools-extra/trunk/clangd/FindSymbols.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/FindSymbols.h?rev=356445&r1=356444&r2=356445&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/FindSymbols.h (original)
+++ clang-tools-extra/trunk/clangd/FindSymbols.h Tue Mar 19 02:27:04 2019
@@ -21,9 +21,10 @@ class ParsedAST;
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).
Modified: clang-tools-extra/trunk/clangd/Protocol.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.cpp?rev=356445&r1=356444&r2=356445&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.cpp (original)
+++ clang-tools-extra/trunk/clangd/Protocol.cpp Tue Mar 19 02:27:04 2019
@@ -211,6 +211,61 @@ SymbolKind adjustKindToCapability(Symbol
}
}
+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 @@ bool fromJSON(const llvm::json::Value &P
return true;
}
+bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out) {
+ auto T = E.getAsInteger();
+ if (!T)
+ return false;
+ if (*T < static_cast<int>(TypeHierarchyDirection::Children) ||
+ *T > static_cast<int>(TypeHierarchyDirection::Both))
+ return false;
+ Out = static_cast<TypeHierarchyDirection>(*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<int>(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);
Modified: clang-tools-extra/trunk/clangd/Protocol.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/Protocol.h?rev=356445&r1=356444&r2=356445&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/Protocol.h (original)
+++ clang-tools-extra/trunk/clangd/Protocol.h Tue Mar 19 02:27:04 2019
@@ -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 <bitset>
@@ -331,6 +332,12 @@ bool fromJSON(const llvm::json::Value &,
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 @@ struct DocumentHighlight {
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<std::string> 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<std::vector<TypeHierarchyItem>> 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<std::vector<TypeHierarchyItem>> 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.
};
Modified: clang-tools-extra/trunk/clangd/XRefs.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/XRefs.cpp?rev=356445&r1=356444&r2=356445&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/XRefs.cpp (original)
+++ clang-tools-extra/trunk/clangd/XRefs.cpp Tue Mar 19 02:27:04 2019
@@ -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 @@ llvm::raw_ostream &operator<<(llvm::raw_
return OS;
}
+// FIXME(nridge): Reduce duplication between this function and declToSym().
+static llvm::Optional<TypeHierarchyItem>
+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<TypeHierarchyItem> getTypeAncestors(const CXXRecordDecl &CXXRD,
+ ASTContext &ASTCtx) {
+ Optional<TypeHierarchyItem> Result = declToTypeHierarchyItem(ASTCtx, CXXRD);
+ if (!Result)
+ return Result;
+
+ Result->parents.emplace();
+
+ for (const CXXRecordDecl *ParentDecl : typeParents(&CXXRD)) {
+ if (Optional<TypeHierarchyItem> 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<VarDecl>(D)) {
+ // If this is a variable, use the type of the variable.
+ return VD->getType().getTypePtr()->getAsCXXRecordDecl();
+ }
+
+ if (const CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(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<CXXRecordDecl>(D);
+}
+
+std::vector<const CXXRecordDecl *> typeParents(const CXXRecordDecl *CXXRD) {
+ std::vector<const CXXRecordDecl *> Result;
+
+ for (auto Base : CXXRD->bases()) {
+ const CXXRecordDecl *ParentDecl = nullptr;
+
+ const Type *Type = Base.getType().getTypePtr();
+ if (const RecordType *RT = Type->getAs<RecordType>()) {
+ ParentDecl = RT->getAsCXXRecordDecl();
+ }
+
+ // For now, do not handle dependent bases such as "Base<T>".
+ // 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<TypeHierarchyItem>
+getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels,
+ TypeHierarchyDirection Direction) {
+ const CXXRecordDecl *CXXRD = findRecordTypeAt(AST, Pos);
+ if (!CXXRD)
+ return llvm::None;
+
+ Optional<TypeHierarchyItem> 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
Modified: clang-tools-extra/trunk/clangd/XRefs.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/XRefs.h?rev=356445&r1=356444&r2=356445&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/XRefs.h (original)
+++ clang-tools-extra/trunk/clangd/XRefs.h Tue Mar 19 02:27:04 2019
@@ -58,6 +58,17 @@ std::vector<Location> findReferences(Par
/// Get info about symbols at \p Pos.
std::vector<SymbolDetails> 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<const CXXRecordDecl *> typeParents(const CXXRecordDecl *CXXRD);
+
+/// Get type hierarchy information at \p Pos.
+llvm::Optional<TypeHierarchyItem>
+getTypeHierarchy(ParsedAST &AST, Position Pos, int Resolve,
+ TypeHierarchyDirection Direction);
+
} // namespace clangd
} // namespace clang
Modified: clang-tools-extra/trunk/clangd/index/SymbolCollector.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/index/SymbolCollector.cpp?rev=356445&r1=356444&r2=356445&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/index/SymbolCollector.cpp (original)
+++ clang-tools-extra/trunk/clangd/index/SymbolCollector.cpp Tue Mar 19 02:27:04 2019
@@ -326,8 +326,8 @@ bool SymbolCollector::handleDeclOccurenc
// 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 @@ void SymbolCollector::finish() {
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();
Modified: clang-tools-extra/trunk/clangd/index/SymbolCollector.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/index/SymbolCollector.h?rev=356445&r1=356444&r2=356445&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/index/SymbolCollector.h (original)
+++ clang-tools-extra/trunk/clangd/index/SymbolCollector.h Tue Mar 19 02:27:04 2019
@@ -113,7 +113,8 @@ public:
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.
Modified: clang-tools-extra/trunk/test/clangd/initialize-params.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/initialize-params.test?rev=356445&r1=356444&r2=356445&view=diff
==============================================================================
--- clang-tools-extra/trunk/test/clangd/initialize-params.test (original)
+++ clang-tools-extra/trunk/test/clangd/initialize-params.test Tue Mar 19 02:27:04 2019
@@ -40,6 +40,7 @@
# CHECK-NEXT: ]
# CHECK-NEXT: },
# CHECK-NEXT: "textDocumentSync": 2,
+# CHECK-NEXT: "typeHierarchyProvider": true
# CHECK-NEXT: "workspaceSymbolProvider": true
# CHECK-NEXT: }
# CHECK-NEXT: }
Added: clang-tools-extra/trunk/test/clangd/type-hierarchy.test
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/test/clangd/type-hierarchy.test?rev=356445&view=auto
==============================================================================
--- clang-tools-extra/trunk/test/clangd/type-hierarchy.test (added)
+++ clang-tools-extra/trunk/test/clangd/type-hierarchy.test Tue Mar 19 02:27:04 2019
@@ -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"}
Modified: clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt?rev=356445&r1=356444&r2=356445&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/unittests/clangd/CMakeLists.txt Tue Mar 19 02:27:04 2019
@@ -46,6 +46,7 @@ add_extra_unittest(ClangdTests
TestTU.cpp
ThreadingTests.cpp
TraceTests.cpp
+ TypeHierarchyTests.cpp
TweakTests.cpp
URITests.cpp
XRefsTests.cpp
Modified: clang-tools-extra/trunk/unittests/clangd/Matchers.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/Matchers.h?rev=356445&r1=356444&r2=356445&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/Matchers.h (original)
+++ clang-tools-extra/trunk/unittests/clangd/Matchers.h Tue Mar 19 02:27:04 2019
@@ -127,6 +127,73 @@ PolySubsequenceMatcher<Args...> HasSubse
llvm::consumeError(ComputedValue.takeError()); \
} while (false)
+// Implements the HasValue(m) matcher for matching an Optional whose
+// value matches matcher m.
+template <typename InnerMatcher> 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 <typename Optional> operator Matcher<Optional>() const {
+ return MakeMatcher(new Impl<Optional>(matcher_));
+ }
+
+private:
+ // The monomorphic implementation that works for a particular optional type.
+ template <typename Optional>
+ class Impl : public ::testing::MatcherInterface<Optional> {
+ public:
+ using Value = typename std::remove_const<
+ typename std::remove_reference<Optional>::type>::type::value_type;
+
+ explicit Impl(const InnerMatcher &matcher)
+ : matcher_(::testing::MatcherCast<const Value &>(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<const Value &> 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 <typename InnerMatcher>
+inline OptionalMatcher<InnerMatcher>
+HasValue(const InnerMatcher &inner_matcher) {
+ return OptionalMatcher<InnerMatcher>(inner_matcher);
+}
+
} // namespace clangd
} // namespace clang
#endif
Added: clang-tools-extra/trunk/unittests/clangd/TypeHierarchyTests.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/TypeHierarchyTests.cpp?rev=356445&view=auto
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/TypeHierarchyTests.cpp (added)
+++ clang-tools-extra/trunk/unittests/clangd/TypeHierarchyTests.cpp Tue Mar 19 02:27:04 2019
@@ -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 <class... ParentMatchers>
+testing::Matcher<TypeHierarchyItem> 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<const NamedDecl *>(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<const NamedDecl *>(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<CXXRecordDecl>(&findDecl(AST, "Parent"));
+ const CXXRecordDecl *Child1 =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Child1"));
+ const CXXRecordDecl *Child2 =
+ dyn_cast<CXXRecordDecl>(&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<CXXRecordDecl>(&findDecl(AST, "Parent1"));
+ const CXXRecordDecl *Parent2 =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent2"));
+ const CXXRecordDecl *Parent3 =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent3"));
+ const CXXRecordDecl *Child = dyn_cast<CXXRecordDecl>(&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 <typename T>
+struct Child : Parent {};
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ const CXXRecordDecl *Parent =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent"));
+ const CXXRecordDecl *Child =
+ dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Child"))->getTemplatedDecl();
+
+ EXPECT_THAT(typeParents(Child), ElementsAre(Parent));
+}
+
+MATCHER_P(ImplicitSpecOf, ClassTemplate, "") {
+ const ClassTemplateSpecializationDecl *CTS =
+ dyn_cast<ClassTemplateSpecializationDecl>(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 <typename T>
+struct Parent {};
+
+template <>
+struct Parent<int> {};
+
+struct Child1 : Parent<float> {};
+
+struct Child2 : Parent<int> {};
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ const CXXRecordDecl *Parent =
+ dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Parent"))->getTemplatedDecl();
+ const CXXRecordDecl *ParentSpec =
+ dyn_cast<CXXRecordDecl>(&findDeclWithTemplateArgs(AST, "Parent<int>"));
+ const CXXRecordDecl *Child1 =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Child1"));
+ const CXXRecordDecl *Child2 =
+ dyn_cast<CXXRecordDecl>(&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 <typename T>
+struct Child {};
+
+template <>
+struct Child<int> : Parent {};
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ const CXXRecordDecl *Parent =
+ dyn_cast<CXXRecordDecl>(&findDecl(AST, "Parent"));
+ const CXXRecordDecl *Child =
+ dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Child"))->getTemplatedDecl();
+ const CXXRecordDecl *ChildSpec =
+ dyn_cast<CXXRecordDecl>(&findDeclWithTemplateArgs(AST, "Child<int>"));
+
+ 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 <typename T>
+struct Parent {};
+
+template <typename T>
+struct Child1 : Parent<T> {};
+
+template <typename T>
+struct Child2 : Parent<T>::Type {};
+
+template <typename T>
+struct Child3 : T {};
+)cpp");
+
+ TestTU TU = TestTU::withCode(Source.code());
+ auto AST = TU.build();
+
+ ASSERT_TRUE(AST.getDiagnostics().empty());
+
+ const CXXRecordDecl *Parent =
+ dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Parent"))->getTemplatedDecl();
+ const CXXRecordDecl *Child1 =
+ dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Child1"))->getTemplatedDecl();
+ const CXXRecordDecl *Child2 =
+ dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Child2"))->getTemplatedDecl();
+ const CXXRecordDecl *Child3 =
+ dyn_cast<ClassTemplateDecl>(&findDecl(AST, "Child3"))->getTemplatedDecl();
+
+ // For "Parent<T>", use the primary template as a best-effort guess.
+ EXPECT_THAT(typeParents(Child1), ElementsAre(Parent));
+ // For "Parent<T>::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<TypeHierarchyItem> 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 <int N>
+ struct S : S<N + 1> {};
+
+ 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<TypeHierarchyItem> 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 <int N>
+ struct S : S<N - 1> {};
+
+ 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<TypeHierarchyItem> 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 <int N>
+ struct S : S<N - 1> {};
+
+ template <>
+ struct S<0>{};
+
+ template <int N>
+ struct Foo {
+ S^<N> 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<TypeHierarchyItem> 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
More information about the cfe-commits
mailing list