diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -115,6 +115,15 @@ Callback>); void onResolveTypeHierarchy(const ResolveTypeHierarchyItemParams &, Callback>); + void onPrepareCallHierarchy( + const CallHierarchyPrepareParams &, + Callback>>); + void onCallHierarchyIncomingCalls( + const CallHierarchyIncomingCallsParams &, + Callback>>); + void onCallHierarchyOutgoingCalls( + const CallHierarchyOutgoingCallsParams &, + Callback>>); void onChangeConfiguration(const DidChangeConfigurationParams &); void onSymbolInfo(const TextDocumentPositionParams &, Callback>); diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -627,6 +627,7 @@ ExecuteCommandParams::CLANGD_APPLY_TWEAK}}, }}, {"typeHierarchyProvider", true}, + {"callHierarchyProvider", true}, }}}}; if (NegotiatedOffsetEncoding) Result["offsetEncoding"] = *NegotiatedOffsetEncoding; @@ -1190,6 +1191,25 @@ std::move(Reply)); } +void ClangdLSPServer::onPrepareCallHierarchy( + const CallHierarchyPrepareParams &Params, + Callback>> Reply) { + Server->prepareCallHierarchy(Params.textDocument.uri.file(), Params.position, + std::move(Reply)); +} + +void ClangdLSPServer::onCallHierarchyIncomingCalls( + const CallHierarchyIncomingCallsParams &Params, + Callback>> Reply) { + Server->incomingCalls(Params.Item, std::move(Reply)); +} + +void ClangdLSPServer::onCallHierarchyOutgoingCalls( + const CallHierarchyOutgoingCallsParams &Params, + Callback>> Reply) { + Server->outgoingCalls(Params.Item, std::move(Reply)); +} + void ClangdLSPServer::applyConfiguration( const ConfigurationSettings &Settings) { // Per-file update to the compilation database. @@ -1391,6 +1411,9 @@ MsgHandler->bind("textDocument/symbolInfo", &ClangdLSPServer::onSymbolInfo); MsgHandler->bind("textDocument/typeHierarchy", &ClangdLSPServer::onTypeHierarchy); MsgHandler->bind("typeHierarchy/resolve", &ClangdLSPServer::onResolveTypeHierarchy); + MsgHandler->bind("textDocument/prepareCallHierarchy", &ClangdLSPServer::onPrepareCallHierarchy); + MsgHandler->bind("callHierarchy/incomingCalls", &ClangdLSPServer::onCallHierarchyIncomingCalls); + MsgHandler->bind("callHierarchy/outgoingCalls", &ClangdLSPServer::onCallHierarchyOutgoingCalls); MsgHandler->bind("textDocument/selectionRange", &ClangdLSPServer::onSelectionRange); MsgHandler->bind("textDocument/documentLink", &ClangdLSPServer::onDocumentLink); MsgHandler->bind("textDocument/semanticTokens/full", &ClangdLSPServer::onSemanticTokens); diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -244,6 +244,21 @@ TypeHierarchyDirection Direction, Callback> CB); + /// Get information about call hierarchy for a given position. + void prepareCallHierarchy( + PathRef File, Position Pos, + Callback>> CB); + + /// Resolve incoming calls for a given call hierarchy item. + void incomingCalls( + const CallHierarchyItem &Item, + Callback>>); + + /// Resolve outgoing calls for a given call hierarchy item. + void outgoingCalls( + const CallHierarchyItem &Item, + Callback>>); + /// Retrieve the top symbols from the workspace matching a query. void workspaceSymbols(StringRef Query, int Limit, Callback> CB); diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -683,6 +683,30 @@ CB(Item); } +void ClangdServer::prepareCallHierarchy( + PathRef File, Position Pos, + Callback>> CB) { + auto Action = [File = File.str(), Pos, CB = std::move(CB), + this](Expected InpAST) mutable { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::prepareCallHierarchy(InpAST->AST, Pos, Index, File)); + }; + WorkScheduler.runWithAST("Call Hierarchy", File, std::move(Action)); +} + +void ClangdServer::incomingCalls( + const CallHierarchyItem &Item, + Callback>> CB) { + CB(clangd::incomingCalls(Item, Index)); +} + +void ClangdServer::outgoingCalls( + const CallHierarchyItem &Item, + Callback>> CB) { + 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/Protocol.h b/clang-tools-extra/clangd/Protocol.h --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -1341,6 +1341,77 @@ }; bool fromJSON(const llvm::json::Value &, ResolveTypeHierarchyItemParams &); +enum class SymbolTag { Deprecated = 1 }; +llvm::json::Value toJSON(SymbolTag); + +/// Represents programming constructs like functions or constructors +/// in the context of call hierarchy. +struct CallHierarchyItem { + /// The name of this item. + std::string Name; + + /// The kind of this item. + SymbolKind Kind; + + /// Tags for this item. + std::vector Tags; + + /// More detaill for this item, e.g. the signature of a function. + std::string Detail; + + /// The resource identifier of this item. + URIForFile Uri; + + /// The range enclosing this symbol not including leading / trailing + /// whitespace but everything else, e.g. comments and code. + Range Rng; + + /// The range that should be selected and revealed when this symbol + /// is being picked, e.g. the name of a function. + /// Must be contained by `Rng`. + Range SelectionRange; +}; +llvm::json::Value toJSON(const CallHierarchyItem &); +bool fromJSON(const llvm::json::Value &, CallHierarchyItem &); + +/// Represents an incoming call, e.g. a caller of a method or constructor. +struct CallHierarchyIncomingCall { + /// The item that makes the call. + CallHierarchyItem From; + + /// The range at which the calls appear. + /// This is relative to the caller denoted by `From`. + std::vector FromRanges; +}; +llvm::json::Value toJSON(const CallHierarchyIncomingCall &); + +/// Represents an outgoing call, e.g. calling a getter from a method or +/// a method from a constructor etc. +struct CallHierarchyOutgoingCall { + /// The item that is called. + CallHierarchyItem To; + + /// The range at which this item is called. + /// This is the range relative to the caller, and not `To`. + std::vector FromRanges; +}; +llvm::json::Value toJSON(const CallHierarchyOutgoingCall &); + +/// The parameter of a `textDocument/prepareCallHierarchy` request. +struct CallHierarchyPrepareParams : public TextDocumentPositionParams {}; + +/// The parameter of a `callHierarchy/incomingCalls` request. +struct CallHierarchyIncomingCallsParams { + CallHierarchyItem Item; +}; +bool fromJSON(const llvm::json::Value &, CallHierarchyIncomingCallsParams &); + +/// The parameter of a `callHierarchy/outgoingCalls` request. +struct CallHierarchyOutgoingCallsParams { + CallHierarchyItem Item; +}; +bool fromJSON(const llvm::json::Value &, CallHierarchyOutgoingCallsParams &); + struct ReferenceParams : public TextDocumentPositionParams { // For now, no options like context.includeDeclaration are supported. }; diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -464,7 +464,7 @@ llvm::json::ObjectMapper O(Params); if (!O) return false; - O.map("forceRebuild", R.forceRebuild); // Optional clangd extension. + O.map("forceRebuild", R.forceRebuild); // Optional clangd extension. return O.map("textDocument", R.textDocument) && O.map("contentChanges", R.contentChanges) && O.map("wantDiagnostics", R.wantDiagnostics); @@ -1167,6 +1167,49 @@ return fromJSON(Params, Base); } +llvm::json::Value toJSON(SymbolTag Tag) { + return llvm::json::Value{static_cast(Tag)}; +} + +llvm::json::Value toJSON(const CallHierarchyItem &I) { + return llvm::json::Object{ + {"name", I.Name}, {"kind", static_cast(I.Kind)}, + {"tags", I.Tags}, {"detail", I.Detail}, + {"range", I.Rng}, {"selectionRange", I.SelectionRange}, + {"uri", I.Uri}}; +} + +bool fromJSON(const llvm::json::Value &Params, CallHierarchyItem &I) { + llvm::json::ObjectMapper O(Params); + + // Populate the required fields only. We don't care about the + // optional fields `Tags` and `Detail` for the purpose of + // client --> server communication. + return O && O.map("name", I.Name) && O.map("kind", I.Kind) && + O.map("uri", I.Uri) && O.map("range", I.Rng) && + O.map("selectionRange", I.SelectionRange); +} + +llvm::json::Value toJSON(const CallHierarchyIncomingCall &C) { + return llvm::json::Object{{"from", C.From}, {"fromRanges", C.FromRanges}}; +} + +llvm::json::Value toJSON(const CallHierarchyOutgoingCall &C) { + return llvm::json::Object{{"from", C.To}, {"fromRanges", C.FromRanges}}; +} + +bool fromJSON(const llvm::json::Value &Params, + CallHierarchyIncomingCallsParams &P) { + llvm::json::ObjectMapper O(Params); + return O.map("item", P.Item); +} + +bool fromJSON(const llvm::json::Value &Params, + CallHierarchyOutgoingCallsParams &P) { + llvm::json::ObjectMapper O(Params); + return O.map("item", P.Item); +} + static const char *toString(OffsetEncoding OE) { switch (OE) { case OffsetEncoding::UTF8: diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h --- a/clang-tools-extra/clangd/XRefs.h +++ b/clang-tools-extra/clangd/XRefs.h @@ -105,6 +105,17 @@ TypeHierarchyDirection Direction, const SymbolIndex *Index); +/// Get call hierarchy information at \p Pos. +llvm::Optional> +prepareCallHierarchy(ParsedAST &AST, Position Pos, const SymbolIndex *Index, + PathRef TUPath); + +llvm::Optional> +incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index); + +llvm::Optional> +outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index); + /// Returns all decls that are referenced in the \p FD except local symbols. llvm::DenseSet getNonLocalDeclRefs(ParsedAST &AST, const FunctionDecl *FD); diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -5,7 +5,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -#include "XRefs.h" #include "AST.h" #include "CodeCompletionStrings.h" #include "FindSymbols.h" @@ -16,6 +15,7 @@ #include "Selection.h" #include "SourceCode.h" #include "URI.h" +#include "XRefs.h" #include "index/Index.h" #include "index/Merge.h" #include "index/Relation.h" @@ -47,6 +47,7 @@ #include "clang/Index/USRGeneration.h" #include "clang/Tooling/Syntax/Tokens.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/MapVector.h" #include "llvm/ADT/None.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" @@ -1312,9 +1313,8 @@ return THI; } -static Optional -symbolToTypeHierarchyItem(const Symbol &S, const SymbolIndex *Index, - PathRef TUPath) { +static Optional symbolToTypeHierarchyItem(const Symbol &S, + PathRef TUPath) { auto Loc = symbolToLocation(S, TUPath); if (!Loc) { log("Type hierarchy: {0}", Loc.takeError()); @@ -1345,7 +1345,7 @@ Req.Predicate = RelationKind::BaseOf; Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) { if (Optional ChildSym = - symbolToTypeHierarchyItem(Object, Index, TUPath)) { + symbolToTypeHierarchyItem(Object, TUPath)) { if (Levels > 1) { ChildSym->children.emplace(); fillSubTypes(Object.ID, *ChildSym->children, Index, Levels - 1, TUPath); @@ -1539,6 +1539,199 @@ } } +static llvm::Optional +declToCallHierarchyItem(ASTContext &Ctx, const NamedDecl &ND) { + auto &SM = Ctx.getSourceManager(); + SourceLocation NameLoc = nameLocation(ND, Ctx.getSourceManager()); + SourceLocation BeginLoc = SM.getSpellingLoc(SM.getFileLoc(ND.getBeginLoc())); + SourceLocation EndLoc = SM.getSpellingLoc(SM.getFileLoc(ND.getEndLoc())); + const auto DeclRange = + toHalfOpenFileRange(SM, Ctx.getLangOpts(), {BeginLoc, EndLoc}); + if (!DeclRange) + return llvm::None; + auto FilePath = + getCanonicalPath(SM.getFileEntryForID(SM.getFileID(NameLoc)), SM); + auto TUPath = getCanonicalPath(SM.getFileEntryForID(SM.getMainFileID()), SM); + if (!FilePath || !TUPath) + return llvm::None; // Not useful without a uri. + + Position NameBegin = sourceLocToPosition(SM, NameLoc); + Position NameEnd = sourceLocToPosition( + SM, Lexer::getLocForEndOfToken(NameLoc, 0, SM, Ctx.getLangOpts())); + + index::SymbolInfo SymInfo = index::getSymbolInfo(&ND); + // FIXME: this is not classifying constructors, destructors and operators + // correctly (they're all "methods"). + SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind); + + CallHierarchyItem CHI; + // We need to print the fully qualified name, otherwise we can't recover + // the symbol from the CallHierarchyItem. + CHI.Name = printQualifiedName(ND); + CHI.Kind = SK; + if (ND.isDeprecated()) { + CHI.Tags.push_back(SymbolTag::Deprecated); + } + CHI.Rng = Range{sourceLocToPosition(SM, DeclRange->getBegin()), + sourceLocToPosition(SM, DeclRange->getEnd())}; + CHI.SelectionRange = Range{NameBegin, NameEnd}; + if (!CHI.Rng.contains(CHI.Rng)) { + // 'selectionRange' must be contained in 'range', so in cases where clang + // reports unrelated ranges we need to reconcile somehow. + CHI.Rng = CHI.SelectionRange; + } + + CHI.Uri = URIForFile::canonicalize(*FilePath, *TUPath); + + return CHI; +} + +llvm::Optional> +prepareCallHierarchy(ParsedAST &AST, Position Pos, const SymbolIndex *Index, + PathRef TUPath) { + const auto &SM = AST.getSourceManager(); + auto Loc = sourceLocationInMainFile(SM, Pos); + if (!Loc) { + llvm::consumeError(Loc.takeError()); + return llvm::None; + } + std::vector Result; + for (const NamedDecl *Decl : getDeclAtPosition(AST, *Loc, {})) { + if (Decl->isFunctionOrFunctionTemplate()) { + if (auto CHI = declToCallHierarchyItem(AST.getASTContext(), *Decl)) + Result.push_back(*std::move(CHI)); + } + } + return Result; +} + +static llvm::Optional +callHierarchyItemToSymbol(const CallHierarchyItem &Item, + const SymbolIndex *Index) { + FuzzyFindRequest Request; + auto Split = splitQualifiedName(Item.Name); + Request.Query = std::string(Split.second); + if (!Split.first.empty()) + Request.Scopes = {std::string(Split.first)}; + // Explain. + Request.AnyScope = true; + llvm::Optional Result; + bool Found = false; + auto OnFuzzyMatch = [&](const Symbol &S) { + if (Found) + return; + if (S.Name != Split.second) + return; + + auto LocationMatches = [&](const SymbolLocation &L) { + if (!L) + return false; + auto Loc = indexToLSPLocation(L, Item.Uri.file()); + if (Loc) { + vlog("Comparing locations {0}:{1} and {2}:{3}", Loc->uri.uri(), + Loc->range, Item.Uri.uri(), Item.SelectionRange); + } + return Loc && Loc->uri == Item.Uri && Loc->range == Item.SelectionRange; + }; + if (!(LocationMatches(S.Definition) || + LocationMatches(S.CanonicalDeclaration))) + return; + + Result = S; + Found = true; + }; + Index->fuzzyFind(Request, OnFuzzyMatch); + /*if (!Found && !Request.AnyScope) { + // If we didn't find any results when searching with AnyScope=false, + // fall back on trying AnyScope=true. + // This is a workaround for the fact that symbols in anonymous + // namespaces are not found inside a scope that comes from printing + // their qualifier (which omits anonymous namespaces). + Request.AnyScope = true; + Index->fuzzyFind(Request, OnFuzzyMatch); + }*/ + return Result; +} + +llvm::Optional symbolToCallHierarchyItem(const Symbol &S, + PathRef TUPath) { + auto Loc = symbolToLocation(S, TUPath); + if (!Loc) { + log("Type hierarchy: {0}", Loc.takeError()); + return llvm::None; + } + CallHierarchyItem CHI; + CHI.Name = std::string(S.Name); + CHI.Kind = indexSymbolKindToSymbolKind(S.SymInfo.Kind); + if (S.Flags & Symbol::Deprecated) + CHI.Tags.push_back(SymbolTag::Deprecated); + CHI.Detail = S.Signature.str(); + CHI.SelectionRange = Loc->range; + // FIXME: Populate 'range' correctly + // (https://github.com/clangd/clangd/issues/59). + CHI.Rng = CHI.SelectionRange; + CHI.Uri = Loc->uri; + + return std::move(CHI); +} + +llvm::Optional> +incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) { + // Unlike TypeHierarchyItem, CallHierarchyItem does not have a "data" + // field into which we can stash a SymbolID (and we can't unilaterally + // add one because client implementations of call hierarchy, e.g. the + // one in vscode, don't know about it and wouldn't round-trip it). + // So, we have to reconstitute the symbol based on the information + // present in the CallHierarchyItem. + if (!Index) + return llvm::None; + auto Sym = callHierarchyItemToSymbol(Item, Index); + if (!Sym) + return llvm::None; + RefsRequest Request; + Request.IDs.insert(Sym->ID); + // FIXME: Perhaps we should be even more specific and introduce a + // RefKind for calls, and use that? + Request.Filter = RefKind::Reference; + // Initially store the results in a map keyed by SymbolID. + // This allows us to group different calls with the same caller + // into the same CallHierarchyIncomingCall. + llvm::DenseMap ResultMap; + Index->refs(Request, [&](const Ref &R) { + if (auto Loc = indexToLSPLocation(R.Location, Item.Uri.file())) { + LookupRequest Lookup; + Lookup.IDs.insert(R.Referrer); + Index->lookup(Lookup, [&](const Symbol &Caller) { + // See if we already have a CallHierarchyIncomingCall for this caller. + auto It = ResultMap.find(Caller.ID); + if (It == ResultMap.end()) { + // If not, try to create one. + if (auto CHI = symbolToCallHierarchyItem(Caller, Item.Uri.file())) { + CallHierarchyIncomingCall Call; + Call.From = *CHI; + It = ResultMap.insert({Caller.ID, std::move(Call)}).first; + } + } + if (It != ResultMap.end()) { + It->second.FromRanges.push_back(Loc->range); + } + }); + } + }); + // Flatten the results into a vector. + std::vector Results; + for (auto &&Entry : ResultMap) { + Results.push_back(std::move(Entry.second)); + } + return Results; +} + +llvm::Optional> +outgoingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) { + // TODO: Implement. + return llvm::None; +} + llvm::DenseSet getNonLocalDeclRefs(ParsedAST &AST, const FunctionDecl *FD) { if (!FD->hasBody()) diff --git a/clang-tools-extra/clangd/index/Ref.h b/clang-tools-extra/clangd/index/Ref.h --- a/clang-tools-extra/clangd/index/Ref.h +++ b/clang-tools-extra/clangd/index/Ref.h @@ -88,13 +88,16 @@ /// The source location where the symbol is named. SymbolLocation Location; RefKind Kind = RefKind::Unknown; + SymbolID Referrer; }; inline bool operator<(const Ref &L, const Ref &R) { - return std::tie(L.Location, L.Kind) < std::tie(R.Location, R.Kind); + return std::tie(L.Location, L.Kind, L.Referrer) < + std::tie(R.Location, R.Kind, R.Referrer); } inline bool operator==(const Ref &L, const Ref &R) { - return std::tie(L.Location, L.Kind) == std::tie(R.Location, R.Kind); + return std::tie(L.Location, L.Kind, L.Referrer) == + std::tie(R.Location, R.Kind, R.Referrer); } llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Ref &); diff --git a/clang-tools-extra/clangd/index/Serialization.cpp b/clang-tools-extra/clangd/index/Serialization.cpp --- a/clang-tools-extra/clangd/index/Serialization.cpp +++ b/clang-tools-extra/clangd/index/Serialization.cpp @@ -345,6 +345,7 @@ for (const auto &Ref : Refs) { OS.write(static_cast(Ref.Kind)); writeLocation(Ref.Location, Strings, OS); + OS << Ref.Referrer.raw(); } } @@ -356,6 +357,7 @@ for (auto &Ref : Result.second) { Ref.Kind = static_cast(Data.consume8()); Ref.Location = readLocation(Data, Strings); + Ref.Referrer = Data.consumeID(); } return Result; } diff --git a/clang-tools-extra/clangd/index/SymbolCollector.h b/clang-tools-extra/clangd/index/SymbolCollector.h --- a/clang-tools-extra/clangd/index/SymbolCollector.h +++ b/clang-tools-extra/clangd/index/SymbolCollector.h @@ -156,7 +156,11 @@ std::shared_ptr CompletionAllocator; std::unique_ptr CompletionTUInfo; Options Opts; - using SymbolRef = std::pair; + struct SymbolRef { + SourceLocation Loc; + index::SymbolRoleSet Roles; + const NamedDecl *Referrer; + }; // Symbols referenced from the current TU, flushed on finish(). llvm::DenseSet ReferencedDecls; llvm::DenseSet ReferencedMacros; diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -344,7 +344,9 @@ !isa(ND) && (Opts.RefsInHeaders || SM.getFileID(SM.getFileLoc(Loc)) == SM.getMainFileID())) - DeclRefs[ND].emplace_back(SM.getFileLoc(Loc), Roles); + DeclRefs[ND].push_back( + SymbolRef{SM.getFileLoc(Loc), Roles, + dyn_cast_or_null(ASTNode.Parent)}); // Don't continue indexing if this is a mere reference. if (IsOnlyRef) return true; @@ -422,7 +424,8 @@ // Do not store references to main-file macros. if ((static_cast(Opts.RefFilter) & Roles) && !IsMainFileOnly && (Opts.RefsInHeaders || SM.getFileID(SpellingLoc) == SM.getMainFileID())) - MacroRefs[*ID].push_back({Loc, Roles}); + // FIXME: Populate referrer information for macro references. + MacroRefs[*ID].push_back({Loc, Roles, /*Referrer=*/nullptr}); // Collect symbols. if (!Opts.CollectMacro) @@ -582,24 +585,23 @@ } return Found->second; }; - auto CollectRef = - [&](SymbolID ID, - const std::pair &LocAndRole, - bool Spelled = false) { - auto FileID = SM.getFileID(LocAndRole.first); - // FIXME: use the result to filter out references. - shouldIndexFile(FileID); - if (auto FileURI = GetURI(FileID)) { - auto Range = - getTokenRange(LocAndRole.first, SM, ASTCtx->getLangOpts()); - Ref R; - R.Location.Start = Range.first; - R.Location.End = Range.second; - R.Location.FileURI = FileURI->c_str(); - R.Kind = toRefKind(LocAndRole.second, Spelled); - Refs.insert(ID, R); - } - }; + auto CollectRef = [&](SymbolID ID, const SymbolRef &LocAndRole, + bool Spelled = false) { + auto FileID = SM.getFileID(LocAndRole.Loc); + // FIXME: use the result to filter out references. + shouldIndexFile(FileID); + if (auto FileURI = GetURI(FileID)) { + auto Range = getTokenRange(LocAndRole.Loc, SM, ASTCtx->getLangOpts()); + Ref R; + R.Location.Start = Range.first; + R.Location.End = Range.second; + R.Location.FileURI = FileURI->c_str(); + R.Kind = toRefKind(LocAndRole.Roles, Spelled); + if (auto RefID = getSymbolID(LocAndRole.Referrer)) + R.Referrer = *RefID; + Refs.insert(ID, R); + } + }; // Populate Refs slab from MacroRefs. // FIXME: All MacroRefs are marked as Spelled now, but this should be checked. for (const auto &IDAndRefs : MacroRefs) @@ -610,7 +612,7 @@ for (auto &DeclAndRef : DeclRefs) { if (auto ID = getSymbolID(DeclAndRef.first)) { for (auto &LocAndRole : DeclAndRef.second) { - const auto FileID = SM.getFileID(LocAndRole.first); + const auto FileID = SM.getFileID(LocAndRole.Loc); // FIXME: It's better to use TokenBuffer by passing spelled tokens from // the caller of SymbolCollector. if (!FilesToTokensCache.count(FileID)) @@ -620,7 +622,7 @@ // Check if the referenced symbol is spelled exactly the same way the // corresponding NamedDecl is. If it is, mark this reference as spelled. const auto *IdentifierToken = - spelledIdentifierTouching(LocAndRole.first, Tokens); + spelledIdentifierTouching(LocAndRole.Loc, Tokens); DeclarationName Name = DeclAndRef.first->getDeclName(); const auto NameKind = Name.getNameKind(); bool IsTargetKind = NameKind == DeclarationName::Identifier || diff --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test --- a/clang-tools-extra/clangd/test/initialize-params.test +++ b/clang-tools-extra/clangd/test/initialize-params.test @@ -5,6 +5,7 @@ # CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "result": { # CHECK-NEXT: "capabilities": { +# CHECK-NEXT: "callHierarchyProvider": true, # CHECK-NEXT: "codeActionProvider": true, # CHECK-NEXT: "completionProvider": { # CHECK-NEXT: "allCommitCharacters": [ diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -36,6 +36,7 @@ Annotations.cpp ASTTests.cpp BackgroundIndexTests.cpp + CallHierarchyTests.cpp CanonicalIncludesTests.cpp ClangdTests.cpp ClangdLSPServerTests.cpp diff --git a/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clangd/unittests/CallHierarchyTests.cpp @@ -0,0 +1,137 @@ +//===-- CallHierarchyTests.cpp ---------------------------*- C++ -*-------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "Annotations.h" +#include "Compiler.h" +#include "Matchers.h" +#include "ParsedAST.h" +#include "SyncAPI.h" +#include "TestFS.h" +#include "TestTU.h" +#include "XRefs.h" +#include "index/FileIndex.h" +#include "index/SymbolCollector.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/Index/IndexingAction.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/ScopedPrinter.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +using ::testing::AllOf; +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::Field; +using ::testing::IsEmpty; +using ::testing::Matcher; +using ::testing::Pointee; +using ::testing::UnorderedElementsAre; + +// Helpers for matching call hierarchy data structures. +MATCHER_P(WithName, N, "") { return arg.Name == N; } +MATCHER_P(WithSelectionRange, R, "") { return arg.SelectionRange == R; } + +template +::testing::Matcher From(ItemMatcher M) { + return Field(&CallHierarchyIncomingCall::From, M); +} +template +::testing::Matcher FromRanges(RangeMatchers... M) { + return Field(&CallHierarchyIncomingCall::FromRanges, + UnorderedElementsAre(M...)); +} + +TEST(CallHierarchy, IncomingOneFile) { + Annotations Source(R"cpp( + void call^ee(int); + void caller1() { + $Callee[[callee]](42); + } + void caller2() { + $Caller1A[[caller1]](); + $Caller1B[[caller1]](); + } + void caller3() { + $Caller1C[[caller1]](); + $Caller2[[caller2]](); + } + )cpp"); + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + auto Index = TU.index(); + + llvm::Optional> Items = prepareCallHierarchy( + AST, Source.point(), Index.get(), testPath(TU.Filename)); + ASSERT_TRUE(bool(Items)); + EXPECT_THAT(*Items, ElementsAre(WithName("callee"))); + auto IncomingLevel1 = incomingCalls((*Items)[0], Index.get()); + ASSERT_TRUE(bool(IncomingLevel1)); + EXPECT_THAT(*IncomingLevel1, + ElementsAre(AllOf(From(WithName("caller1")), + FromRanges(Source.range("Callee"))))); + + auto IncomingLevel2 = incomingCalls((*IncomingLevel1)[0].From, Index.get()); + ASSERT_TRUE(bool(IncomingLevel2)); + EXPECT_THAT( + *IncomingLevel2, + UnorderedElementsAre( + AllOf(From(WithName("caller2")), + FromRanges(Source.range("Caller1A"), Source.range("Caller1B"))), + AllOf(From(WithName("caller3")), + FromRanges(Source.range("Caller1C"))))); + + auto IncomingLevel3 = incomingCalls((*IncomingLevel2)[0].From, Index.get()); + ASSERT_TRUE(bool(IncomingLevel3)); + EXPECT_THAT(*IncomingLevel3, + ElementsAre(AllOf(From(WithName("caller3")), + FromRanges(Source.range("Caller2"))))); + + auto IncomingLevel4 = incomingCalls((*IncomingLevel3)[0].From, Index.get()); + ASSERT_TRUE(bool(IncomingLevel4)); + EXPECT_THAT(*IncomingLevel4, ElementsAre()); +} + +TEST(CallHierarchy, IncomingQualified) { + Annotations Source(R"cpp( + namespace ns { + struct Waldo { + void find(); + }; + void Waldo::find() {} + void caller1(Waldo &W) { + W.$Caller1[[f^ind]](); + } + void caller2(Waldo &W) { + W.$Caller2[[find]](); + } + } + )cpp"); + TestTU TU = TestTU::withCode(Source.code()); + auto AST = TU.build(); + auto Index = TU.index(); + + llvm::Optional> Items = prepareCallHierarchy( + AST, Source.point(), Index.get(), testPath(TU.Filename)); + ASSERT_TRUE(bool(Items)); + EXPECT_THAT(*Items, ElementsAre(WithName("find"))); + auto Incoming = incomingCalls((*Items)[0], Index.get()); + ASSERT_TRUE(bool(Incoming)); + EXPECT_THAT(*Incoming, + UnorderedElementsAre(AllOf(From(WithName("caller1")), + FromRanges(Source.range("Caller1"))), + AllOf(From(WithName("caller2")), + FromRanges(Source.range("Caller2"))))); +} + +} // namespace +} // namespace clangd +} // namespace clang diff --git a/llvm/utils/gn/secondary/clang-tools-extra/clangd/unittests/BUILD.gn b/llvm/utils/gn/secondary/clang-tools-extra/clangd/unittests/BUILD.gn --- a/llvm/utils/gn/secondary/clang-tools-extra/clangd/unittests/BUILD.gn +++ b/llvm/utils/gn/secondary/clang-tools-extra/clangd/unittests/BUILD.gn @@ -44,6 +44,7 @@ "ASTTests.cpp", "Annotations.cpp", "BackgroundIndexTests.cpp", + "CallHierarchyTests.cpp", "CanonicalIncludesTests.cpp", "ClangdLSPServerTests.cpp", "ClangdTests.cpp",