-
Notifications
You must be signed in to change notification settings - Fork 13.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Revert "[clangd] Support outgoing calls in call hierarchy (#77556)" #117668
Merged
HighCommander4
merged 1 commit into
llvm:main
from
HighCommander4:revert-call-hierarchy
Nov 26, 2024
Merged
Revert "[clangd] Support outgoing calls in call hierarchy (#77556)" #117668
HighCommander4
merged 1 commit into
llvm:main
from
HighCommander4:revert-call-hierarchy
Nov 26, 2024
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This reverts commit ca184cf.
Reverting #77556 due to build breakage. |
@llvm/pr-subscribers-clang-tools-extra @llvm/pr-subscribers-clangd Author: Nathan Ridge (HighCommander4) ChangesThis reverts commit ca184cf. Patch is 47.40 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/117668.diff 23 Files Affected:
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 1391dcaa0c81a4..05dd313d0a0d35 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -1415,12 +1415,6 @@ void ClangdLSPServer::onInlayHint(const InlayHintsParams &Params,
std::move(Reply));
}
-void ClangdLSPServer::onCallHierarchyOutgoingCalls(
- const CallHierarchyOutgoingCallsParams &Params,
- Callback<std::vector<CallHierarchyOutgoingCall>> Reply) {
- Server->outgoingCalls(Params.item, std::move(Reply));
-}
-
void ClangdLSPServer::applyConfiguration(
const ConfigurationSettings &Settings) {
// Per-file update to the compilation database.
@@ -1699,7 +1693,6 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind,
Bind.method("typeHierarchy/subtypes", this, &ClangdLSPServer::onSubTypes);
Bind.method("textDocument/prepareCallHierarchy", this, &ClangdLSPServer::onPrepareCallHierarchy);
Bind.method("callHierarchy/incomingCalls", this, &ClangdLSPServer::onCallHierarchyIncomingCalls);
- Bind.method("callHierarchy/outgoingCalls", this, &ClangdLSPServer::onCallHierarchyOutgoingCalls);
Bind.method("textDocument/selectionRange", this, &ClangdLSPServer::onSelectionRange);
Bind.method("textDocument/documentLink", this, &ClangdLSPServer::onDocumentLink);
Bind.method("textDocument/semanticTokens/full", this, &ClangdLSPServer::onSemanticTokens);
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index 597fd9de7ff688..0b8e4720f53236 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -156,9 +156,6 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
void onCallHierarchyIncomingCalls(
const CallHierarchyIncomingCallsParams &,
Callback<std::vector<CallHierarchyIncomingCall>>);
- void onCallHierarchyOutgoingCalls(
- const CallHierarchyOutgoingCallsParams &,
- Callback<std::vector<CallHierarchyOutgoingCall>>);
void onClangdInlayHints(const InlayHintsParams &,
Callback<llvm::json::Value>);
void onInlayHint(const InlayHintsParams &, Callback<std::vector<InlayHint>>);
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 63f83bc36f0c69..9b38be04e7ddd7 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -912,15 +912,6 @@ void ClangdServer::inlayHints(PathRef File, std::optional<Range> RestrictRange,
WorkScheduler->runWithAST("InlayHints", File, std::move(Action), Transient);
}
-void ClangdServer::outgoingCalls(
- const CallHierarchyItem &Item,
- Callback<std::vector<CallHierarchyOutgoingCall>> CB) {
- WorkScheduler->run("Outgoing Calls", "",
- [CB = std::move(CB), Item, this]() mutable {
- CB(clangd::outgoingCalls(Item, Index));
- });
-}
-
void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
// FIXME: Do nothing for now. This will be used for indexing and potentially
// invalidating other caches.
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 8b6618cf96ccfc..a653cdb56b751b 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -292,10 +292,6 @@ class ClangdServer {
void incomingCalls(const CallHierarchyItem &Item,
Callback<std::vector<CallHierarchyIncomingCall>>);
- /// Resolve outgoing calls for a given call hierarchy item.
- void outgoingCalls(const CallHierarchyItem &Item,
- Callback<std::vector<CallHierarchyOutgoingCall>>);
-
/// Resolve inlay hints for a given document.
void inlayHints(PathRef File, std::optional<Range> RestrictRange,
Callback<std::vector<InlayHint>>);
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index d237d95b3eb655..61fa66180376cd 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1702,7 +1702,6 @@ declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
HierarchyItem HI;
HI.name = printName(Ctx, ND);
- // FIXME: Populate HI.detail the way we do in symbolToHierarchyItem?
HI.kind = SK;
HI.range = Range{sourceLocToPosition(SM, DeclRange->getBegin()),
sourceLocToPosition(SM, DeclRange->getEnd())};
@@ -1754,7 +1753,6 @@ static std::optional<HierarchyItem> symbolToHierarchyItem(const Symbol &S,
}
HierarchyItem HI;
HI.name = std::string(S.Name);
- HI.detail = (S.Scope + S.Name).str();
HI.kind = indexSymbolKindToSymbolKind(S.SymInfo.Kind);
HI.selectionRange = Loc->range;
// FIXME: Populate 'range' correctly
@@ -2321,63 +2319,6 @@ incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
return Results;
}
-std::vector<CallHierarchyOutgoingCall>
-outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) {
- std::vector<CallHierarchyOutgoingCall> Results;
- if (!Index || Item.data.empty())
- return Results;
- auto ID = SymbolID::fromStr(Item.data);
- if (!ID) {
- elog("outgoingCalls failed to find symbol: {0}", ID.takeError());
- return Results;
- }
- // In this function, we find outgoing calls based on the index only.
- ContainedRefsRequest Request;
- Request.ID = *ID;
- // Initially store the ranges in a map keyed by SymbolID of the callee.
- // This allows us to group different calls to the same function
- // into the same CallHierarchyOutgoingCall.
- llvm::DenseMap<SymbolID, std::vector<Range>> CallsOut;
- // We can populate the ranges based on a refs request only. As we do so, we
- // also accumulate the callee IDs into a lookup request.
- LookupRequest CallsOutLookup;
- Index->containedRefs(Request, [&](const auto &R) {
- auto Loc = indexToLSPLocation(R.Location, Item.uri.file());
- if (!Loc) {
- elog("outgoingCalls failed to convert location: {0}", Loc.takeError());
- return;
- }
- auto It = CallsOut.try_emplace(R.Symbol, std::vector<Range>{}).first;
- It->second.push_back(Loc->range);
-
- CallsOutLookup.IDs.insert(R.Symbol);
- });
- // Perform the lookup request and combine its results with CallsOut to
- // get complete CallHierarchyOutgoingCall objects.
- Index->lookup(CallsOutLookup, [&](const Symbol &Callee) {
- // The containedRefs request should only return symbols which are
- // function-like, i.e. symbols for which references to them can be "calls".
- using SK = index::SymbolKind;
- auto Kind = Callee.SymInfo.Kind;
- assert(Kind == SK::Function || Kind == SK::InstanceMethod ||
- Kind == SK::ClassMethod || Kind == SK::StaticMethod ||
- Kind == SK::Constructor || Kind == SK::Destructor ||
- Kind == SK::ConversionFunction);
-
- auto It = CallsOut.find(Callee.ID);
- assert(It != CallsOut.end());
- if (auto CHI = symbolToCallHierarchyItem(Callee, Item.uri.file()))
- Results.push_back(
- CallHierarchyOutgoingCall{std::move(*CHI), std::move(It->second)});
- });
- // Sort results by name of the callee.
- llvm::sort(Results, [](const CallHierarchyOutgoingCall &A,
- const CallHierarchyOutgoingCall &B) {
- return A.to.name < B.to.name;
- });
- return Results;
-}
-
llvm::DenseSet<const Decl *> getNonLocalDeclRefs(ParsedAST &AST,
const FunctionDecl *FD) {
if (!FD->hasBody())
diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index 247e52314c3f94..df91dd15303c18 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -150,9 +150,6 @@ prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath);
std::vector<CallHierarchyIncomingCall>
incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index);
-std::vector<CallHierarchyOutgoingCall>
-outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index);
-
/// Returns all decls that are referenced in the \p FD except local symbols.
llvm::DenseSet<const Decl *> getNonLocalDeclRefs(ParsedAST &AST,
const FunctionDecl *FD);
diff --git a/clang-tools-extra/clangd/index/Index.cpp b/clang-tools-extra/clangd/index/Index.cpp
index 86dc6ed7633449..7a0c23287db225 100644
--- a/clang-tools-extra/clangd/index/Index.cpp
+++ b/clang-tools-extra/clangd/index/Index.cpp
@@ -66,11 +66,6 @@ bool SwapIndex::refs(const RefsRequest &R,
llvm::function_ref<void(const Ref &)> CB) const {
return snapshot()->refs(R, CB);
}
-bool SwapIndex::containedRefs(
- const ContainedRefsRequest &R,
- llvm::function_ref<void(const ContainedRefsResult &)> CB) const {
- return snapshot()->containedRefs(R, CB);
-}
void SwapIndex::relations(
const RelationsRequest &R,
llvm::function_ref<void(const SymbolID &, const Symbol &)> CB) const {
diff --git a/clang-tools-extra/clangd/index/Index.h b/clang-tools-extra/clangd/index/Index.h
index a193b1a191216a..047ce08e93e3ab 100644
--- a/clang-tools-extra/clangd/index/Index.h
+++ b/clang-tools-extra/clangd/index/Index.h
@@ -77,19 +77,6 @@ struct RefsRequest {
bool WantContainer = false;
};
-struct ContainedRefsRequest {
- /// Note that RefKind::Call just restricts the matched SymbolKind to
- /// functions, not the form of the reference (e.g. address-of-function,
- /// which can indicate an indirect call, should still be caught).
- static const RefKind SupportedRefKinds = RefKind::Call;
-
- SymbolID ID;
- /// If set, limit the number of refers returned from the index. The index may
- /// choose to return less than this, e.g. it tries to avoid returning stale
- /// results.
- std::optional<uint32_t> Limit;
-};
-
struct RelationsRequest {
llvm::DenseSet<SymbolID> Subjects;
RelationKind Predicate;
@@ -97,14 +84,6 @@ struct RelationsRequest {
std::optional<uint32_t> Limit;
};
-struct ContainedRefsResult {
- /// The source location where the symbol is named.
- SymbolLocation Location;
- RefKind Kind = RefKind::Unknown;
- /// The ID of the symbol which is referred to
- SymbolID Symbol;
-};
-
/// Describes what data is covered by an index.
///
/// Indexes may contain symbols but not references from a file, etc.
@@ -162,17 +141,6 @@ class SymbolIndex {
virtual bool refs(const RefsRequest &Req,
llvm::function_ref<void(const Ref &)> Callback) const = 0;
- /// Find all symbols that are referenced by a symbol and apply
- /// \p Callback on each result.
- ///
- /// Results should be returned in arbitrary order.
- /// The returned result must be deep-copied if it's used outside Callback.
- ///
- /// Returns true if there will be more results (limited by Req.Limit);
- virtual bool containedRefs(
- const ContainedRefsRequest &Req,
- llvm::function_ref<void(const ContainedRefsResult &)> Callback) const = 0;
-
/// Finds all relations (S, P, O) stored in the index such that S is among
/// Req.Subjects and P is Req.Predicate, and invokes \p Callback for (S, O) in
/// each.
@@ -207,9 +175,6 @@ class SwapIndex : public SymbolIndex {
llvm::function_ref<void(const Symbol &)>) const override;
bool refs(const RefsRequest &,
llvm::function_ref<void(const Ref &)>) const override;
- bool containedRefs(
- const ContainedRefsRequest &,
- llvm::function_ref<void(const ContainedRefsResult &)>) const override;
void relations(const RelationsRequest &,
llvm::function_ref<void(const SymbolID &, const Symbol &)>)
const override;
diff --git a/clang-tools-extra/clangd/index/MemIndex.cpp b/clang-tools-extra/clangd/index/MemIndex.cpp
index 9c9d3942bdee63..2665d46b97d839 100644
--- a/clang-tools-extra/clangd/index/MemIndex.cpp
+++ b/clang-tools-extra/clangd/index/MemIndex.cpp
@@ -9,7 +9,6 @@
#include "MemIndex.h"
#include "FuzzyMatch.h"
#include "Quality.h"
-#include "index/Index.h"
#include "support/Trace.h"
namespace clang {
@@ -86,25 +85,6 @@ bool MemIndex::refs(const RefsRequest &Req,
return false; // We reported all refs.
}
-bool MemIndex::containedRefs(
- const ContainedRefsRequest &Req,
- llvm::function_ref<void(const ContainedRefsResult &)> Callback) const {
- trace::Span Tracer("MemIndex refersTo");
- uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
- for (const auto &Pair : Refs) {
- for (const auto &R : Pair.second) {
- if (!static_cast<int>(ContainedRefsRequest::SupportedRefKinds & R.Kind) ||
- Req.ID != R.Container)
- continue;
- if (Remaining == 0)
- return true; // More refs were available.
- --Remaining;
- Callback({R.Location, R.Kind, Pair.first});
- }
- }
- return false; // We reported all refs.
-}
-
void MemIndex::relations(
const RelationsRequest &Req,
llvm::function_ref<void(const SymbolID &, const Symbol &)> Callback) const {
diff --git a/clang-tools-extra/clangd/index/MemIndex.h b/clang-tools-extra/clangd/index/MemIndex.h
index 8f390c5028dc4d..fba2c1a7120a2b 100644
--- a/clang-tools-extra/clangd/index/MemIndex.h
+++ b/clang-tools-extra/clangd/index/MemIndex.h
@@ -72,10 +72,6 @@ class MemIndex : public SymbolIndex {
bool refs(const RefsRequest &Req,
llvm::function_ref<void(const Ref &)> Callback) const override;
- bool containedRefs(const ContainedRefsRequest &Req,
- llvm::function_ref<void(const ContainedRefsResult &)>
- Callback) const override;
-
void relations(const RelationsRequest &Req,
llvm::function_ref<void(const SymbolID &, const Symbol &)>
Callback) const override;
diff --git a/clang-tools-extra/clangd/index/Merge.cpp b/clang-tools-extra/clangd/index/Merge.cpp
index aecca38a885b66..8221d4b1f44405 100644
--- a/clang-tools-extra/clangd/index/Merge.cpp
+++ b/clang-tools-extra/clangd/index/Merge.cpp
@@ -155,40 +155,6 @@ bool MergedIndex::refs(const RefsRequest &Req,
return More || StaticHadMore;
}
-bool MergedIndex::containedRefs(
- const ContainedRefsRequest &Req,
- llvm::function_ref<void(const ContainedRefsResult &)> Callback) const {
- trace::Span Tracer("MergedIndex refersTo");
- bool More = false;
- uint32_t Remaining = Req.Limit.value_or(std::numeric_limits<uint32_t>::max());
- // We don't want duplicated refs from the static/dynamic indexes,
- // and we can't reliably deduplicate them because offsets may differ slightly.
- // We consider the dynamic index authoritative and report all its refs,
- // and only report static index refs from other files.
- More |= Dynamic->containedRefs(Req, [&](const auto &O) {
- Callback(O);
- assert(Remaining != 0);
- --Remaining;
- });
- if (Remaining == 0 && More)
- return More;
- auto DynamicContainsFile = Dynamic->indexedFiles();
- // We return less than Req.Limit if static index returns more refs for dirty
- // files.
- bool StaticHadMore = Static->containedRefs(Req, [&](const auto &O) {
- if ((DynamicContainsFile(O.Location.FileURI) & IndexContents::References) !=
- IndexContents::None)
- return; // ignore refs that have been seen from dynamic index.
- if (Remaining == 0) {
- More = true;
- return;
- }
- --Remaining;
- Callback(O);
- });
- return More || StaticHadMore;
-}
-
llvm::unique_function<IndexContents(llvm::StringRef) const>
MergedIndex::indexedFiles() const {
return [DynamicContainsFile{Dynamic->indexedFiles()},
diff --git a/clang-tools-extra/clangd/index/Merge.h b/clang-tools-extra/clangd/index/Merge.h
index 7441be6e57e854..b8a562b0df5d92 100644
--- a/clang-tools-extra/clangd/index/Merge.h
+++ b/clang-tools-extra/clangd/index/Merge.h
@@ -38,9 +38,6 @@ class MergedIndex : public SymbolIndex {
llvm::function_ref<void(const Symbol &)>) const override;
bool refs(const RefsRequest &,
llvm::function_ref<void(const Ref &)>) const override;
- bool containedRefs(
- const ContainedRefsRequest &,
- llvm::function_ref<void(const ContainedRefsResult &)>) const override;
void relations(const RelationsRequest &,
llvm::function_ref<void(const SymbolID &, const Symbol &)>)
const override;
diff --git a/clang-tools-extra/clangd/index/ProjectAware.cpp b/clang-tools-extra/clangd/index/ProjectAware.cpp
index 9836f0130362a0..2c6f8273b35d0e 100644
--- a/clang-tools-extra/clangd/index/ProjectAware.cpp
+++ b/clang-tools-extra/clangd/index/ProjectAware.cpp
@@ -35,10 +35,6 @@ class ProjectAwareIndex : public SymbolIndex {
/// Query all indexes while prioritizing the associated one (if any).
bool refs(const RefsRequest &Req,
llvm::function_ref<void(const Ref &)> Callback) const override;
- /// Query all indexes while prioritizing the associated one (if any).
- bool containedRefs(const ContainedRefsRequest &Req,
- llvm::function_ref<void(const ContainedRefsResult &)>
- Callback) const override;
/// Queries only the associates index when Req.RestrictForCodeCompletion is
/// set, otherwise queries all.
@@ -98,15 +94,6 @@ bool ProjectAwareIndex::refs(
return false;
}
-bool ProjectAwareIndex::containedRefs(
- const ContainedRefsRequest &Req,
- llvm::function_ref<void(const ContainedRefsResult &)> Callback) const {
- trace::Span Tracer("ProjectAwareIndex::refersTo");
- if (auto *Idx = getIndex())
- return Idx->containedRefs(Req, Callback);
- return false;
-}
-
bool ProjectAwareIndex::fuzzyFind(
const FuzzyFindRequest &Req,
llvm::function_ref<void(const Symbol &)> Callback) const {
diff --git a/clang-tools-extra/clangd/index/Ref.h b/clang-tools-extra/clangd/index/Ref.h
index 870f77f56e6cb3..6e383e2ade3d25 100644
--- a/clang-tools-extra/clangd/index/Ref.h
+++ b/clang-tools-extra/clangd/index/Ref.h
@@ -63,9 +63,6 @@ enum class RefKind : uint8_t {
// ^ this references Foo, but does not explicitly spell out its name
// };
Spelled = 1 << 3,
- // A reference which is a call. Used as a filter for which references
- // to store in data structures used for computing outgoing calls.
- Call = 1 << 4,
All = Declaration | Definition | Reference | Spelled,
};
diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp
index 81125dbb1aeafc..91ae9d3003a971 100644
--- a/clang-tools-extra/clangd/index/SymbolCollector.cpp
+++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp
@@ -18,7 +18,6 @@
#include "clang-include-cleaner/Record.h"
#include "clang-include-cleaner/Types.h"
#include "index/CanonicalIncludes.h"
-#include "index/Ref.h"
#include "index/Relation.h"
#include "index/Symbol.h"
#include "index/SymbolID.h"
@@ -661,7 +660,7 @@ bool SymbolCollector::handleDeclOccurrence(
auto FileLoc = SM.getFileLoc(Loc);
auto FID = SM.getFileID(FileLoc);
if (Opts.RefsInHeaders || FID == SM.getMainFileID()) {
- addRef(ID, SymbolRef{FileLoc, FID, Roles, index::getSymbolInfo(ND).Kind,
+ addRef(ID, SymbolRef{FileLoc, FID, Roles,
getRefContainer(ASTNode.Parent, Opts),
isSpelled(FileLoc, *ND)});
}
@@ -775,10 +774,8 @@ bool SymbolCollector::handleMacroOccurrence(const IdentifierInfo *Name,
// FIXME: Populate container information for macro references.
// FIXME: All MacroRefs are marked as Spelled now, but this should be
// checked.
- addRef(ID,
- SymbolRef{Loc, SM.getFileID(Loc), Roles, index::SymbolKind::Macro,
- /*Container=*/nullptr,
- /*Spelled=*/true});
+ addRef(ID, SymbolRef{Loc, SM.getFileID(Loc), Roles, /*Container=*/nullptr,
+ /*Spelled=*/true});
}
// Collect symbols.
@@ -1169,14 +1166,6 @@ bool SymbolCollector::shouldIndexFile(FileID FID) {
return I.first->second;
}
-static bool refIsCall(index::SymbolKind Kind) {
- using SK = index::SymbolKind;
- return Kind == SK::Function || Kind == SK::InstanceMethod ||
- Kind == SK::ClassMethod || Kind == SK::StaticMethod ||
- Kind == SK::Constructor || Kind == SK::Destructor ||
- ...
[truncated]
|
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This reverts commit ca184cf.