Skip to content

Commit f3d2263

Browse files
qchateauHighCommander4
authored andcommitted
[clangd] Support outgoing calls in call hierarchy
The implementation is very close the the incoming calls implementation. The results of the outgoing calls are expected to be the exact symmetry of the incoming calls. Differential Revision: https://reviews.llvm.org/D93829
1 parent 9e65dca commit f3d2263

20 files changed

+454
-120
lines changed

clang-tools-extra/clangd/ClangdLSPServer.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,6 +1415,12 @@ void ClangdLSPServer::onInlayHint(const InlayHintsParams &Params,
14151415
std::move(Reply));
14161416
}
14171417

1418+
void ClangdLSPServer::onCallHierarchyOutgoingCalls(
1419+
const CallHierarchyOutgoingCallsParams &Params,
1420+
Callback<std::vector<CallHierarchyOutgoingCall>> Reply) {
1421+
Server->outgoingCalls(Params.item, std::move(Reply));
1422+
}
1423+
14181424
void ClangdLSPServer::applyConfiguration(
14191425
const ConfigurationSettings &Settings) {
14201426
// Per-file update to the compilation database.
@@ -1696,6 +1702,7 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind,
16961702
Bind.method("typeHierarchy/subtypes", this, &ClangdLSPServer::onSubTypes);
16971703
Bind.method("textDocument/prepareCallHierarchy", this, &ClangdLSPServer::onPrepareCallHierarchy);
16981704
Bind.method("callHierarchy/incomingCalls", this, &ClangdLSPServer::onCallHierarchyIncomingCalls);
1705+
Bind.method("callHierarchy/outgoingCalls", this, &ClangdLSPServer::onCallHierarchyOutgoingCalls);
16991706
Bind.method("textDocument/selectionRange", this, &ClangdLSPServer::onSelectionRange);
17001707
Bind.method("textDocument/documentLink", this, &ClangdLSPServer::onDocumentLink);
17011708
Bind.method("textDocument/semanticTokens/full", this, &ClangdLSPServer::onSemanticTokens);

clang-tools-extra/clangd/ClangdLSPServer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
156156
void onCallHierarchyIncomingCalls(
157157
const CallHierarchyIncomingCallsParams &,
158158
Callback<std::vector<CallHierarchyIncomingCall>>);
159+
void onCallHierarchyOutgoingCalls(
160+
const CallHierarchyOutgoingCallsParams &,
161+
Callback<std::vector<CallHierarchyOutgoingCall>>);
159162
void onClangdInlayHints(const InlayHintsParams &,
160163
Callback<llvm::json::Value>);
161164
void onInlayHint(const InlayHintsParams &, Callback<std::vector<InlayHint>>);

clang-tools-extra/clangd/ClangdServer.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,15 @@ void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange,
911911
WorkScheduler->runWithAST("InlayHints", File, std::move(Action), Transient);
912912
}
913913

914+
void ClangdServer::outgoingCalls(
915+
const CallHierarchyItem &Item,
916+
Callback<std::vector<CallHierarchyOutgoingCall>> CB) {
917+
WorkScheduler->run("Outgoing Calls", "",
918+
[CB = std::move(CB), Item, this]() mutable {
919+
CB(clangd::outgoingCalls(Item, Index));
920+
});
921+
}
922+
914923
void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
915924
// FIXME: Do nothing for now. This will be used for indexing and potentially
916925
// invalidating other caches.

clang-tools-extra/clangd/ClangdServer.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,10 @@ class ClangdServer {
292292
void incomingCalls(const CallHierarchyItem &Item,
293293
Callback<std::vector<CallHierarchyIncomingCall>>);
294294

295+
/// Resolve outgoing calls for a given call hierarchy item.
296+
void outgoingCalls(const CallHierarchyItem &Item,
297+
Callback<std::vector<CallHierarchyOutgoingCall>>);
298+
295299
/// Resolve inlay hints for a given document.
296300
void inlayHints(PathRef File, std::optional<Range> RestrictRange,
297301
Callback<std::vector<InlayHint>>);

clang-tools-extra/clangd/XRefs.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1701,6 +1701,7 @@ declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
17011701

17021702
HierarchyItem HI;
17031703
HI.name = printName(Ctx, ND);
1704+
// FIXME: Populate HI.detail the way we do in symbolToHierarchyItem?
17041705
HI.kind = SK;
17051706
HI.range = Range{sourceLocToPosition(SM, DeclRange->getBegin()),
17061707
sourceLocToPosition(SM, DeclRange->getEnd())};
@@ -1752,6 +1753,7 @@ static std::optional<HierarchyItem> symbolToHierarchyItem(const Symbol &S,
17521753
}
17531754
HierarchyItem HI;
17541755
HI.name = std::string(S.Name);
1756+
HI.detail = (S.Scope + S.Name).str();
17551757
HI.kind = indexSymbolKindToSymbolKind(S.SymInfo.Kind);
17561758
HI.selectionRange = Loc->range;
17571759
// FIXME: Populate 'range' correctly
@@ -2304,6 +2306,67 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
23042306
return Results;
23052307
}
23062308

2309+
std::vector<CallHierarchyOutgoingCall>
2310+
outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
2311+
std::vector<CallHierarchyOutgoingCall> Results;
2312+
if (!Index || Item.data.empty())
2313+
return Results;
2314+
auto ID = SymbolID::fromStr(Item.data);
2315+
if (!ID) {
2316+
elog("outgoingCalls failed to find symbol: {0}", ID.takeError());
2317+
return Results;
2318+
}
2319+
// In this function, we find outgoing calls based on the index only.
2320+
RefsRequest Request;
2321+
Request.IDs.insert(*ID);
2322+
// We could restrict more specifically to calls by introducing a new RefKind,
2323+
// but non-call references (such as address-of-function) can still be
2324+
// interesting as they can indicate indirect calls.
2325+
Request.Filter = RefKind::Reference;
2326+
// Initially store the ranges in a map keyed by SymbolID of the callee.
2327+
// This allows us to group different calls to the same function
2328+
// into the same CallHierarchyOutgoingCall.
2329+
llvm::DenseMap<SymbolID, std::vector<Range>> CallsOut;
2330+
// We can populate the ranges based on a refs request only. As we do so, we
2331+
// also accumulate the callee IDs into a lookup request.
2332+
LookupRequest CallsOutLookup;
2333+
Index->refersTo(Request, [&](const auto &R) {
2334+
auto Loc = indexToLSPLocation(R.Location, Item.uri.file());
2335+
if (!Loc) {
2336+
elog("outgoingCalls failed to convert location: {0}", Loc.takeError());
2337+
return;
2338+
}
2339+
auto It = CallsOut.try_emplace(R.Symbol, std::vector<Range>{}).first;
2340+
It->second.push_back(Loc->range);
2341+
2342+
CallsOutLookup.IDs.insert(R.Symbol);
2343+
});
2344+
// Perform the lookup request and combine its results with CallsOut to
2345+
// get complete CallHierarchyOutgoingCall objects.
2346+
Index->lookup(CallsOutLookup, [&](const Symbol &Callee) {
2347+
// Filter references to only keep function calls
2348+
using SK = index::SymbolKind;
2349+
auto Kind = Callee.SymInfo.Kind;
2350+
if (Kind != SK::Function && Kind != SK::InstanceMethod &&
2351+
Kind != SK::ClassMethod && Kind != SK::StaticMethod &&
2352+
Kind != SK::Constructor && Kind != SK::Destructor &&
2353+
Kind != SK::ConversionFunction)
2354+
return;
2355+
2356+
auto It = CallsOut.find(Callee.ID);
2357+
assert(It != CallsOut.end());
2358+
if (auto CHI = symbolToCallHierarchyItem(Callee, Item.uri.file()))
2359+
Results.push_back(
2360+
CallHierarchyOutgoingCall{std::move(*CHI), std::move(It->second)});
2361+
});
2362+
// Sort results by name of the callee.
2363+
llvm::sort(Results, [](const CallHierarchyOutgoingCall &A,
2364+
const CallHierarchyOutgoingCall &B) {
2365+
return A.to.name < B.to.name;
2366+
});
2367+
return Results;
2368+
}
2369+
23072370
llvm::DenseSet<const Decl *> getNonLocalDeclRefs(ParsedAST &AST,
23082371
const FunctionDecl *FD) {
23092372
if (!FD->hasBody())

clang-tools-extra/clangd/XRefs.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath);
150150
std::vector<CallHierarchyIncomingCall>
151151
incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index);
152152

153+
std::vector<CallHierarchyOutgoingCall>
154+
outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index);
155+
153156
/// Returns all decls that are referenced in the \p FD except local symbols.
154157
llvm::DenseSet<const Decl *> getNonLocalDeclRefs(ParsedAST &AST,
155158
const FunctionDecl *FD);

clang-tools-extra/clangd/index/Index.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ bool SwapIndex::refs(const RefsRequest &R,
6666
llvm::function_ref<void(const Ref &)> CB) const {
6767
return snapshot()->refs(R, CB);
6868
}
69+
bool SwapIndex::refersTo(
70+
const RefsRequest &R,
71+
llvm::function_ref<void(const RefersToResult &)> CB) const {
72+
return snapshot()->refersTo(R, CB);
73+
}
6974
void SwapIndex::relations(
7075
const RelationsRequest &R,
7176
llvm::function_ref<void(const SymbolID &, const Symbol &)> CB) const {

clang-tools-extra/clangd/index/Index.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ struct RelationsRequest {
8484
std::optional<uint32_t> Limit;
8585
};
8686

87+
struct RefersToResult {
88+
/// The source location where the symbol is named.
89+
SymbolLocation Location;
90+
RefKind Kind = RefKind::Unknown;
91+
/// The ID of the symbol which is referred to
92+
SymbolID Symbol;
93+
};
94+
8795
/// Describes what data is covered by an index.
8896
///
8997
/// Indexes may contain symbols but not references from a file, etc.
@@ -141,6 +149,17 @@ class SymbolIndex {
141149
virtual bool refs(const RefsRequest &Req,
142150
llvm::function_ref<void(const Ref &)> Callback) const = 0;
143151

152+
/// Find all symbols that are referenced by a symbol and apply
153+
/// \p Callback on each result.
154+
///
155+
/// Results should be returned in arbitrary order.
156+
/// The returned result must be deep-copied if it's used outside Callback.
157+
///
158+
/// Returns true if there will be more results (limited by Req.Limit);
159+
virtual bool
160+
refersTo(const RefsRequest &Req,
161+
llvm::function_ref<void(const RefersToResult &)> Callback) const = 0;
162+
144163
/// Finds all relations (S, P, O) stored in the index such that S is among
145164
/// Req.Subjects and P is Req.Predicate, and invokes \p Callback for (S, O) in
146165
/// each.
@@ -175,6 +194,9 @@ class SwapIndex : public SymbolIndex {
175194
llvm::function_ref<void(const Symbol &)>) const override;
176195
bool refs(const RefsRequest &,
177196
llvm::function_ref<void(const Ref &)>) const override;
197+
bool
198+
refersTo(const RefsRequest &,
199+
llvm::function_ref<void(const RefersToResult &)>) const override;
178200
void relations(const RelationsRequest &,
179201
llvm::function_ref<void(const SymbolID &, const Symbol &)>)
180202
const override;

clang-tools-extra/clangd/index/MemIndex.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,25 @@ bool MemIndex::refs(const RefsRequest &Req,
8585
return false; // We reported all refs.
8686
}
8787

88+
bool MemIndex::refersTo(
89+
const RefsRequest &Req,
90+
llvm::function_ref<void(const RefersToResult &)> Callback) const {
91+
trace::Span Tracer("MemIndex refersTo");
92+
uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
93+
for (const auto &Pair : Refs) {
94+
for (const auto &R : Pair.second) {
95+
if (!static_cast<int>(Req.Filter & R.Kind) ||
96+
!Req.IDs.contains(R.Container))
97+
continue;
98+
if (Remaining == 0)
99+
return true; // More refs were available.
100+
--Remaining;
101+
Callback({R.Location, R.Kind, Pair.first});
102+
}
103+
}
104+
return false; // We reported all refs.
105+
}
106+
88107
void MemIndex::relations(
89108
const RelationsRequest &Req,
90109
llvm::function_ref<void(const SymbolID &, const Symbol &)> Callback) const {

clang-tools-extra/clangd/index/MemIndex.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ class MemIndex : public SymbolIndex {
7272
bool refs(const RefsRequest &Req,
7373
llvm::function_ref<void(const Ref &)> Callback) const override;
7474

75+
bool refersTo(
76+
const RefsRequest &Req,
77+
llvm::function_ref<void(const RefersToResult &)> Callback) const override;
78+
7579
void relations(const RelationsRequest &Req,
7680
llvm::function_ref<void(const SymbolID &, const Symbol &)>
7781
Callback) const override;

clang-tools-extra/clangd/index/Merge.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,40 @@ bool MergedIndex::refs(const RefsRequest &Req,
155155
return More || StaticHadMore;
156156
}
157157

158+
bool MergedIndex::refersTo(
159+
const RefsRequest &Req,
160+
llvm::function_ref<void(const RefersToResult &)> Callback) const {
161+
trace::Span Tracer("MergedIndex refersTo");
162+
bool More = false;
163+
uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
164+
// We don't want duplicated refs from the static/dynamic indexes,
165+
// and we can't reliably deduplicate them because offsets may differ slightly.
166+
// We consider the dynamic index authoritative and report all its refs,
167+
// and only report static index refs from other files.
168+
More |= Dynamic->refersTo(Req, [&](const auto &O) {
169+
Callback(O);
170+
assert(Remaining != 0);
171+
--Remaining;
172+
});
173+
if (Remaining == 0 && More)
174+
return More;
175+
auto DynamicContainsFile = Dynamic->indexedFiles();
176+
// We return less than Req.Limit if static index returns more refs for dirty
177+
// files.
178+
bool StaticHadMore = Static->refersTo(Req, [&](const auto &O) {
179+
if ((DynamicContainsFile(O.Location.FileURI) & IndexContents::References) !=
180+
IndexContents::None)
181+
return; // ignore refs that have been seen from dynamic index.
182+
if (Remaining == 0) {
183+
More = true;
184+
return;
185+
}
186+
--Remaining;
187+
Callback(O);
188+
});
189+
return More || StaticHadMore;
190+
}
191+
158192
llvm::unique_function<IndexContents(llvm::StringRef) const>
159193
MergedIndex::indexedFiles() const {
160194
return [DynamicContainsFile{Dynamic->indexedFiles()},

clang-tools-extra/clangd/index/Merge.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ class MergedIndex : public SymbolIndex {
3838
llvm::function_ref<void(const Symbol &)>) const override;
3939
bool refs(const RefsRequest &,
4040
llvm::function_ref<void(const Ref &)>) const override;
41+
bool
42+
refersTo(const RefsRequest &,
43+
llvm::function_ref<void(const RefersToResult &)>) const override;
4144
void relations(const RelationsRequest &,
4245
llvm::function_ref<void(const SymbolID &, const Symbol &)>)
4346
const override;

clang-tools-extra/clangd/index/ProjectAware.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ class ProjectAwareIndex : public SymbolIndex {
3535
/// Query all indexes while prioritizing the associated one (if any).
3636
bool refs(const RefsRequest &Req,
3737
llvm::function_ref<void(const Ref &)> Callback) const override;
38+
/// Query all indexes while prioritizing the associated one (if any).
39+
bool refersTo(
40+
const RefsRequest &Req,
41+
llvm::function_ref<void(const RefersToResult &)> Callback) const override;
3842

3943
/// Queries only the associates index when Req.RestrictForCodeCompletion is
4044
/// set, otherwise queries all.
@@ -94,6 +98,15 @@ bool ProjectAwareIndex::refs(
9498
return false;
9599
}
96100

101+
bool ProjectAwareIndex::refersTo(
102+
const RefsRequest &Req,
103+
llvm::function_ref<void(const RefersToResult &)> Callback) const {
104+
trace::Span Tracer("ProjectAwareIndex::refersTo");
105+
if (auto *Idx = getIndex())
106+
return Idx->refersTo(Req, Callback);
107+
return false;
108+
}
109+
97110
bool ProjectAwareIndex::fuzzyFind(
98111
const FuzzyFindRequest &Req,
99112
llvm::function_ref<void(const Symbol &)> Callback) const {

clang-tools-extra/clangd/index/dex/Dex.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ void Dex::buildIndex() {
147147
for (DocID SymbolRank = 0; SymbolRank < Symbols.size(); ++SymbolRank)
148148
Builder.add(*Symbols[SymbolRank], SymbolRank);
149149
InvertedIndex = std::move(Builder).build();
150+
151+
// Build RevRefs
152+
for (const auto &Pair : Refs)
153+
for (const auto &R : Pair.second)
154+
RevRefs[R.Container].emplace_back(R, Pair.first);
150155
}
151156

152157
std::unique_ptr<Iterator> Dex::iterator(const Token &Tok) const {
@@ -314,6 +319,23 @@ bool Dex::refs(const RefsRequest &Req,
314319
return false; // We reported all refs.
315320
}
316321

322+
bool Dex::refersTo(
323+
const RefsRequest &Req,
324+
llvm::function_ref<void(const RefersToResult &)> Callback) const {
325+
trace::Span Tracer("Dex reversed refs");
326+
uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
327+
for (const auto &ID : Req.IDs)
328+
for (const auto &Rev : RevRefs.lookup(ID)) {
329+
if (!static_cast<int>(Req.Filter & Rev.ref().Kind))
330+
continue;
331+
if (Remaining == 0)
332+
return true; // More refs were available.
333+
--Remaining;
334+
Callback(Rev.refersToResult());
335+
}
336+
return false; // We reported all refs.
337+
}
338+
317339
void Dex::relations(
318340
const RelationsRequest &Req,
319341
llvm::function_ref<void(const SymbolID &, const Symbol &)> Callback) const {
@@ -350,6 +372,9 @@ size_t Dex::estimateMemoryUsage() const {
350372
for (const auto &TokenToPostingList : InvertedIndex)
351373
Bytes += TokenToPostingList.second.bytes();
352374
Bytes += Refs.getMemorySize();
375+
Bytes += RevRefs.getMemorySize();
376+
for (const auto &Entry : RevRefs)
377+
Bytes += Entry.second.size() * sizeof(Entry.second.front());
353378
Bytes += Relations.getMemorySize();
354379
return Bytes + BackingDataSize;
355380
}

0 commit comments

Comments
 (0)