Skip to content

Commit

Permalink
[clangd] Support for standard type hierarchy
Browse files Browse the repository at this point in the history
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 clangd/clangd#826.

Differential Revision: https://reviews.llvm.org/D131385

(cherry picked from commit 83411bf)
  • Loading branch information
kadircet authored and tru committed Aug 23, 2022
1 parent 0777364 commit 99b6254
Show file tree
Hide file tree
Showing 12 changed files with 785 additions and 362 deletions.
101 changes: 93 additions & 8 deletions clang-tools-extra/clangd/ClangdLSPServer.cpp
Expand Up @@ -26,6 +26,8 @@
#include "support/Trace.h"
#include "clang/Tooling/Core/Replacement.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/FunctionExtras.h"
#include "llvm/ADT/None.h"
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringRef.h"
Expand Down Expand Up @@ -571,8 +573,12 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
{"referencesProvider", true},
{"astProvider", true}, // clangd extension
{"typeHierarchyProvider", true},
{"memoryUsageProvider", true}, // clangd extension
{"compilationDatabase", // clangd extension
// Unfortunately our extension made use of the same capability name as the
// standard. Advertise this capability to tell clients that implement our
// extension we really have support for the standardized one as well.
{"standardTypeHierarchyProvider", true}, // clangd extension
{"memoryUsageProvider", true}, // clangd extension
{"compilationDatabase", // clangd extension
llvm::json::Object{{"automaticReload", true}}},
{"callHierarchyProvider", true},
{"clangdInlayHintsProvider", true},
Expand Down Expand Up @@ -1183,18 +1189,94 @@ void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params,
});
}

void ClangdLSPServer::onTypeHierarchy(
const TypeHierarchyParams &Params,
Callback<Optional<TypeHierarchyItem>> Reply) {
// Our extension has a different representation on the wire than the standard.
// https://clangd.llvm.org/extensions#type-hierarchy
llvm::json::Value serializeTHIForExtension(TypeHierarchyItem THI) {
llvm::json::Object Result{{
{"name", std::move(THI.name)},
{"kind", static_cast<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,
Params.resolve, Params.direction, std::move(Reply));
Params.resolve, Params.direction, std::move(Serialize));
}

void ClangdLSPServer::onResolveTypeHierarchy(
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,
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(
Expand Down Expand Up @@ -1523,6 +1605,9 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind,
Bind.method("textDocument/symbolInfo", this, &ClangdLSPServer::onSymbolInfo);
Bind.method("textDocument/typeHierarchy", this, &ClangdLSPServer::onTypeHierarchy);
Bind.method("typeHierarchy/resolve", this, &ClangdLSPServer::onResolveTypeHierarchy);
Bind.method("textDocument/prepareTypeHierarchy", this, &ClangdLSPServer::onPrepareTypeHierarchy);
Bind.method("typeHierarchy/supertypes", this, &ClangdLSPServer::onSuperTypes);
Bind.method("typeHierarchy/subtypes", this, &ClangdLSPServer::onSubTypes);
Bind.method("textDocument/prepareCallHierarchy", this, &ClangdLSPServer::onPrepareCallHierarchy);
Bind.method("callHierarchy/incomingCalls", this, &ClangdLSPServer::onCallHierarchyIncomingCalls);
Bind.method("textDocument/selectionRange", this, &ClangdLSPServer::onSelectionRange);
Expand Down
13 changes: 10 additions & 3 deletions clang-tools-extra/clangd/ClangdLSPServer.h
Expand Up @@ -23,6 +23,7 @@
#include <chrono>
#include <cstddef>
#include <memory>
#include <vector>

namespace clang {
namespace clangd {
Expand Down Expand Up @@ -132,10 +133,16 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
void onRename(const RenameParams &, Callback<WorkspaceEdit>);
void onHover(const TextDocumentPositionParams &,
Callback<llvm::Optional<Hover>>);
void onTypeHierarchy(const TypeHierarchyParams &,
Callback<llvm::Optional<TypeHierarchyItem>>);
void onPrepareTypeHierarchy(const TypeHierarchyPrepareParams &,
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 &,
Callback<llvm::Optional<TypeHierarchyItem>>);
Callback<llvm::json::Value>);
void onPrepareCallHierarchy(const CallHierarchyPrepareParams &,
Callback<std::vector<CallHierarchyItem>>);
void onCallHierarchyIncomingCalls(
Expand Down
18 changes: 17 additions & 1 deletion clang-tools-extra/clangd/ClangdServer.cpp
Expand Up @@ -751,7 +751,7 @@ void ClangdServer::findHover(PathRef File, Position Pos,

void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve,
TypeHierarchyDirection Direction,
Callback<Optional<TypeHierarchyItem>> CB) {
Callback<std::vector<TypeHierarchyItem>> CB) {
auto Action = [File = File.str(), Pos, Resolve, Direction, CB = std::move(CB),
this](Expected<InputsAndAST> InpAST) mutable {
if (!InpAST)
Expand All @@ -763,6 +763,22 @@ void ClangdServer::typeHierarchy(PathRef File, Position Pos, int Resolve,
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(
TypeHierarchyItem Item, int Resolve, TypeHierarchyDirection Direction,
Callback<llvm::Optional<TypeHierarchyItem>> CB) {
Expand Down
8 changes: 7 additions & 1 deletion clang-tools-extra/clangd/ClangdServer.h
Expand Up @@ -253,7 +253,13 @@ class ClangdServer {
/// Get information about type hierarchy for a given position.
void typeHierarchy(PathRef File, Position Pos, int Resolve,
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.
void resolveTypeHierarchy(TypeHierarchyItem Item, int Resolve,
Expand Down
60 changes: 42 additions & 18 deletions clang-tools-extra/clangd/Protocol.cpp
Expand Up @@ -1199,36 +1199,42 @@ bool fromJSON(const llvm::json::Value &E, TypeHierarchyDirection &Out,
return true;
}

bool fromJSON(const llvm::json::Value &Params, TypeHierarchyParams &R,
bool fromJSON(const llvm::json::Value &Params, TypeHierarchyPrepareParams &R,
llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("textDocument", R.textDocument) &&
O.map("position", R.position) && O.map("resolve", R.resolve) &&
O.map("direction", R.direction);
O.map("position", R.position) &&
mapOptOrNull(Params, "resolve", R.resolve, P) &&
mapOptOrNull(Params, "direction", R.direction, P);
}

llvm::raw_ostream &operator<<(llvm::raw_ostream &O,
const TypeHierarchyItem &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::Object Result{{"name", I.name},
{"kind", static_cast<int>(I.kind)},
{"range", I.range},
{"selectionRange", I.selectionRange},
{"uri", I.uri}};
llvm::json::Object Result{
{"name", I.name}, {"kind", static_cast<int>(I.kind)},
{"range", I.range}, {"selectionRange", I.selectionRange},
{"uri", I.uri}, {"data", I.data},
};

if (I.detail)
Result["detail"] = I.detail;
if (I.deprecated)
Result["deprecated"] = I.deprecated;
if (I.parents)
Result["parents"] = I.parents;
if (I.children)
Result["children"] = I.children;
if (I.data)
Result["data"] = I.data;
return std::move(Result);
}

Expand All @@ -1250,8 +1256,9 @@ bool fromJSON(const llvm::json::Value &Params, TypeHierarchyItem &I,
bool fromJSON(const llvm::json::Value &Params,
ResolveTypeHierarchyItemParams &R, llvm::json::Path P) {
llvm::json::ObjectMapper O(Params, P);
return O && O.map("item", R.item) && O.map("resolve", R.resolve) &&
O.map("direction", R.direction);
return O && O.map("item", R.item) &&
mapOptOrNull(Params, "resolve", R.resolve, P) &&
mapOptOrNull(Params, "direction", R.direction, P);
}

bool fromJSON(const llvm::json::Value &Params, ReferenceContext &R,
Expand Down Expand Up @@ -1498,5 +1505,22 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const ASTNode &Root) {
return OS;
}

bool fromJSON(const llvm::json::Value &E, SymbolID &S, llvm::json::Path P) {
auto Str = E.getAsString();
if (!Str) {
P.report("expected a string");
return false;
}
auto ID = SymbolID::fromStr(*Str);
if (!ID) {
elog("Malformed symbolid: {0}", ID.takeError());
P.report("malformed symbolid");
return false;
}
S = *ID;
return true;
}
llvm::json::Value toJSON(const SymbolID &S) { return S.str(); }

} // namespace clangd
} // namespace clang

0 comments on commit 99b6254

Please sign in to comment.