[clangd] Support for standard type hierarchy

This is mostly a mechanical change to adapt standard type hierarchy
support proposed in LSP 3.17 on top of clangd's existing extension support.

This does mainly two things:
- Incorporate symbolids for all the parents inside resolution parameters, so
  that they can be retrieved from index later on. This is a new code path, as
  extension always resolved them eagerly.
- Propogate parent information when resolving children, so that at least one
  branch of parents is always preserved. This is to address a shortcoming in the
  extension.

This doesn't drop support for the extension, but it's deprecated from now on and
will be deleted in upcoming releases. Currently we use the same struct
internally but don't serialize extra fields.

Fixes https://github.com/clangd/clangd/issues/826.

Differential Revision: https://reviews.llvm.org/D131385
This commit is contained in:
Kadir Cetinkaya 2022-08-08 11:22:31 +02:00
parent 177cbb1c9b
commit 83411bf06f
No known key found for this signature in database
GPG Key ID: E39E36B8D2057ED6
12 changed files with 782 additions and 360 deletions

View File

@ -26,6 +26,8 @@
#include "support/Trace.h" #include "support/Trace.h"
#include "clang/Tooling/Core/Replacement.h" #include "clang/Tooling/Core/Replacement.h"
#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/FunctionExtras.h"
#include "llvm/ADT/None.h"
#include "llvm/ADT/Optional.h" #include "llvm/ADT/Optional.h"
#include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringRef.h"
@ -571,8 +573,12 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
{"referencesProvider", true}, {"referencesProvider", true},
{"astProvider", true}, // clangd extension {"astProvider", true}, // clangd extension
{"typeHierarchyProvider", true}, {"typeHierarchyProvider", true},
{"memoryUsageProvider", true}, // clangd extension // Unfortunately our extension made use of the same capability name as the
{"compilationDatabase", // clangd extension // standard. Advertise this capability to tell clients that implement our
// extension we really have support for the standardized one as well.
{"standardTypeHierarchyProvider", true}, // clangd extension
{"memoryUsageProvider", true}, // clangd extension
{"compilationDatabase", // clangd extension
llvm::json::Object{{"automaticReload", true}}}, llvm::json::Object{{"automaticReload", true}}},
{"callHierarchyProvider", true}, {"callHierarchyProvider", true},
{"clangdInlayHintsProvider", true}, {"clangdInlayHintsProvider", true},
@ -1183,18 +1189,94 @@ void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params,
}); });
} }
void ClangdLSPServer::onTypeHierarchy( // Our extension has a different representation on the wire than the standard.
const TypeHierarchyParams &Params, // https://clangd.llvm.org/extensions#type-hierarchy
Callback<Optional<TypeHierarchyItem>> Reply) { llvm::json::Value serializeTHIForExtension(TypeHierarchyItem THI) {
llvm::json::Object Result{{
{"name", std::move(THI.name)},
{"kind", static_cast<int>(THI.kind)},
{"uri", std::move(THI.uri)},
{"range", THI.range},
{"selectionRange", THI.selectionRange},
{"data", std::move(THI.data)},
}};
if (THI.deprecated)
Result["deprecated"] = THI.deprecated;
if (THI.detail)
Result["detail"] = std::move(*THI.detail);
if (THI.parents) {
llvm::json::Array Parents;
for (auto &Parent : *THI.parents)
Parents.emplace_back(serializeTHIForExtension(std::move(Parent)));
Result["parents"] = std::move(Parents);
}
if (THI.children) {
llvm::json::Array Children;
for (auto &child : *THI.children)
Children.emplace_back(serializeTHIForExtension(std::move(child)));
Result["children"] = std::move(Children);
}
return Result;
}
void ClangdLSPServer::onTypeHierarchy(const TypeHierarchyPrepareParams &Params,
Callback<llvm::json::Value> Reply) {
auto Serialize =
[Reply = std::move(Reply)](
llvm::Expected<std::vector<TypeHierarchyItem>> Resp) mutable {
if (!Resp) {
Reply(Resp.takeError());
return;
}
if (Resp->empty()) {
Reply(nullptr);
return;
}
Reply(serializeTHIForExtension(std::move(Resp->front())));
};
Server->typeHierarchy(Params.textDocument.uri.file(), Params.position, Server->typeHierarchy(Params.textDocument.uri.file(), Params.position,
Params.resolve, Params.direction, std::move(Reply)); Params.resolve, Params.direction, std::move(Serialize));
} }
void ClangdLSPServer::onResolveTypeHierarchy( void ClangdLSPServer::onResolveTypeHierarchy(
const ResolveTypeHierarchyItemParams &Params, const ResolveTypeHierarchyItemParams &Params,
Callback<Optional<TypeHierarchyItem>> Reply) { Callback<llvm::json::Value> Reply) {
auto Serialize =
[Reply = std::move(Reply)](
llvm::Expected<llvm::Optional<TypeHierarchyItem>> Resp) mutable {
if (!Resp) {
Reply(Resp.takeError());
return;
}
if (!*Resp) {
Reply(std::move(*Resp));
return;
}
Reply(serializeTHIForExtension(std::move(**Resp)));
};
Server->resolveTypeHierarchy(Params.item, Params.resolve, Params.direction, Server->resolveTypeHierarchy(Params.item, Params.resolve, Params.direction,
std::move(Reply)); std::move(Serialize));
}
void ClangdLSPServer::onPrepareTypeHierarchy(
const TypeHierarchyPrepareParams &Params,
Callback<std::vector<TypeHierarchyItem>> Reply) {
Server->typeHierarchy(Params.textDocument.uri.file(), Params.position,
Params.resolve, Params.direction, std::move(Reply));
}
void ClangdLSPServer::onSuperTypes(
const ResolveTypeHierarchyItemParams &Params,
Callback<llvm::Optional<std::vector<TypeHierarchyItem>>> Reply) {
Server->superTypes(Params.item, std::move(Reply));
}
void ClangdLSPServer::onSubTypes(
const ResolveTypeHierarchyItemParams &Params,
Callback<std::vector<TypeHierarchyItem>> Reply) {
Server->subTypes(Params.item, std::move(Reply));
} }
void ClangdLSPServer::onPrepareCallHierarchy( void ClangdLSPServer::onPrepareCallHierarchy(
@ -1523,6 +1605,9 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind,
Bind.method("textDocument/symbolInfo", this, &ClangdLSPServer::onSymbolInfo); Bind.method("textDocument/symbolInfo", this, &ClangdLSPServer::onSymbolInfo);
Bind.method("textDocument/typeHierarchy", this, &ClangdLSPServer::onTypeHierarchy); Bind.method("textDocument/typeHierarchy", this, &ClangdLSPServer::onTypeHierarchy);
Bind.method("typeHierarchy/resolve", this, &ClangdLSPServer::onResolveTypeHierarchy); Bind.method("typeHierarchy/resolve", this, &ClangdLSPServer::onResolveTypeHierarchy);
Bind.method("textDocument/prepareTypeHierarchy", this, &ClangdLSPServer::onPrepareTypeHierarchy);
Bind.method("typeHierarchy/supertypes", this, &ClangdLSPServer::onSuperTypes);
Bind.method("typeHierarchy/subtypes", this, &ClangdLSPServer::onSubTypes);
Bind.method("textDocument/prepareCallHierarchy", this, &ClangdLSPServer::onPrepareCallHierarchy); Bind.method("textDocument/prepareCallHierarchy", this, &ClangdLSPServer::onPrepareCallHierarchy);
Bind.method("callHierarchy/incomingCalls", this, &ClangdLSPServer::onCallHierarchyIncomingCalls); Bind.method("callHierarchy/incomingCalls", this, &ClangdLSPServer::onCallHierarchyIncomingCalls);
Bind.method("textDocument/selectionRange", this, &ClangdLSPServer::onSelectionRange); Bind.method("textDocument/selectionRange", this, &ClangdLSPServer::onSelectionRange);

View File

@ -23,6 +23,7 @@
#include <chrono> #include <chrono>
#include <cstddef> #include <cstddef>
#include <memory> #include <memory>
#include <vector>
namespace clang { namespace clang {
namespace clangd { namespace clangd {
@ -132,10 +133,16 @@ private:
void onRename(const RenameParams &, Callback<WorkspaceEdit>); void onRename(const RenameParams &, Callback<WorkspaceEdit>);
void onHover(const TextDocumentPositionParams &, void onHover(const TextDocumentPositionParams &,
Callback<llvm::Optional<Hover>>); Callback<llvm::Optional<Hover>>);
void onTypeHierarchy(const TypeHierarchyParams &, void onPrepareTypeHierarchy(const TypeHierarchyPrepareParams &,
Callback<llvm::Optional<TypeHierarchyItem>>); Callback<std::vector<TypeHierarchyItem>>);
void onSuperTypes(const ResolveTypeHierarchyItemParams &,
Callback<llvm::Optional<std::vector<TypeHierarchyItem>>>);
void onSubTypes(const ResolveTypeHierarchyItemParams &,
Callback<std::vector<TypeHierarchyItem>>);
void onTypeHierarchy(const TypeHierarchyPrepareParams &,
Callback<llvm::json::Value>);
void onResolveTypeHierarchy(const ResolveTypeHierarchyItemParams &, void onResolveTypeHierarchy(const ResolveTypeHierarchyItemParams &,
Callback<llvm::Optional<TypeHierarchyItem>>); Callback<llvm::json::Value>);
void onPrepareCallHierarchy(const CallHierarchyPrepareParams &, void onPrepareCallHierarchy(const CallHierarchyPrepareParams &,
Callback<std::vector<CallHierarchyItem>>); Callback<std::vector<CallHierarchyItem>>);
void onCallHierarchyIncomingCalls( void onCallHierarchyIncomingCalls(

View File

@ -751,7 +751,7 @@ void ClangdServer::findHover(PathRef File, Position Pos,
void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve, void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve,
TypeHierarchyDirection Direction, TypeHierarchyDirection Direction,
Callback<Optional<TypeHierarchyItem>> CB) { Callback<std::vector<TypeHierarchyItem>> CB) {
auto Action = [File = File.str(), Pos, Resolve, Direction, CB = std::move(CB), auto Action = [File = File.str(), Pos, Resolve, Direction, CB = std::move(CB),
this](Expected<InputsAndAST> InpAST) mutable { this](Expected<InputsAndAST> InpAST) mutable {
if (!InpAST) if (!InpAST)
@ -763,6 +763,22 @@ void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve,
WorkScheduler->runWithAST("TypeHierarchy", File, std::move(Action)); WorkScheduler->runWithAST("TypeHierarchy", File, std::move(Action));
} }
void ClangdServer::superTypes(
const TypeHierarchyItem &Item,
Callback<llvm::Optional<std::vector<TypeHierarchyItem>>> CB) {
WorkScheduler->run("typeHierarchy/superTypes", /*Path=*/"",
[=, CB = std::move(CB)]() mutable {
CB(clangd::superTypes(Item, Index));
});
}
void ClangdServer::subTypes(const TypeHierarchyItem &Item,
Callback<std::vector<TypeHierarchyItem>> CB) {
WorkScheduler->run(
"typeHierarchy/subTypes", /*Path=*/"",
[=, CB = std::move(CB)]() mutable { CB(clangd::subTypes(Item, Index)); });
}
void ClangdServer::resolveTypeHierarchy( void ClangdServer::resolveTypeHierarchy(
TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction, TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction,
Callback<llvm::Optional<TypeHierarchyItem>> CB) { Callback<llvm::Optional<TypeHierarchyItem>> CB) {

View File

@ -253,7 +253,13 @@ public:
/// Get information about type hierarchy for a given position. /// Get information about type hierarchy for a given position.
void typeHierarchy(PathRef File, Position Pos, int Resolve, void typeHierarchy(PathRef File, Position Pos, int Resolve,
TypeHierarchyDirection Direction, TypeHierarchyDirection Direction,
Callback<llvm::Optional<TypeHierarchyItem>> CB); Callback<std::vector<TypeHierarchyItem>> CB);
/// Get direct parents of a type hierarchy item.
void superTypes(const TypeHierarchyItem &Item,
Callback<llvm::Optional<std::vector<TypeHierarchyItem>>> CB);
/// Get direct children of a type hierarchy item.
void subTypes(const TypeHierarchyItem &Item,
Callback<std::vector<TypeHierarchyItem>> CB);
/// Resolve type hierarchy item in the given direction. /// Resolve type hierarchy item in the given direction.
void resolveTypeHierarchy(TypeHierarchyItem Item, int Resolve, void resolveTypeHierarchy(TypeHierarchyItem Item, int Resolve,

View File

@ -1207,12 +1207,13 @@ bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out,
return true; return true;
} }
bool fromJSON(const llvm::json::Value &Params, TypeHierarchyParams &R, bool fromJSON(const llvm::json::Value &Params, TypeHierarchyPrepareParams &R,
llvm::json::Path P) { llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P); llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument) && return O && O.map("textDocument", R.textDocument) &&
O.map("position", R.position) && O.map("resolve", R.resolve) && O.map("position", R.position) &&
O.map("direction", R.direction); mapOptOrNull(Params, "resolve", R.resolve, P) &&
mapOptOrNull(Params, "direction", R.direction, P);
} }
llvm::raw_ostream &operator<<(llvm::raw_ostream &O, llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
@ -1220,23 +1221,28 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
return O << I.name << " - " << toJSON(I); return O << I.name << " - " << toJSON(I);
} }
llvm::json::Value toJSON(const TypeHierarchyItem::ResolveParams &RP) {
llvm::json::Object Result{{"symbolID", RP.symbolID}};
if (RP.parents)
Result["parents"] = RP.parents;
return std::move(Result);
}
bool fromJSON(const llvm::json::Value &Params,
TypeHierarchyItem::ResolveParams &RP, llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("symbolID", RP.symbolID) &&
mapOptOrNull(Params, "parents", RP.parents, P);
}
llvm::json::Value toJSON(const TypeHierarchyItem &I) { llvm::json::Value toJSON(const TypeHierarchyItem &I) {
llvm::json::Object Result{{"name", I.name}, llvm::json::Object Result{
{"kind", static_cast<int>(I.kind)}, {"name", I.name}, {"kind", static_cast<int>(I.kind)},
{"range", I.range}, {"range", I.range}, {"selectionRange", I.selectionRange},
{"selectionRange", I.selectionRange}, {"uri", I.uri}, {"data", I.data},
{"uri", I.uri}}; };
if (I.detail) if (I.detail)
Result["detail"] = I.detail; Result["detail"] = I.detail;
if (I.deprecated)
Result["deprecated"] = I.deprecated;
if (I.parents)
Result["parents"] = I.parents;
if (I.children)
Result["children"] = I.children;
if (I.data)
Result["data"] = I.data;
return std::move(Result); return std::move(Result);
} }
@ -1258,8 +1264,9 @@ bool fromJSON(const llvm::json::Value &Params, TypeHierarchyItem &I,
bool fromJSON(const llvm::json::Value &Params, bool fromJSON(const llvm::json::Value &Params,
ResolveTypeHierarchyItemParams &R, llvm::json::Path P) { ResolveTypeHierarchyItemParams &R, llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P); llvm::json::ObjectMapper O(Params, P);
return O && O.map("item", R.item) && O.map("resolve", R.resolve) && return O && O.map("item", R.item) &&
O.map("direction", R.direction); mapOptOrNull(Params, "resolve", R.resolve, P) &&
mapOptOrNull(Params, "direction", R.direction, P);
} }
bool fromJSON(const llvm::json::Value &Params, ReferenceContext &R, bool fromJSON(const llvm::json::Value &Params, ReferenceContext &R,
@ -1510,5 +1517,22 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const ASTNode &Root) {
return OS; return OS;
} }
bool fromJSON(const llvm::json::Value &E, SymbolID &S, llvm::json::Path P) {
auto Str = E.getAsString();
if (!Str) {
P.report("expected a string");
return false;
}
auto ID = SymbolID::fromStr(*Str);
if (!ID) {
elog("Malformed symbolid: {0}", ID.takeError());
P.report("malformed symbolid");
return false;
}
S = *ID;
return true;
}
llvm::json::Value toJSON(const SymbolID &S) { return S.str(); }
} // namespace clangd } // namespace clangd
} // namespace clang } // namespace clang

View File

@ -77,6 +77,9 @@ public:
} }
}; };
bool fromJSON(const llvm::json::Value &, SymbolID &, llvm::json::Path);
llvm::json::Value toJSON(const SymbolID &);
// URI in "file" scheme for a file. // URI in "file" scheme for a file.
struct URIForFile { struct URIForFile {
URIForFile() = default; URIForFile() = default;
@ -1379,58 +1382,66 @@ bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out,
/// The type hierarchy params is an extension of the /// The type hierarchy params is an extension of the
/// `TextDocumentPositionsParams` with optional properties which can be used to /// `TextDocumentPositionsParams` with optional properties which can be used to
/// eagerly resolve the item when requesting from the server. /// eagerly resolve the item when requesting from the server.
struct TypeHierarchyParams : public TextDocumentPositionParams { struct TypeHierarchyPrepareParams : public TextDocumentPositionParams {
/// The hierarchy levels to resolve. `0` indicates no level. /// The hierarchy levels to resolve. `0` indicates no level.
/// This is a clangd extension.
int resolve = 0; int resolve = 0;
/// The direction of the hierarchy levels to resolve. /// The direction of the hierarchy levels to resolve.
/// This is a clangd extension.
TypeHierarchyDirection direction = TypeHierarchyDirection::Parents; TypeHierarchyDirection direction = TypeHierarchyDirection::Parents;
}; };
bool fromJSON(const llvm::json::Value &, TypeHierarchyParams &, bool fromJSON(const llvm::json::Value &, TypeHierarchyPrepareParams &,
llvm::json::Path); llvm::json::Path);
struct TypeHierarchyItem { struct TypeHierarchyItem {
/// The human readable name of the hierarchy item. /// The name of this item.
std::string name; std::string name;
/// Optional detail for the hierarchy item. It can be, for instance, the /// The kind of this item.
/// signature of a function or method.
llvm::Optional<std::string> detail;
/// The kind of the hierarchy item. For instance, class or interface.
SymbolKind kind; SymbolKind kind;
/// `true` if the hierarchy item is deprecated. Otherwise, `false`. /// More detail for this item, e.g. the signature of a function.
bool deprecated = false; llvm::Optional<std::string> detail;
/// The URI of the text document where this type hierarchy item belongs to. /// The resource identifier of this item.
URIForFile uri; URIForFile uri;
/// The range enclosing this type hierarchy item not including /// The range enclosing this symbol not including leading/trailing whitespace
/// leading/trailing whitespace but everything else like comments. This /// but everything else, e.g. comments and code.
/// 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; Range range;
/// The range that should be selected and revealed when this type hierarchy /// The range that should be selected and revealed when this symbol is being
/// item is being picked, e.g. the name of a function. Must be contained by /// picked, e.g. the name of a function. Must be contained by the `range`.
/// the `range`.
Range selectionRange; Range selectionRange;
/// If this type hierarchy item is resolved, it contains the direct parents. /// Used to resolve a client provided item back.
/// Could be empty if the item does not have direct parents. If not defined, struct ResolveParams {
/// the parents have not been resolved yet. SymbolID symbolID;
/// None means parents aren't resolved and empty is no parents.
llvm::Optional<std::vector<ResolveParams>> parents;
};
/// A data entry field that is preserved between a type hierarchy prepare and
/// supertypes or subtypes requests. It could also be used to identify the
/// type hierarchy in the server, helping improve the performance on resolving
/// supertypes and subtypes.
ResolveParams data;
/// `true` if the hierarchy item is deprecated. Otherwise, `false`.
/// This is a clangd exntesion.
bool deprecated = false;
/// This is a clangd exntesion.
llvm::Optional<std::vector<TypeHierarchyItem>> parents; llvm::Optional<std::vector<TypeHierarchyItem>> parents;
/// If this type hierarchy item is resolved, it contains the direct children /// 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 /// of the current item. Could be empty if the item does not have any
/// descendants. If not defined, the children have not been resolved. /// descendants. If not defined, the children have not been resolved.
/// This is a clangd exntesion.
llvm::Optional<std::vector<TypeHierarchyItem>> children; llvm::Optional<std::vector<TypeHierarchyItem>> children;
/// An optional 'data' field, which can be used to identify a type hierarchy
/// item in a resolve request.
llvm::Optional<std::string> data;
}; };
llvm::json::Value toJSON(const TypeHierarchyItem::ResolveParams &);
bool fromJSON(const TypeHierarchyItem::ResolveParams &);
llvm::json::Value toJSON(const TypeHierarchyItem &); llvm::json::Value toJSON(const TypeHierarchyItem &);
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TypeHierarchyItem &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TypeHierarchyItem &);
bool fromJSON(const llvm::json::Value &, TypeHierarchyItem &, llvm::json::Path); bool fromJSON(const llvm::json::Value &, TypeHierarchyItem &, llvm::json::Path);

View File

@ -51,6 +51,7 @@
#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/None.h" #include "llvm/ADT/None.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallSet.h"
@ -60,6 +61,7 @@
#include "llvm/Support/Error.h" #include "llvm/Support/Error.h"
#include "llvm/Support/Path.h" #include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h" #include "llvm/Support/raw_ostream.h"
#include <vector>
namespace clang { namespace clang {
namespace clangd { namespace clangd {
@ -864,7 +866,8 @@ public:
}; };
ReferenceFinder(const ParsedAST &AST, ReferenceFinder(const ParsedAST &AST,
const llvm::ArrayRef<const NamedDecl *> Targets, bool PerToken) const llvm::ArrayRef<const NamedDecl *> Targets,
bool PerToken)
: PerToken(PerToken), AST(AST) { : PerToken(PerToken), AST(AST) {
for (const NamedDecl *ND : Targets) { for (const NamedDecl *ND : Targets) {
const Decl *CD = ND->getCanonicalDecl(); const Decl *CD = ND->getCanonicalDecl();
@ -937,7 +940,7 @@ private:
}; };
std::vector<ReferenceFinder::Reference> std::vector<ReferenceFinder::Reference>
findRefs(const llvm::ArrayRef<const NamedDecl*> TargetDecls, ParsedAST &AST, findRefs(const llvm::ArrayRef<const NamedDecl *> TargetDecls, ParsedAST &AST,
bool PerToken) { bool PerToken) {
ReferenceFinder RefFinder(AST, TargetDecls, PerToken); ReferenceFinder RefFinder(AST, TargetDecls, PerToken);
index::IndexingOptions IndexOpts; index::IndexingOptions IndexOpts;
@ -1225,8 +1228,8 @@ std::vector<DocumentHighlight> findDocumentHighlights(ParsedAST &AST,
if (const SelectionTree::Node *N = ST.commonAncestor()) { if (const SelectionTree::Node *N = ST.commonAncestor()) {
DeclRelationSet Relations = DeclRelationSet Relations =
DeclRelation::TemplatePattern | DeclRelation::Alias; DeclRelation::TemplatePattern | DeclRelation::Alias;
auto TargetDecls= auto TargetDecls =
targetDecl(N->ASTNode, Relations, AST.getHeuristicResolver()); targetDecl(N->ASTNode, Relations, AST.getHeuristicResolver());
if (!TargetDecls.empty()) { if (!TargetDecls.empty()) {
// FIXME: we may get multiple DocumentHighlights with the same location // FIXME: we may get multiple DocumentHighlights with the same location
// and different kinds, deduplicate them. // and different kinds, deduplicate them.
@ -1595,29 +1598,32 @@ declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
HI.uri = URIForFile::canonicalize(*FilePath, TUPath); HI.uri = URIForFile::canonicalize(*FilePath, TUPath);
// Compute the SymbolID and store it in the 'data' field.
// This allows typeHierarchy/resolve to be used to
// resolve children of items returned in a previous request
// for parents.
if (auto ID = getSymbolID(&ND))
HI.data = ID.str();
return HI; return HI;
} }
static llvm::Optional<TypeHierarchyItem> static llvm::Optional<TypeHierarchyItem>
declToTypeHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) { declToTypeHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
auto Result = declToHierarchyItem<TypeHierarchyItem>(ND, TUPath); auto Result = declToHierarchyItem<TypeHierarchyItem>(ND, TUPath);
if (Result) if (Result) {
Result->deprecated = ND.isDeprecated(); Result->deprecated = ND.isDeprecated();
// Compute the SymbolID and store it in the 'data' field.
// This allows typeHierarchy/resolve to be used to
// resolve children of items returned in a previous request
// for parents.
Result->data.symbolID = getSymbolID(&ND);
}
return Result; return Result;
} }
static llvm::Optional<CallHierarchyItem> static llvm::Optional<CallHierarchyItem>
declToCallHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) { declToCallHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
auto Result = declToHierarchyItem<CallHierarchyItem>(ND, TUPath); auto Result = declToHierarchyItem<CallHierarchyItem>(ND, TUPath);
if (Result && ND.isDeprecated()) if (!Result)
return Result;
if (ND.isDeprecated())
Result->tags.push_back(SymbolTag::Deprecated); Result->tags.push_back(SymbolTag::Deprecated);
if (auto ID = getSymbolID(&ND))
Result->data = ID.str();
return Result; return Result;
} }
@ -1637,10 +1643,6 @@ static llvm::Optional<HierarchyItem> symbolToHierarchyItem(const Symbol &S,
// (https://github.com/clangd/clangd/issues/59). // (https://github.com/clangd/clangd/issues/59).
HI.range = HI.selectionRange; HI.range = HI.selectionRange;
HI.uri = Loc->uri; HI.uri = Loc->uri;
// Store the SymbolID in the 'data' field. The client will
// send this back in requests to resolve additional levels
// of the hierarchy.
HI.data = S.ID.str();
return HI; return HI;
} }
@ -1648,15 +1650,20 @@ static llvm::Optional<HierarchyItem> symbolToHierarchyItem(const Symbol &S,
static llvm::Optional<TypeHierarchyItem> static llvm::Optional<TypeHierarchyItem>
symbolToTypeHierarchyItem(const Symbol &S, PathRef TUPath) { symbolToTypeHierarchyItem(const Symbol &S, PathRef TUPath) {
auto Result = symbolToHierarchyItem<TypeHierarchyItem>(S, TUPath); auto Result = symbolToHierarchyItem<TypeHierarchyItem>(S, TUPath);
if (Result) if (Result) {
Result->deprecated = (S.Flags & Symbol::Deprecated); Result->deprecated = (S.Flags & Symbol::Deprecated);
Result->data.symbolID = S.ID;
}
return Result; return Result;
} }
static llvm::Optional<CallHierarchyItem> static llvm::Optional<CallHierarchyItem>
symbolToCallHierarchyItem(const Symbol &S, PathRef TUPath) { symbolToCallHierarchyItem(const Symbol &S, PathRef TUPath) {
auto Result = symbolToHierarchyItem<CallHierarchyItem>(S, TUPath); auto Result = symbolToHierarchyItem<CallHierarchyItem>(S, TUPath);
if (Result && (S.Flags & Symbol::Deprecated)) if (!Result)
return Result;
Result->data = S.ID.str();
if (S.Flags & Symbol::Deprecated)
Result->tags.push_back(SymbolTag::Deprecated); Result->tags.push_back(SymbolTag::Deprecated);
return Result; return Result;
} }
@ -1681,9 +1688,12 @@ static void fillSubTypes(const SymbolID &ID,
using RecursionProtectionSet = llvm::SmallSet<const CXXRecordDecl *, 4>; using RecursionProtectionSet = llvm::SmallSet<const CXXRecordDecl *, 4>;
// Extracts parents from AST and populates the type hierarchy item.
static void fillSuperTypes(const CXXRecordDecl &CXXRD, llvm::StringRef TUPath, static void fillSuperTypes(const CXXRecordDecl &CXXRD, llvm::StringRef TUPath,
std::vector<TypeHierarchyItem> &SuperTypes, TypeHierarchyItem &Item,
RecursionProtectionSet &RPSet) { RecursionProtectionSet &RPSet) {
Item.parents.emplace();
Item.data.parents.emplace();
// typeParents() will replace dependent template specializations // typeParents() will replace dependent template specializations
// with their class template, so to avoid infinite recursion for // with their class template, so to avoid infinite recursion for
// certain types of hierarchies, keep the templates encountered // certain types of hierarchies, keep the templates encountered
@ -1699,9 +1709,9 @@ static void fillSuperTypes(const CXXRecordDecl &CXXRD, llvm::StringRef TUPath,
for (const CXXRecordDecl *ParentDecl : typeParents(&CXXRD)) { for (const CXXRecordDecl *ParentDecl : typeParents(&CXXRD)) {
if (Optional<TypeHierarchyItem> ParentSym = if (Optional<TypeHierarchyItem> ParentSym =
declToTypeHierarchyItem(*ParentDecl, TUPath)) { declToTypeHierarchyItem(*ParentDecl, TUPath)) {
ParentSym->parents.emplace(); fillSuperTypes(*ParentDecl, TUPath, *ParentSym, RPSet);
fillSuperTypes(*ParentDecl, TUPath, *ParentSym->parents, RPSet); Item.data.parents->emplace_back(ParentSym->data);
SuperTypes.emplace_back(std::move(*ParentSym)); Item.parents->emplace_back(std::move(*ParentSym));
} }
} }
@ -1710,11 +1720,12 @@ static void fillSuperTypes(const CXXRecordDecl &CXXRD, llvm::StringRef TUPath,
} }
} }
const CXXRecordDecl *findRecordTypeAt(ParsedAST &AST, Position Pos) { std::vector<const CXXRecordDecl *> findRecordTypeAt(ParsedAST &AST,
auto RecordFromNode = Position Pos) {
[&AST](const SelectionTree::Node *N) -> const CXXRecordDecl * { auto RecordFromNode = [&AST](const SelectionTree::Node *N) {
std::vector<const CXXRecordDecl *> Records;
if (!N) if (!N)
return nullptr; return Records;
// Note: explicitReferenceTargets() will search for both template // Note: explicitReferenceTargets() will search for both template
// instantiations and template patterns, and prefer the former if available // instantiations and template patterns, and prefer the former if available
@ -1722,30 +1733,32 @@ const CXXRecordDecl *findRecordTypeAt(ParsedAST &AST, Position Pos) {
// class template). // class template).
auto Decls = explicitReferenceTargets(N->ASTNode, DeclRelation::Underlying, auto Decls = explicitReferenceTargets(N->ASTNode, DeclRelation::Underlying,
AST.getHeuristicResolver()); AST.getHeuristicResolver());
if (Decls.empty()) for (const NamedDecl *D : Decls) {
return nullptr;
const NamedDecl *D = Decls[0]; if (const VarDecl *VD = dyn_cast<VarDecl>(D)) {
// If this is a variable, use the type of the variable.
Records.push_back(VD->getType().getTypePtr()->getAsCXXRecordDecl());
continue;
}
if (const VarDecl *VD = dyn_cast<VarDecl>(D)) { if (const CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(D)) {
// If this is a variable, use the type of the variable. // If this is a method, use the type of the class.
return VD->getType().getTypePtr()->getAsCXXRecordDecl(); Records.push_back(Method->getParent());
continue;
}
// We don't handle FieldDecl because it's not clear what behaviour
// the user would expect: the enclosing class type (as with a
// method), or the field's type (as with a variable).
if (auto *RD = dyn_cast<CXXRecordDecl>(D))
Records.push_back(RD);
} }
return Records;
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);
}; };
const SourceManager &SM = AST.getSourceManager(); const SourceManager &SM = AST.getSourceManager();
const CXXRecordDecl *Result = nullptr; std::vector<const CXXRecordDecl *> Result;
auto Offset = positionToOffset(SM.getBufferData(SM.getMainFileID()), Pos); auto Offset = positionToOffset(SM.getBufferData(SM.getMainFileID()), Pos);
if (!Offset) { if (!Offset) {
llvm::consumeError(Offset.takeError()); llvm::consumeError(Offset.takeError());
@ -1754,7 +1767,7 @@ const CXXRecordDecl *findRecordTypeAt(ParsedAST &AST, Position Pos) {
SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), *Offset, SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), *Offset,
*Offset, [&](SelectionTree ST) { *Offset, [&](SelectionTree ST) {
Result = RecordFromNode(ST.commonAncestor()); Result = RecordFromNode(ST.commonAncestor());
return Result != nullptr; return !Result.empty();
}); });
return Result; return Result;
} }
@ -1998,53 +2011,79 @@ std::vector<const CXXRecordDecl *> typeParents(const CXXRecordDecl *CXXRD) {
return Result; return Result;
} }
llvm::Optional<TypeHierarchyItem> std::vector<TypeHierarchyItem>
getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels, getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels,
TypeHierarchyDirection Direction, const SymbolIndex *Index, TypeHierarchyDirection Direction, const SymbolIndex *Index,
PathRef TUPath) { PathRef TUPath) {
const CXXRecordDecl *CXXRD = findRecordTypeAt(AST, Pos); std::vector<TypeHierarchyItem> Results;
if (!CXXRD) for (const auto *CXXRD : findRecordTypeAt(AST, Pos)) {
return llvm::None;
bool WantParents = Direction == TypeHierarchyDirection::Parents || bool WantChildren = Direction == TypeHierarchyDirection::Children ||
Direction == TypeHierarchyDirection::Both; Direction == TypeHierarchyDirection::Both;
bool WantChildren = Direction == TypeHierarchyDirection::Children ||
Direction == TypeHierarchyDirection::Both;
// If we're looking for children, we're doing the lookup in the index. // If we're looking for children, we're doing the lookup in the index.
// The index does not store relationships between implicit // The index does not store relationships between implicit
// specializations, so if we have one, use the template pattern instead. // specializations, so if we have one, use the template pattern instead.
// Note that this needs to be done before the declToTypeHierarchyItem(), // Note that this needs to be done before the declToTypeHierarchyItem(),
// otherwise the type hierarchy item would misleadingly contain the // otherwise the type hierarchy item would misleadingly contain the
// specialization parameters, while the children would involve classes // specialization parameters, while the children would involve classes
// that derive from other specializations of the template. // that derive from other specializations of the template.
if (WantChildren) { if (WantChildren) {
if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(CXXRD)) if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(CXXRD))
CXXRD = CTSD->getTemplateInstantiationPattern(); CXXRD = CTSD->getTemplateInstantiationPattern();
} }
Optional<TypeHierarchyItem> Result = Optional<TypeHierarchyItem> Result =
declToTypeHierarchyItem(*CXXRD, AST.tuPath()); declToTypeHierarchyItem(*CXXRD, AST.tuPath());
if (!Result) if (!Result)
return Result; continue;
if (WantParents) {
Result->parents.emplace();
RecursionProtectionSet RPSet; RecursionProtectionSet RPSet;
fillSuperTypes(*CXXRD, AST.tuPath(), *Result->parents, RPSet); fillSuperTypes(*CXXRD, AST.tuPath(), *Result, RPSet);
}
if (WantChildren && ResolveLevels > 0) { if (WantChildren && ResolveLevels > 0) {
Result->children.emplace(); Result->children.emplace();
if (Index) { if (Index) {
if (auto ID = getSymbolID(CXXRD)) if (auto ID = getSymbolID(CXXRD))
fillSubTypes(ID, *Result->children, Index, ResolveLevels, TUPath); fillSubTypes(ID, *Result->children, Index, ResolveLevels, TUPath);
}
} }
Results.emplace_back(std::move(*Result));
} }
return Result; return Results;
}
llvm::Optional<std::vector<TypeHierarchyItem>>
superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) {
std::vector<TypeHierarchyItem> Results;
if (!Item.data.parents)
return llvm::None;
if (Item.data.parents->empty())
return Results;
LookupRequest Req;
llvm::DenseMap<SymbolID, const TypeHierarchyItem::ResolveParams *> IDToData;
for (const auto &Parent : *Item.data.parents) {
Req.IDs.insert(Parent.symbolID);
IDToData[Parent.symbolID] = &Parent;
}
Index->lookup(Req, [&Item, &Results, &IDToData](const Symbol &S) {
if (auto THI = symbolToTypeHierarchyItem(S, Item.uri.file())) {
THI->data = *IDToData.lookup(S.ID);
Results.emplace_back(std::move(*THI));
}
});
return Results;
}
std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
const SymbolIndex *Index) {
std::vector<TypeHierarchyItem> Results;
fillSubTypes(Item.data.symbolID, Results, Index, 1, Item.uri.file());
for (auto &ChildSym : Results)
ChildSym.data.parents = {Item.data};
return Results;
} }
void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels, void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
@ -2052,18 +2091,13 @@ void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
const SymbolIndex *Index) { const SymbolIndex *Index) {
// We only support typeHierarchy/resolve for children, because for parents // We only support typeHierarchy/resolve for children, because for parents
// we ignore ResolveLevels and return all levels of parents eagerly. // we ignore ResolveLevels and return all levels of parents eagerly.
if (Direction == TypeHierarchyDirection::Parents || ResolveLevels == 0) if (!Index || Direction == TypeHierarchyDirection::Parents ||
ResolveLevels == 0)
return; return;
Item.children.emplace(); Item.children.emplace();
fillSubTypes(Item.data.symbolID, *Item.children, Index, ResolveLevels,
if (Index && Item.data) { Item.uri.file());
// We store the item's SymbolID in the 'data' field, and the client
// passes it back to us in typeHierarchy/resolve.
if (Expected<SymbolID> ID = SymbolID::fromStr(*Item.data)) {
fillSubTypes(*ID, *Item.children, Index, ResolveLevels, Item.uri.file());
}
}
} }
std::vector<CallHierarchyItem> std::vector<CallHierarchyItem>

View File

@ -117,17 +117,26 @@ ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit,
/// Get info about symbols at \p Pos. /// Get info about symbols at \p Pos.
std::vector<SymbolDetails> getSymbolInfo(ParsedAST &AST, Position Pos); std::vector<SymbolDetails> getSymbolInfo(ParsedAST &AST, Position Pos);
/// Find the record type references at \p Pos. /// Find the record types referenced at \p Pos.
const CXXRecordDecl *findRecordTypeAt(ParsedAST &AST, Position Pos); std::vector<const CXXRecordDecl *> findRecordTypeAt(ParsedAST &AST,
Position Pos);
/// Given a record type declaration, find its base (parent) types. /// Given a record type declaration, find its base (parent) types.
std::vector<const CXXRecordDecl *> typeParents(const CXXRecordDecl *CXXRD); std::vector<const CXXRecordDecl *> typeParents(const CXXRecordDecl *CXXRD);
/// Get type hierarchy information at \p Pos. /// Get type hierarchy information at \p Pos.
llvm::Optional<TypeHierarchyItem> getTypeHierarchy( std::vector<TypeHierarchyItem> getTypeHierarchy(
ParsedAST &AST, Position Pos, int Resolve, TypeHierarchyDirection Direction, ParsedAST &AST, Position Pos, int Resolve, TypeHierarchyDirection Direction,
const SymbolIndex *Index = nullptr, PathRef TUPath = PathRef{}); const SymbolIndex *Index = nullptr, PathRef TUPath = PathRef{});
/// Returns direct parents of a TypeHierarchyItem using SymbolIDs stored inside
/// the item.
llvm::Optional<std::vector<TypeHierarchyItem>>
superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index);
/// Returns direct children of a TypeHierarchyItem.
std::vector<TypeHierarchyItem> subTypes(const TypeHierarchyItem &Item,
const SymbolIndex *Index);
void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels, void resolveTypeHierarchy(TypeHierarchyItem &Item, int ResolveLevels,
TypeHierarchyDirection Direction, TypeHierarchyDirection Direction,
const SymbolIndex *Index); const SymbolIndex *Index);

View File

@ -88,6 +88,7 @@
# CHECK-NEXT: "," # CHECK-NEXT: ","
# CHECK-NEXT: ] # CHECK-NEXT: ]
# CHECK-NEXT: }, # CHECK-NEXT: },
# CHECK-NEXT: "standardTypeHierarchyProvider": true,
# CHECK-NEXT: "textDocumentSync": { # CHECK-NEXT: "textDocumentSync": {
# CHECK-NEXT: "change": 2, # CHECK-NEXT: "change": 2,
# CHECK-NEXT: "openClose": true, # CHECK-NEXT: "openClose": true,

View File

@ -0,0 +1,211 @@
# 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 {};\nstruct Child3 : Child2 {};\nstruct Child4 : Child3 {};"}}}
---
{"jsonrpc":"2.0","id":1,"method":"textDocument/typeHierarchy","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":11},"direction":2,"resolve":1}}
# CHECK: "id": 1
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": {
# CHECK-NEXT: "children": [
# CHECK-NEXT: {
# CHECK-NEXT: "data": {
# CHECK-NEXT: "symbolID": "A6576FE083F2949A"
# CHECK-NEXT: },
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child3",
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 13,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 7,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "selectionRange": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 13,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 7,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "data": {
# CHECK-NEXT: "parents": [
# CHECK-NEXT: {
# CHECK-NEXT: "parents": [
# CHECK-NEXT: {
# CHECK-NEXT: "parents": [],
# CHECK-NEXT: "symbolID": "FE546E7B648D69A7"
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "symbolID": "ECDC0C46D75120F4"
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "symbolID": "8A991335E4E67D08"
# CHECK-NEXT: },
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child2",
# CHECK-NEXT: "parents": [
# CHECK-NEXT: {
# CHECK-NEXT: "data": {
# CHECK-NEXT: "parents": [
# CHECK-NEXT: {
# CHECK-NEXT: "parents": [],
# CHECK-NEXT: "symbolID": "FE546E7B648D69A7"
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "symbolID": "ECDC0C46D75120F4"
# CHECK-NEXT: },
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child1",
# CHECK-NEXT: "parents": [
# CHECK-NEXT: {
# CHECK-NEXT: "data": {
# CHECK-NEXT: "parents": [],
# CHECK-NEXT: "symbolID": "FE546E7B648D69A7"
# CHECK-NEXT: },
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Parent",
# CHECK-NEXT: "parents": [],
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 16,
# CHECK-NEXT: "line": 0
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 0,
# CHECK-NEXT: "line": 0
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "selectionRange": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 13,
# CHECK-NEXT: "line": 0
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 7,
# CHECK-NEXT: "line": 0
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 25,
# CHECK-NEXT: "line": 1
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 0,
# CHECK-NEXT: "line": 1
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "selectionRange": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 13,
# CHECK-NEXT: "line": 1
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 7,
# CHECK-NEXT: "line": 1
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 25,
# CHECK-NEXT: "line": 2
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 0,
# CHECK-NEXT: "line": 2
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "selectionRange": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 13,
# CHECK-NEXT: "line": 2
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 7,
# CHECK-NEXT: "line": 2
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
# CHECK-NEXT: }
---
{"jsonrpc":"2.0","id":2,"method":"typeHierarchy/resolve","params":{"item":{"uri":"test:///main.cpp","data":{"symbolID":"A6576FE083F2949A"},"name":"Child3","kind":23,"range":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}},"selectionRange":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}}},"direction":0,"resolve":1}}
# CHECK: "id": 2
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": {
# CHECK-NEXT: "children": [
# CHECK-NEXT: {
# CHECK-NEXT: "data": {
# CHECK-NEXT: "symbolID": "5705B382DFC77CBC"
# CHECK-NEXT: },
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child4",
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 13,
# CHECK-NEXT: "line": 4
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 7,
# CHECK-NEXT: "line": 4
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "selectionRange": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 13,
# CHECK-NEXT: "line": 4
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 7,
# CHECK-NEXT: "line": 4
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "data": {
# CHECK-NEXT: "symbolID": "A6576FE083F2949A"
# CHECK-NEXT: },
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child3",
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 13,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 7,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "selectionRange": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 13,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 7,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
# CHECK-NEXT: }
---
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
---
{"jsonrpc":"2.0","method":"exit"}

View File

@ -3,178 +3,140 @@
--- ---
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"struct Parent {};\nstruct Child1 : Parent {};\nstruct Child2 : Child1 {};\nstruct Child3 : Child2 {};\nstruct Child4 : Child3 {};"}}} {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"struct Parent {};\nstruct Child1 : Parent {};\nstruct Child2 : Child1 {};\nstruct Child3 : Child2 {};\nstruct Child4 : Child3 {};"}}}
--- ---
{"jsonrpc":"2.0","id":1,"method":"textDocument/typeHierarchy","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":11},"direction":2,"resolve":1}} {"jsonrpc":"2.0","id":1,"method":"textDocument/prepareTypeHierarchy","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":11},"direction":2,"resolve":1}}
# CHECK: "id": 1 # CHECK: "id": 1
# CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": { # CHECK-NEXT: "result": [
# CHECK-NEXT: "children": [ # CHECK-NEXT: {
# CHECK-NEXT: { # CHECK-NEXT: "data": {
# CHECK-NEXT: "data": "A6576FE083F2949A", # CHECK-NEXT: "parents": [
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child3",
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 13,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 7,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "selectionRange": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 13,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 7,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "data": "8A991335E4E67D08",
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child2",
# CHECK-NEXT: "parents": [
# CHECK-NEXT: {
# CHECK-NEXT: "data": "ECDC0C46D75120F4",
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child1",
# CHECK-NEXT: "parents": [
# CHECK-NEXT: { # CHECK-NEXT: {
# CHECK-NEXT: "data": "FE546E7B648D69A7", # CHECK-NEXT: "parents": [
# CHECK-NEXT: "kind": 23, # CHECK-NEXT: {
# CHECK-NEXT: "name": "Parent", # CHECK-NEXT: "parents": [],
# CHECK-NEXT: "parents": [], # CHECK-NEXT: "symbolID": "FE546E7B648D69A7"
# CHECK-NEXT: "range": { # CHECK-NEXT: }
# CHECK-NEXT: "end": { # CHECK-NEXT: ],
# CHECK-NEXT: "character": 16, # CHECK-NEXT: "symbolID": "ECDC0C46D75120F4"
# 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: ], # CHECK-NEXT: ],
# CHECK-NEXT: "range": { # CHECK-NEXT: "symbolID": "8A991335E4E67D08"
# CHECK-NEXT: "end": { # CHECK-NEXT: },
# CHECK-NEXT: "character": 25, # CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "line": 1 # CHECK-NEXT: "name": "Child2",
# CHECK-NEXT: }, # CHECK-NEXT: "range": {
# CHECK-NEXT: "start": { # CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 0, # CHECK-NEXT: "character": 25,
# CHECK-NEXT: "line": 1 # CHECK-NEXT: "line": 2
# CHECK-NEXT: } # CHECK-NEXT: },
# CHECK-NEXT: }, # CHECK-NEXT: "start": {
# CHECK-NEXT: "selectionRange": { # CHECK-NEXT: "character": 0,
# CHECK-NEXT: "end": { # CHECK-NEXT: "line": 2
# CHECK-NEXT: "character": 13, # CHECK-NEXT: }
# CHECK-NEXT: "line": 1 # CHECK-NEXT: },
# CHECK-NEXT: }, # CHECK-NEXT: "selectionRange": {
# CHECK-NEXT: "start": { # CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 7, # CHECK-NEXT: "character": 13,
# CHECK-NEXT: "line": 1 # CHECK-NEXT: "line": 2
# CHECK-NEXT: } # CHECK-NEXT: },
# CHECK-NEXT: }, # CHECK-NEXT: "start": {
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" # CHECK-NEXT: "character": 7,
# CHECK-NEXT: } # CHECK-NEXT: "line": 2
# CHECK-NEXT: ], # CHECK-NEXT: }
# CHECK-NEXT: "range": { # CHECK-NEXT: },
# CHECK-NEXT: "end": { # CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
# CHECK-NEXT: "character": 25, # CHECK-NEXT: }
# CHECK-NEXT: "line": 2 # CHECK-NEXT: ]
# 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":"typeHierarchy/resolve","params":{"item":{"uri":"test:///main.cpp","data":"A6576FE083F2949A","name":"Child3","kind":23,"range":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}},"selectionRange":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}}},"direction":0,"resolve":1}} {"jsonrpc":"2.0","id":2,"method":"typeHierarchy/supertypes","params":{"item":{"uri":"test:///main.cpp","data":{"parents":[{"parents":[{"parents":[],"symbolID":"FE546E7B648D69A7"}],"symbolID":"ECDC0C46D75120F4"}],"symbolID":"8A991335E4E67D08"},"name":"Child2","kind":23,"range":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}},"selectionRange":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}}}}}
# CHECK: "id": 2 # CHECK: "id": 2
# CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": { # CHECK-NEXT: "result": [
# CHECK-NEXT: "children": [ # CHECK-NEXT: {
# CHECK-NEXT: { # CHECK-NEXT: "data": {
# CHECK-NEXT: "data": "5705B382DFC77CBC", # CHECK-NEXT: "parents": [
# CHECK-NEXT: "kind": 23, # CHECK-NEXT: {
# CHECK-NEXT: "name": "Child4", # CHECK-NEXT: "parents": [],
# CHECK-NEXT: "range": { # CHECK-NEXT: "symbolID": "FE546E7B648D69A7"
# CHECK-NEXT: "end": { # CHECK-NEXT: }
# CHECK-NEXT: "character": 13, # CHECK-NEXT: ],
# CHECK-NEXT: "line": 4 # CHECK-NEXT: "symbolID": "ECDC0C46D75120F4"
# CHECK-NEXT: }, # CHECK-NEXT: },
# CHECK-NEXT: "start": { # CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "character": 7, # CHECK-NEXT: "name": "Child1",
# CHECK-NEXT: "line": 4 # CHECK-NEXT: "range": {
# CHECK-NEXT: } # CHECK-NEXT: "end": {
# CHECK-NEXT: }, # CHECK-NEXT: "character": 13,
# CHECK-NEXT: "selectionRange": { # CHECK-NEXT: "line": 1
# CHECK-NEXT: "end": { # CHECK-NEXT: },
# CHECK-NEXT: "character": 13, # CHECK-NEXT: "start": {
# CHECK-NEXT: "line": 4 # CHECK-NEXT: "character": 7,
# CHECK-NEXT: }, # CHECK-NEXT: "line": 1
# CHECK-NEXT: "start": { # CHECK-NEXT: }
# CHECK-NEXT: "character": 7, # CHECK-NEXT: },
# CHECK-NEXT: "line": 4 # CHECK-NEXT: "selectionRange": {
# CHECK-NEXT: } # CHECK-NEXT: "end": {
# CHECK-NEXT: }, # CHECK-NEXT: "character": 13,
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" # CHECK-NEXT: "line": 1
# CHECK-NEXT: } # CHECK-NEXT: },
# CHECK-NEXT: ], # CHECK-NEXT: "start": {
# CHECK-NEXT: "data": "A6576FE083F2949A", # CHECK-NEXT: "character": 7,
# CHECK-NEXT: "kind": 23, # CHECK-NEXT: "line": 1
# CHECK-NEXT: "name": "Child3", # CHECK-NEXT: }
# CHECK-NEXT: "range": { # CHECK-NEXT: },
# CHECK-NEXT: "end": { # CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
# CHECK-NEXT: "character": 13, # CHECK-NEXT: }
# CHECK-NEXT: "line": 3 # CHECK-NEXT: ]
# CHECK-NEXT: }, ---
# CHECK-NEXT: "start": { {"jsonrpc":"2.0","id":2,"method":"typeHierarchy/subtypes","params":{"item":{"uri":"test:///main.cpp","data":{"parents":[{"parents":[{"parents":[],"symbolID":"FE546E7B648D69A7"}],"symbolID":"ECDC0C46D75120F4"}],"symbolID":"8A991335E4E67D08"},"name":"Child2","kind":23,"range":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}},"selectionRange":{"end":{"character":13,"line":3},"start":{"character":7,"line":3}}}}}
# CHECK-NEXT: "character": 7, # CHECK: "id": 2
# CHECK-NEXT: "line": 3 # CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: } # CHECK-NEXT: "result": [
# CHECK-NEXT: }, # CHECK-NEXT: {
# CHECK-NEXT: "selectionRange": { # CHECK-NEXT: "data": {
# CHECK-NEXT: "end": { # CHECK-NEXT: "parents": [
# CHECK-NEXT: "character": 13, # CHECK-NEXT: {
# CHECK-NEXT: "line": 3 # CHECK-NEXT: "parents": [
# CHECK-NEXT: }, # CHECK-NEXT: {
# CHECK-NEXT: "start": { # CHECK-NEXT: "parents": [
# CHECK-NEXT: "character": 7, # CHECK-NEXT: {
# CHECK-NEXT: "line": 3 # CHECK-NEXT: "parents": [],
# CHECK-NEXT: } # CHECK-NEXT: "symbolID": "FE546E7B648D69A7"
# CHECK-NEXT: }, # CHECK-NEXT: }
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp" # CHECK-NEXT: ],
# CHECK-NEXT: } # CHECK-NEXT: "symbolID": "ECDC0C46D75120F4"
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "symbolID": "8A991335E4E67D08"
# CHECK-NEXT: }
# CHECK-NEXT: ],
# CHECK-NEXT: "symbolID": "A6576FE083F2949A"
# CHECK-NEXT: },
# CHECK-NEXT: "kind": 23,
# CHECK-NEXT: "name": "Child3",
# CHECK-NEXT: "range": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 13,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 7,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "selectionRange": {
# CHECK-NEXT: "end": {
# CHECK-NEXT: "character": 13,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: },
# CHECK-NEXT: "start": {
# CHECK-NEXT: "character": 7,
# CHECK-NEXT: "line": 3
# CHECK-NEXT: }
# CHECK-NEXT: },
# CHECK-NEXT: "uri": "file://{{.*}}/clangd-test/main.cpp"
# CHECK-NEXT: }
# CHECK-NEXT: ]
--- ---
{"jsonrpc":"2.0","id":3,"method":"shutdown"} {"jsonrpc":"2.0","id":3,"method":"shutdown"}
--- ---

View File

@ -5,6 +5,7 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
#include "AST.h"
#include "Annotations.h" #include "Annotations.h"
#include "Matchers.h" #include "Matchers.h"
#include "ParsedAST.h" #include "ParsedAST.h"
@ -16,6 +17,7 @@
#include "llvm/Support/Path.h" #include "llvm/Support/Path.h"
#include "gmock/gmock.h" #include "gmock/gmock.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include <vector>
namespace clang { namespace clang {
namespace clangd { namespace clangd {
@ -26,6 +28,7 @@ using ::testing::ElementsAre;
using ::testing::Field; using ::testing::Field;
using ::testing::IsEmpty; using ::testing::IsEmpty;
using ::testing::Matcher; using ::testing::Matcher;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre; using ::testing::UnorderedElementsAre;
// GMock helpers for matching TypeHierarchyItem. // GMock helpers for matching TypeHierarchyItem.
@ -45,6 +48,10 @@ template <class... ChildMatchers>
// Note: "not resolved" is different from "resolved but empty"! // Note: "not resolved" is different from "resolved but empty"!
MATCHER(parentsNotResolved, "") { return !arg.parents; } MATCHER(parentsNotResolved, "") { return !arg.parents; }
MATCHER(childrenNotResolved, "") { return !arg.children; } MATCHER(childrenNotResolved, "") { return !arg.children; }
MATCHER_P(withResolveID, SID, "") { return arg.symbolID.str() == SID; }
MATCHER_P(withResolveParents, M, "") {
return testing::ExplainMatchResult(M, arg.data.parents, result_listener);
}
TEST(FindRecordTypeAt, TypeOrVariable) { TEST(FindRecordTypeAt, TypeOrVariable) {
Annotations Source(R"cpp( Annotations Source(R"cpp(
@ -64,8 +71,10 @@ int main() {
auto AST = TU.build(); auto AST = TU.build();
for (Position Pt : Source.points()) { for (Position Pt : Source.points()) {
const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt); auto Records = findRecordTypeAt(AST, Pt);
EXPECT_EQ(&findDecl(AST, "Child2"), static_cast<const NamedDecl *>(RD)); ASSERT_THAT(Records, SizeIs(1));
EXPECT_EQ(&findDecl(AST, "Child2"),
static_cast<const NamedDecl *>(Records.front()));
} }
} }
@ -86,8 +95,10 @@ int main() {
auto AST = TU.build(); auto AST = TU.build();
for (Position Pt : Source.points()) { for (Position Pt : Source.points()) {
const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt); auto Records = findRecordTypeAt(AST, Pt);
EXPECT_EQ(&findDecl(AST, "Child2"), static_cast<const NamedDecl *>(RD)); ASSERT_THAT(Records, SizeIs(1));
EXPECT_EQ(&findDecl(AST, "Child2"),
static_cast<const NamedDecl *>(Records.front()));
} }
} }
@ -107,11 +118,10 @@ int main() {
auto AST = TU.build(); auto AST = TU.build();
for (Position Pt : Source.points()) { for (Position Pt : Source.points()) {
const CXXRecordDecl *RD = findRecordTypeAt(AST, Pt);
// A field does not unambiguously specify a record type // A field does not unambiguously specify a record type
// (possible associated reocrd types could be the field's type, // (possible associated reocrd types could be the field's type,
// or the type of the record that the field is a member of). // or the type of the record that the field is a member of).
EXPECT_EQ(nullptr, RD); EXPECT_THAT(findRecordTypeAt(AST, Pt), SizeIs(0));
} }
} }
@ -359,11 +369,11 @@ int main() {
for (Position Pt : Source.points()) { for (Position Pt : Source.points()) {
// Set ResolveLevels to 0 because it's only used for Children; // Set ResolveLevels to 0 because it's only used for Children;
// for Parents, getTypeHierarchy() always returns all levels. // for Parents, getTypeHierarchy() always returns all levels.
llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy( auto Result = getTypeHierarchy(AST, Pt, /*ResolveLevels=*/0,
AST, Pt, /*ResolveLevels=*/0, TypeHierarchyDirection::Parents); TypeHierarchyDirection::Parents);
ASSERT_TRUE(bool(Result)); ASSERT_THAT(Result, SizeIs(1));
EXPECT_THAT( EXPECT_THAT(
*Result, Result.front(),
AllOf( AllOf(
withName("Child"), withKind(SymbolKind::Struct), withName("Child"), withKind(SymbolKind::Struct),
parents(AllOf(withName("Parent1"), withKind(SymbolKind::Struct), parents(AllOf(withName("Parent1"), withKind(SymbolKind::Struct),
@ -398,11 +408,11 @@ TEST(TypeHierarchy, RecursiveHierarchyUnbounded) {
// The parent is reported as "S" because "S<0>" is an invalid instantiation. // The parent is reported as "S" because "S<0>" is an invalid instantiation.
// We then iterate once more and find "S" again before detecting the // We then iterate once more and find "S" again before detecting the
// recursion. // recursion.
llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy( auto Result = getTypeHierarchy(AST, Source.points()[0], 0,
AST, Source.points()[0], 0, TypeHierarchyDirection::Parents); TypeHierarchyDirection::Parents);
ASSERT_TRUE(bool(Result)); ASSERT_THAT(Result, SizeIs(1));
EXPECT_THAT( EXPECT_THAT(
*Result, Result.front(),
AllOf(withName("S<0>"), withKind(SymbolKind::Struct), AllOf(withName("S<0>"), withKind(SymbolKind::Struct),
parents( parents(
AllOf(withName("S"), withKind(SymbolKind::Struct), AllOf(withName("S"), withKind(SymbolKind::Struct),
@ -432,11 +442,11 @@ TEST(TypeHierarchy, RecursiveHierarchyBounded) {
// Make sure getTypeHierarchy() doesn't get into an infinite recursion // Make sure getTypeHierarchy() doesn't get into an infinite recursion
// for either a concrete starting point or a dependent starting point. // for either a concrete starting point or a dependent starting point.
llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy( auto Result = getTypeHierarchy(AST, Source.point("SRefConcrete"), 0,
AST, Source.point("SRefConcrete"), 0, TypeHierarchyDirection::Parents); TypeHierarchyDirection::Parents);
ASSERT_TRUE(bool(Result)); ASSERT_THAT(Result, SizeIs(1));
EXPECT_THAT( EXPECT_THAT(
*Result, Result.front(),
AllOf(withName("S<2>"), withKind(SymbolKind::Struct), AllOf(withName("S<2>"), withKind(SymbolKind::Struct),
parents(AllOf( parents(AllOf(
withName("S<1>"), withKind(SymbolKind::Struct), withName("S<1>"), withKind(SymbolKind::Struct),
@ -445,9 +455,9 @@ TEST(TypeHierarchy, RecursiveHierarchyBounded) {
parents())))))); parents()))))));
Result = getTypeHierarchy(AST, Source.point("SRefDependent"), 0, Result = getTypeHierarchy(AST, Source.point("SRefDependent"), 0,
TypeHierarchyDirection::Parents); TypeHierarchyDirection::Parents);
ASSERT_TRUE(bool(Result)); ASSERT_THAT(Result, SizeIs(1));
EXPECT_THAT( EXPECT_THAT(
*Result, Result.front(),
AllOf(withName("S"), withKind(SymbolKind::Struct), AllOf(withName("S"), withKind(SymbolKind::Struct),
parents(AllOf(withName("S"), withKind(SymbolKind::Struct), parents(AllOf(withName("S"), withKind(SymbolKind::Struct),
selectionRangeIs(Source.range("SDef")), parents())))); selectionRangeIs(Source.range("SDef")), parents()))));
@ -469,11 +479,11 @@ TEST(TypeHierarchy, DeriveFromImplicitSpec) {
auto AST = TU.build(); auto AST = TU.build();
auto Index = TU.index(); auto Index = TU.index();
llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy( auto Result = getTypeHierarchy(AST, Source.points()[0], 2,
AST, Source.points()[0], 2, TypeHierarchyDirection::Children, Index.get(), TypeHierarchyDirection::Children, Index.get(),
testPath(TU.Filename)); testPath(TU.Filename));
ASSERT_TRUE(bool(Result)); ASSERT_THAT(Result, SizeIs(1));
EXPECT_THAT(*Result, EXPECT_THAT(Result.front(),
AllOf(withName("Parent"), withKind(SymbolKind::Struct), AllOf(withName("Parent"), withKind(SymbolKind::Struct),
children(AllOf(withName("Child1"), children(AllOf(withName("Child1"),
withKind(SymbolKind::Struct), children()), withKind(SymbolKind::Struct), children()),
@ -495,12 +505,12 @@ TEST(TypeHierarchy, DeriveFromPartialSpec) {
auto AST = TU.build(); auto AST = TU.build();
auto Index = TU.index(); auto Index = TU.index();
llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy( auto Result = getTypeHierarchy(AST, Source.points()[0], 2,
AST, Source.points()[0], 2, TypeHierarchyDirection::Children, Index.get(), TypeHierarchyDirection::Children, Index.get(),
testPath(TU.Filename)); testPath(TU.Filename));
ASSERT_TRUE(bool(Result)); ASSERT_THAT(Result, SizeIs(1));
EXPECT_THAT(*Result, AllOf(withName("Parent"), withKind(SymbolKind::Struct), EXPECT_THAT(Result.front(), AllOf(withName("Parent"),
children())); withKind(SymbolKind::Struct), children()));
} }
TEST(TypeHierarchy, DeriveFromTemplate) { TEST(TypeHierarchy, DeriveFromTemplate) {
@ -521,11 +531,11 @@ TEST(TypeHierarchy, DeriveFromTemplate) {
// FIXME: We'd like this to show the implicit specializations Parent<int> // FIXME: We'd like this to show the implicit specializations Parent<int>
// and Child<int>, but currently libIndex does not expose relationships // and Child<int>, but currently libIndex does not expose relationships
// between implicit specializations. // between implicit specializations.
llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy( auto Result = getTypeHierarchy(AST, Source.points()[0], 2,
AST, Source.points()[0], 2, TypeHierarchyDirection::Children, Index.get(), TypeHierarchyDirection::Children, Index.get(),
testPath(TU.Filename)); testPath(TU.Filename));
ASSERT_TRUE(bool(Result)); ASSERT_THAT(Result, SizeIs(1));
EXPECT_THAT(*Result, EXPECT_THAT(Result.front(),
AllOf(withName("Parent"), withKind(SymbolKind::Struct), AllOf(withName("Parent"), withKind(SymbolKind::Struct),
children(AllOf(withName("Child"), children(AllOf(withName("Child"),
withKind(SymbolKind::Struct), children())))); withKind(SymbolKind::Struct), children()))));
@ -546,12 +556,12 @@ struct [[Parent]] {
TU.HeaderCode = HeaderInPreambleAnnotations.code().str(); TU.HeaderCode = HeaderInPreambleAnnotations.code().str();
auto AST = TU.build(); auto AST = TU.build();
llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy( std::vector<TypeHierarchyItem> Result = getTypeHierarchy(
AST, SourceAnnotations.point(), 1, TypeHierarchyDirection::Parents); AST, SourceAnnotations.point(), 1, TypeHierarchyDirection::Parents);
ASSERT_TRUE(Result); ASSERT_THAT(Result, SizeIs(1));
EXPECT_THAT( EXPECT_THAT(
*Result, Result.front(),
AllOf(withName("Child"), AllOf(withName("Child"),
parents(AllOf(withName("Parent"), parents(AllOf(withName("Parent"),
selectionRangeIs(HeaderInPreambleAnnotations.range()), selectionRangeIs(HeaderInPreambleAnnotations.range()),
@ -722,22 +732,21 @@ struct Child2b : Child1 {};
auto AST = TU.build(); auto AST = TU.build();
auto Index = TU.index(); auto Index = TU.index();
llvm::Optional<TypeHierarchyItem> Result = getTypeHierarchy( auto Result = getTypeHierarchy(AST, Source.point(), /*ResolveLevels=*/1,
AST, Source.point(), /*ResolveLevels=*/1, TypeHierarchyDirection::Children, Index.get(),
TypeHierarchyDirection::Children, Index.get(), testPath(TU.Filename)); testPath(TU.Filename));
ASSERT_TRUE(bool(Result)); ASSERT_THAT(Result, SizeIs(1));
EXPECT_THAT( EXPECT_THAT(
*Result, Result.front(),
AllOf(withName("Parent"), withKind(SymbolKind::Struct), AllOf(withName("Parent"), withKind(SymbolKind::Struct), parents(),
parentsNotResolved(),
children(AllOf(withName("Child1"), withKind(SymbolKind::Struct), children(AllOf(withName("Child1"), withKind(SymbolKind::Struct),
parentsNotResolved(), childrenNotResolved())))); parentsNotResolved(), childrenNotResolved()))));
resolveTypeHierarchy((*Result->children)[0], /*ResolveLevels=*/1, resolveTypeHierarchy((*Result.front().children)[0], /*ResolveLevels=*/1,
TypeHierarchyDirection::Children, Index.get()); TypeHierarchyDirection::Children, Index.get());
EXPECT_THAT( EXPECT_THAT(
(*Result->children)[0], (*Result.front().children)[0],
AllOf(withName("Child1"), withKind(SymbolKind::Struct), AllOf(withName("Child1"), withKind(SymbolKind::Struct),
parentsNotResolved(), parentsNotResolved(),
children(AllOf(withName("Child2a"), withKind(SymbolKind::Struct), children(AllOf(withName("Child2a"), withKind(SymbolKind::Struct),
@ -746,6 +755,53 @@ struct Child2b : Child1 {};
parentsNotResolved(), childrenNotResolved())))); parentsNotResolved(), childrenNotResolved()))));
} }
TEST(Standard, SubTypes) {
Annotations Source(R"cpp(
struct Pare^nt1 {};
struct Parent2 {};
struct Child : Parent1, Parent2 {};
)cpp");
TestTU TU = TestTU::withCode(Source.code());
auto AST = TU.build();
auto Index = TU.index();
auto Result = getTypeHierarchy(AST, Source.point(), /*ResolveLevels=*/1,
TypeHierarchyDirection::Children, Index.get(),
testPath(TU.Filename));
ASSERT_THAT(Result, SizeIs(1));
auto Children = subTypes(Result.front(), Index.get());
// Make sure parents are populated when getting children.
// FIXME: This is partial.
EXPECT_THAT(
Children,
UnorderedElementsAre(
AllOf(withName("Child"),
withResolveParents(HasValue(UnorderedElementsAre(withResolveID(
getSymbolID(&findDecl(AST, "Parent1")).str())))))));
}
TEST(Standard, SuperTypes) {
Annotations Source(R"cpp(
struct Parent {};
struct Chil^d : Parent {};
)cpp");
TestTU TU = TestTU::withCode(Source.code());
auto AST = TU.build();
auto Index = TU.index();
auto Result = getTypeHierarchy(AST, Source.point(), /*ResolveLevels=*/1,
TypeHierarchyDirection::Children, Index.get(),
testPath(TU.Filename));
ASSERT_THAT(Result, SizeIs(1));
auto Parents = superTypes(Result.front(), Index.get());
EXPECT_THAT(Parents, HasValue(UnorderedElementsAre(
AllOf(withName("Parent"),
withResolveParents(HasValue(IsEmpty()))))));
}
} // namespace } // namespace
} // namespace clangd } // namespace clangd
} // namespace clang } // namespace clang