mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2025-02-13 22:00:14 +00:00
[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 llvm-svn: 356445
This commit is contained in:
parent
ad78768d59
commit
8665802202
@ -368,6 +368,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
|
||||
{ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND,
|
||||
ExecuteCommandParams::CLANGD_APPLY_TWEAK}},
|
||||
}},
|
||||
{"typeHierarchyProvider", true},
|
||||
}}}});
|
||||
}
|
||||
|
||||
@ -806,6 +807,13 @@ void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params,
|
||||
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 Transport &Transp,
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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>>);
|
||||
|
@ -362,9 +362,8 @@ void ClangdServer::enumerateTweaks(PathRef File, Range Sel,
|
||||
|
||||
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 File, Position Pos,
|
||||
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);
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
using ScoredSymbolInfo = std::pair<float, SymbolInformation>;
|
||||
struct ScoredSymbolGreater {
|
||||
bool operator()(const ScoredSymbolInfo &L, const ScoredSymbolInfo &R) {
|
||||
|
@ -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).
|
||||
|
@ -211,6 +211,61 @@ SymbolKind adjustKindToCapability(SymbolKind Kind,
|
||||
}
|
||||
}
|
||||
|
||||
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 &Params, InitializationOptions &Opts) {
|
||||
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);
|
||||
|
@ -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 &, SymbolKindBitset &);
|
||||
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.
|
||||
};
|
||||
|
@ -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_ostream &OS, const LocatedSymbol &S) {
|
||||
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
|
||||
|
@ -58,6 +58,17 @@ std::vector<Location> findReferences(ParsedAST &AST, Position Pos,
|
||||
/// 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
|
||||
|
||||
|
@ -326,8 +326,8 @@ bool SymbolCollector::handleDeclOccurence(
|
||||
|
||||
// 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();
|
||||
|
@ -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.
|
||||
|
@ -40,6 +40,7 @@
|
||||
# CHECK-NEXT: ]
|
||||
# CHECK-NEXT: },
|
||||
# CHECK-NEXT: "textDocumentSync": 2,
|
||||
# CHECK-NEXT: "typeHierarchyProvider": true
|
||||
# CHECK-NEXT: "workspaceSymbolProvider": true
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: }
|
||||
|
92
clang-tools-extra/test/clangd/type-hierarchy.test
Normal file
92
clang-tools-extra/test/clangd/type-hierarchy.test
Normal file
@ -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"}
|
@ -46,6 +46,7 @@ add_extra_unittest(ClangdTests
|
||||
TestTU.cpp
|
||||
ThreadingTests.cpp
|
||||
TraceTests.cpp
|
||||
TypeHierarchyTests.cpp
|
||||
TweakTests.cpp
|
||||
URITests.cpp
|
||||
XRefsTests.cpp
|
||||
|
@ -127,6 +127,73 @@ PolySubsequenceMatcher<Args...> HasSubsequence(Args &&... M) {
|
||||
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
|
||||
|
462
clang-tools-extra/unittests/clangd/TypeHierarchyTests.cpp
Normal file
462
clang-tools-extra/unittests/clangd/TypeHierarchyTests.cpp
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user