[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:
Kadir Cetinkaya 2019-03-19 09:27:04 +00:00
parent ad78768d59
commit 8665802202
17 changed files with 991 additions and 71 deletions

View File

@ -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
}

View File

@ -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>>);

View File

@ -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);

View 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);

View File

@ -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) {

View File

@ -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).

View File

@ -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);

View File

@ -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.
};

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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.

View File

@ -40,6 +40,7 @@
# CHECK-NEXT: ]
# CHECK-NEXT: },
# CHECK-NEXT: "textDocumentSync": 2,
# CHECK-NEXT: "typeHierarchyProvider": true
# CHECK-NEXT: "workspaceSymbolProvider": true
# CHECK-NEXT: }
# CHECK-NEXT: }

View 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"}

View File

@ -46,6 +46,7 @@ add_extra_unittest(ClangdTests
TestTU.cpp
ThreadingTests.cpp
TraceTests.cpp
TypeHierarchyTests.cpp
TweakTests.cpp
URITests.cpp
XRefsTests.cpp

View File

@ -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

View 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