Mercurial > hg > CbC > CbC_llvm
view clang-tools-extra/clangd/XRefs.cpp @ 266:00f31e85ec16 default tip
Added tag current for changeset 31d058e83c98
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Sat, 14 Oct 2023 10:13:55 +0900 |
parents | 1f2b6ac9f198 |
children |
line wrap: on
line source
//===--- XRefs.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 "XRefs.h" #include "AST.h" #include "FindSymbols.h" #include "FindTarget.h" #include "Headers.h" #include "HeuristicResolver.h" #include "IncludeCleaner.h" #include "ParsedAST.h" #include "Protocol.h" #include "Quality.h" #include "Selection.h" #include "SourceCode.h" #include "URI.h" #include "clang-include-cleaner/Analysis.h" #include "clang-include-cleaner/Types.h" #include "index/Index.h" #include "index/Merge.h" #include "index/Relation.h" #include "index/SymbolCollector.h" #include "index/SymbolID.h" #include "index/SymbolLocation.h" #include "support/Logger.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTTypeTraits.h" #include "clang/AST/Attr.h" #include "clang/AST/Attrs.inc" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/DeclVisitor.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Stmt.h" #include "clang/AST/StmtCXX.h" #include "clang/AST/StmtVisitor.h" #include "clang/AST/Type.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TokenKinds.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexSymbol.h" #include "clang/Index/IndexingAction.h" #include "clang/Index/IndexingOptions.h" #include "clang/Index/USRGeneration.h" #include "clang/Lex/Lexer.h" #include "clang/Tooling/Syntax/Tokens.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Error.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include <optional> #include <string> #include <vector> namespace clang { namespace clangd { namespace { // Returns the single definition of the entity declared by D, if visible. // In particular: // - for non-redeclarable kinds (e.g. local vars), return D // - for kinds that allow multiple definitions (e.g. namespaces), return nullptr // Kinds of nodes that always return nullptr here will not have definitions // reported by locateSymbolAt(). const NamedDecl *getDefinition(const NamedDecl *D) { assert(D); // Decl has one definition that we can find. if (const auto *TD = dyn_cast<TagDecl>(D)) return TD->getDefinition(); if (const auto *VD = dyn_cast<VarDecl>(D)) return VD->getDefinition(); if (const auto *FD = dyn_cast<FunctionDecl>(D)) return FD->getDefinition(); if (const auto *CTD = dyn_cast<ClassTemplateDecl>(D)) if (const auto *RD = CTD->getTemplatedDecl()) return RD->getDefinition(); if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) { if (MD->isThisDeclarationADefinition()) return MD; // Look for the method definition inside the implementation decl. auto *DeclCtx = cast<Decl>(MD->getDeclContext()); if (DeclCtx->isInvalidDecl()) return nullptr; if (const auto *CD = dyn_cast<ObjCContainerDecl>(DeclCtx)) if (const auto *Impl = getCorrespondingObjCImpl(CD)) return Impl->getMethod(MD->getSelector(), MD->isInstanceMethod()); } if (const auto *CD = dyn_cast<ObjCContainerDecl>(D)) return getCorrespondingObjCImpl(CD); // Only a single declaration is allowed. if (isa<ValueDecl>(D) || isa<TemplateTypeParmDecl>(D) || isa<TemplateTemplateParmDecl>(D)) // except cases above return D; // Multiple definitions are allowed. return nullptr; // except cases above } void logIfOverflow(const SymbolLocation &Loc) { if (Loc.Start.hasOverflow() || Loc.End.hasOverflow()) log("Possible overflow in symbol location: {0}", Loc); } // Convert a SymbolLocation to LSP's Location. // TUPath is used to resolve the path of URI. // FIXME: figure out a good home for it, and share the implementation with // FindSymbols. std::optional<Location> toLSPLocation(const SymbolLocation &Loc, llvm::StringRef TUPath) { if (!Loc) return std::nullopt; auto Uri = URI::parse(Loc.FileURI); if (!Uri) { elog("Could not parse URI {0}: {1}", Loc.FileURI, Uri.takeError()); return std::nullopt; } auto U = URIForFile::fromURI(*Uri, TUPath); if (!U) { elog("Could not resolve URI {0}: {1}", Loc.FileURI, U.takeError()); return std::nullopt; } Location LSPLoc; LSPLoc.uri = std::move(*U); LSPLoc.range.start.line = Loc.Start.line(); LSPLoc.range.start.character = Loc.Start.column(); LSPLoc.range.end.line = Loc.End.line(); LSPLoc.range.end.character = Loc.End.column(); logIfOverflow(Loc); return LSPLoc; } SymbolLocation toIndexLocation(const Location &Loc, std::string &URIStorage) { SymbolLocation SymLoc; URIStorage = Loc.uri.uri(); SymLoc.FileURI = URIStorage.c_str(); SymLoc.Start.setLine(Loc.range.start.line); SymLoc.Start.setColumn(Loc.range.start.character); SymLoc.End.setLine(Loc.range.end.line); SymLoc.End.setColumn(Loc.range.end.character); return SymLoc; } // Returns the preferred location between an AST location and an index location. SymbolLocation getPreferredLocation(const Location &ASTLoc, const SymbolLocation &IdxLoc, std::string &Scratch) { // Also use a mock symbol for the index location so that other fields (e.g. // definition) are not factored into the preference. Symbol ASTSym, IdxSym; ASTSym.ID = IdxSym.ID = SymbolID("mock_symbol_id"); ASTSym.CanonicalDeclaration = toIndexLocation(ASTLoc, Scratch); IdxSym.CanonicalDeclaration = IdxLoc; auto Merged = mergeSymbol(ASTSym, IdxSym); return Merged.CanonicalDeclaration; } std::vector<std::pair<const NamedDecl *, DeclRelationSet>> getDeclAtPositionWithRelations(ParsedAST &AST, SourceLocation Pos, DeclRelationSet Relations, ASTNodeKind *NodeKind = nullptr) { unsigned Offset = AST.getSourceManager().getDecomposedSpellingLoc(Pos).second; std::vector<std::pair<const NamedDecl *, DeclRelationSet>> Result; auto ResultFromTree = [&](SelectionTree ST) { if (const SelectionTree::Node *N = ST.commonAncestor()) { if (NodeKind) *NodeKind = N->ASTNode.getNodeKind(); // Attributes don't target decls, look at the // thing it's attached to. // We still report the original NodeKind! // This makes the `override` hack work. if (N->ASTNode.get<Attr>() && N->Parent) N = N->Parent; llvm::copy_if(allTargetDecls(N->ASTNode, AST.getHeuristicResolver()), std::back_inserter(Result), [&](auto &Entry) { return !(Entry.second & ~Relations); }); } return !Result.empty(); }; SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), Offset, Offset, ResultFromTree); return Result; } std::vector<const NamedDecl *> getDeclAtPosition(ParsedAST &AST, SourceLocation Pos, DeclRelationSet Relations, ASTNodeKind *NodeKind = nullptr) { std::vector<const NamedDecl *> Result; for (auto &Entry : getDeclAtPositionWithRelations(AST, Pos, Relations, NodeKind)) Result.push_back(Entry.first); return Result; } // Expects Loc to be a SpellingLocation, will bail out otherwise as it can't // figure out a filename. std::optional<Location> makeLocation(const ASTContext &AST, SourceLocation Loc, llvm::StringRef TUPath) { const auto &SM = AST.getSourceManager(); const auto F = SM.getFileEntryRefForID(SM.getFileID(Loc)); if (!F) return std::nullopt; auto FilePath = getCanonicalPath(*F, SM.getFileManager()); if (!FilePath) { log("failed to get path!"); return std::nullopt; } Location L; L.uri = URIForFile::canonicalize(*FilePath, TUPath); // We call MeasureTokenLength here as TokenBuffer doesn't store spelled tokens // outside the main file. auto TokLen = Lexer::MeasureTokenLength(Loc, SM, AST.getLangOpts()); L.range = halfOpenToRange( SM, CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(TokLen))); return L; } // Treat #included files as symbols, to enable go-to-definition on them. std::optional<LocatedSymbol> locateFileReferent(const Position &Pos, ParsedAST &AST, llvm::StringRef MainFilePath) { for (auto &Inc : AST.getIncludeStructure().MainFileIncludes) { if (!Inc.Resolved.empty() && Inc.HashLine == Pos.line) { LocatedSymbol File; File.Name = std::string(llvm::sys::path::filename(Inc.Resolved)); File.PreferredDeclaration = { URIForFile::canonicalize(Inc.Resolved, MainFilePath), Range{}}; File.Definition = File.PreferredDeclaration; // We're not going to find any further symbols on #include lines. return File; } } return std::nullopt; } // Macros are simple: there's no declaration/definition distinction. // As a consequence, there's no need to look them up in the index either. std::optional<LocatedSymbol> locateMacroReferent(const syntax::Token &TouchedIdentifier, ParsedAST &AST, llvm::StringRef MainFilePath) { if (auto M = locateMacroAt(TouchedIdentifier, AST.getPreprocessor())) { if (auto Loc = makeLocation(AST.getASTContext(), M->NameLoc, MainFilePath)) { LocatedSymbol Macro; Macro.Name = std::string(M->Name); Macro.PreferredDeclaration = *Loc; Macro.Definition = Loc; Macro.ID = getSymbolID(M->Name, M->Info, AST.getSourceManager()); return Macro; } } return std::nullopt; } // A wrapper around `Decl::getCanonicalDecl` to support cases where Clang's // definition of a canonical declaration doesn't match up to what a programmer // would expect. For example, Objective-C classes can have three types of // declarations: // // - forward declaration(s): @class MyClass; // - true declaration (interface definition): @interface MyClass ... @end // - true definition (implementation): @implementation MyClass ... @end // // Clang will consider the forward declaration to be the canonical declaration // because it is first. We actually want the class definition if it is // available since that is what a programmer would consider the primary // declaration to be. const NamedDecl *getPreferredDecl(const NamedDecl *D) { // FIXME: Canonical declarations of some symbols might refer to built-in // decls with possibly-invalid source locations (e.g. global new operator). // In such cases we should pick up a redecl with valid source location // instead of failing. D = llvm::cast<NamedDecl>(D->getCanonicalDecl()); // Prefer Objective-C class/protocol definitions over the forward declaration. if (const auto *ID = dyn_cast<ObjCInterfaceDecl>(D)) if (const auto *DefinitionID = ID->getDefinition()) return DefinitionID; if (const auto *PD = dyn_cast<ObjCProtocolDecl>(D)) if (const auto *DefinitionID = PD->getDefinition()) return DefinitionID; return D; } std::vector<LocatedSymbol> findImplementors(llvm::DenseSet<SymbolID> IDs, RelationKind Predicate, const SymbolIndex *Index, llvm::StringRef MainFilePath) { if (IDs.empty() || !Index) return {}; static constexpr trace::Metric FindImplementorsMetric( "find_implementors", trace::Metric::Counter, "case"); switch (Predicate) { case RelationKind::BaseOf: FindImplementorsMetric.record(1, "find-base"); break; case RelationKind::OverriddenBy: FindImplementorsMetric.record(1, "find-override"); break; } RelationsRequest Req; Req.Predicate = Predicate; Req.Subjects = std::move(IDs); std::vector<LocatedSymbol> Results; Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) { auto DeclLoc = indexToLSPLocation(Object.CanonicalDeclaration, MainFilePath); if (!DeclLoc) { elog("Find overrides: {0}", DeclLoc.takeError()); return; } Results.emplace_back(); Results.back().Name = Object.Name.str(); Results.back().PreferredDeclaration = *DeclLoc; auto DefLoc = indexToLSPLocation(Object.Definition, MainFilePath); if (!DefLoc) { elog("Failed to convert location: {0}", DefLoc.takeError()); return; } Results.back().Definition = *DefLoc; }); return Results; } // Given LocatedSymbol results derived from the AST, query the index to obtain // definitions and preferred declarations. void enhanceLocatedSymbolsFromIndex(llvm::MutableArrayRef<LocatedSymbol> Result, const SymbolIndex *Index, llvm::StringRef MainFilePath) { LookupRequest QueryRequest; llvm::DenseMap<SymbolID, unsigned> ResultIndex; for (unsigned I = 0; I < Result.size(); ++I) { if (auto ID = Result[I].ID) { ResultIndex.try_emplace(ID, I); QueryRequest.IDs.insert(ID); } } if (!Index || QueryRequest.IDs.empty()) return; std::string Scratch; Index->lookup(QueryRequest, [&](const Symbol &Sym) { auto &R = Result[ResultIndex.lookup(Sym.ID)]; if (R.Definition) { // from AST // Special case: if the AST yielded a definition, then it may not be // the right *declaration*. Prefer the one from the index. if (auto Loc = toLSPLocation(Sym.CanonicalDeclaration, MainFilePath)) R.PreferredDeclaration = *Loc; // We might still prefer the definition from the index, e.g. for // generated symbols. if (auto Loc = toLSPLocation( getPreferredLocation(*R.Definition, Sym.Definition, Scratch), MainFilePath)) R.Definition = *Loc; } else { R.Definition = toLSPLocation(Sym.Definition, MainFilePath); // Use merge logic to choose AST or index declaration. if (auto Loc = toLSPLocation( getPreferredLocation(R.PreferredDeclaration, Sym.CanonicalDeclaration, Scratch), MainFilePath)) R.PreferredDeclaration = *Loc; } }); } // Decls are more complicated. // The AST contains at least a declaration, maybe a definition. // These are up-to-date, and so generally preferred over index results. // We perform a single batch index lookup to find additional definitions. std::vector<LocatedSymbol> locateASTReferent(SourceLocation CurLoc, const syntax::Token *TouchedIdentifier, ParsedAST &AST, llvm::StringRef MainFilePath, const SymbolIndex *Index, ASTNodeKind &NodeKind) { const SourceManager &SM = AST.getSourceManager(); // Results follow the order of Symbols.Decls. std::vector<LocatedSymbol> Result; static constexpr trace::Metric LocateASTReferentMetric( "locate_ast_referent", trace::Metric::Counter, "case"); auto AddResultDecl = [&](const NamedDecl *D) { D = getPreferredDecl(D); auto Loc = makeLocation(AST.getASTContext(), nameLocation(*D, SM), MainFilePath); if (!Loc) return; Result.emplace_back(); Result.back().Name = printName(AST.getASTContext(), *D); Result.back().PreferredDeclaration = *Loc; Result.back().ID = getSymbolID(D); if (const NamedDecl *Def = getDefinition(D)) Result.back().Definition = makeLocation( AST.getASTContext(), nameLocation(*Def, SM), MainFilePath); }; // Emit all symbol locations (declaration or definition) from AST. DeclRelationSet Relations = DeclRelation::TemplatePattern | DeclRelation::Alias; auto Candidates = getDeclAtPositionWithRelations(AST, CurLoc, Relations, &NodeKind); llvm::DenseSet<SymbolID> VirtualMethods; for (const auto &E : Candidates) { const NamedDecl *D = E.first; if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(D)) { // Special case: virtual void ^method() = 0: jump to all overrides. // FIXME: extend it to ^virtual, unfortunately, virtual location is not // saved in the AST. if (CMD->isPure()) { if (TouchedIdentifier && SM.getSpellingLoc(CMD->getLocation()) == TouchedIdentifier->location()) { VirtualMethods.insert(getSymbolID(CMD)); LocateASTReferentMetric.record(1, "method-to-override"); } } // Special case: void foo() ^override: jump to the overridden method. if (NodeKind.isSame(ASTNodeKind::getFromNodeKind<OverrideAttr>()) || NodeKind.isSame(ASTNodeKind::getFromNodeKind<FinalAttr>())) { // We may be overridding multiple methods - offer them all. for (const NamedDecl *ND : CMD->overridden_methods()) AddResultDecl(ND); continue; } } // Special case: the cursor is on an alias, prefer other results. // This targets "using ns::^Foo", where the target is more interesting. // This does not trigger on renaming aliases: // `using Foo = ^Bar` already targets Bar via a TypeLoc // `using ^Foo = Bar` has no other results, as Underlying is filtered. if (E.second & DeclRelation::Alias && Candidates.size() > 1 && // beginLoc/endLoc are a token range, so rewind the identifier we're in. SM.isPointWithin(TouchedIdentifier ? TouchedIdentifier->location() : CurLoc, D->getBeginLoc(), D->getEndLoc())) continue; // Special case: the point of declaration of a template specialization, // it's more useful to navigate to the template declaration. if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(D)) { if (TouchedIdentifier && D->getLocation() == TouchedIdentifier->location()) { LocateASTReferentMetric.record(1, "template-specialization-to-primary"); AddResultDecl(CTSD->getSpecializedTemplate()); continue; } } // Special case: if the class name is selected, also map Objective-C // categories and category implementations back to their class interface. // // Since `TouchedIdentifier` might refer to the `ObjCCategoryImplDecl` // instead of the `ObjCCategoryDecl` we intentionally check the contents // of the locs when checking for class name equivalence. if (const auto *CD = dyn_cast<ObjCCategoryDecl>(D)) if (const auto *ID = CD->getClassInterface()) if (TouchedIdentifier && (CD->getLocation() == TouchedIdentifier->location() || ID->getName() == TouchedIdentifier->text(SM))) { LocateASTReferentMetric.record(1, "objc-category-to-class"); AddResultDecl(ID); } LocateASTReferentMetric.record(1, "regular"); // Otherwise the target declaration is the right one. AddResultDecl(D); } enhanceLocatedSymbolsFromIndex(Result, Index, MainFilePath); auto Overrides = findImplementors(VirtualMethods, RelationKind::OverriddenBy, Index, MainFilePath); Result.insert(Result.end(), Overrides.begin(), Overrides.end()); return Result; } std::vector<LocatedSymbol> locateSymbolForType(const ParsedAST &AST, const QualType &Type, const SymbolIndex *Index) { const auto &SM = AST.getSourceManager(); auto MainFilePath = AST.tuPath(); // FIXME: this sends unique_ptr<Foo> to unique_ptr<T>. // Likely it would be better to send it to Foo (heuristically) or to both. auto Decls = targetDecl(DynTypedNode::create(Type.getNonReferenceType()), DeclRelation::TemplatePattern | DeclRelation::Alias, AST.getHeuristicResolver()); if (Decls.empty()) return {}; std::vector<LocatedSymbol> Results; const auto &ASTContext = AST.getASTContext(); for (const NamedDecl *D : Decls) { D = getPreferredDecl(D); auto Loc = makeLocation(ASTContext, nameLocation(*D, SM), MainFilePath); if (!Loc) continue; Results.emplace_back(); Results.back().Name = printName(ASTContext, *D); Results.back().PreferredDeclaration = *Loc; Results.back().ID = getSymbolID(D); if (const NamedDecl *Def = getDefinition(D)) Results.back().Definition = makeLocation(ASTContext, nameLocation(*Def, SM), MainFilePath); } enhanceLocatedSymbolsFromIndex(Results, Index, MainFilePath); return Results; } bool tokenSpelledAt(SourceLocation SpellingLoc, const syntax::TokenBuffer &TB) { auto ExpandedTokens = TB.expandedTokens( TB.sourceManager().getMacroArgExpandedLocation(SpellingLoc)); return !ExpandedTokens.empty(); } llvm::StringRef sourcePrefix(SourceLocation Loc, const SourceManager &SM) { auto D = SM.getDecomposedLoc(Loc); bool Invalid = false; llvm::StringRef Buf = SM.getBufferData(D.first, &Invalid); if (Invalid || D.second > Buf.size()) return ""; return Buf.substr(0, D.second); } bool isDependentName(ASTNodeKind NodeKind) { return NodeKind.isSame(ASTNodeKind::getFromNodeKind<OverloadExpr>()) || NodeKind.isSame( ASTNodeKind::getFromNodeKind<CXXDependentScopeMemberExpr>()) || NodeKind.isSame( ASTNodeKind::getFromNodeKind<DependentScopeDeclRefExpr>()); } } // namespace std::vector<LocatedSymbol> locateSymbolTextually(const SpelledWord &Word, ParsedAST &AST, const SymbolIndex *Index, llvm::StringRef MainFilePath, ASTNodeKind NodeKind) { // Don't use heuristics if this is a real identifier, or not an // identifier. // Exception: dependent names, because those may have useful textual // matches that AST-based heuristics cannot find. if ((Word.ExpandedToken && !isDependentName(NodeKind)) || !Word.LikelyIdentifier || !Index) return {}; // We don't want to handle words in string literals. (It'd be nice to list // *allowed* token kinds explicitly, but comment Tokens aren't retained). if (Word.PartOfSpelledToken && isStringLiteral(Word.PartOfSpelledToken->kind())) return {}; const auto &SM = AST.getSourceManager(); // Look up the selected word in the index. FuzzyFindRequest Req; Req.Query = Word.Text.str(); Req.ProximityPaths = {MainFilePath.str()}; // Find the namespaces to query by lexing the file. Req.Scopes = visibleNamespaces(sourcePrefix(Word.Location, SM), AST.getLangOpts()); // FIXME: For extra strictness, consider AnyScope=false. Req.AnyScope = true; // We limit the results to 3 further below. This limit is to avoid fetching // too much data, while still likely having enough for 3 results to remain // after additional filtering. Req.Limit = 10; bool TooMany = false; using ScoredLocatedSymbol = std::pair<float, LocatedSymbol>; std::vector<ScoredLocatedSymbol> ScoredResults; Index->fuzzyFind(Req, [&](const Symbol &Sym) { // Only consider exact name matches, including case. // This is to avoid too many false positives. // We could relax this in the future (e.g. to allow for typos) if we make // the query more accurate by other means. if (Sym.Name != Word.Text) return; // Exclude constructor results. They have the same name as the class, // but we don't have enough context to prefer them over the class. if (Sym.SymInfo.Kind == index::SymbolKind::Constructor) return; auto MaybeDeclLoc = indexToLSPLocation(Sym.CanonicalDeclaration, MainFilePath); if (!MaybeDeclLoc) { log("locateSymbolNamedTextuallyAt: {0}", MaybeDeclLoc.takeError()); return; } LocatedSymbol Located; Located.PreferredDeclaration = *MaybeDeclLoc; Located.Name = (Sym.Name + Sym.TemplateSpecializationArgs).str(); Located.ID = Sym.ID; if (Sym.Definition) { auto MaybeDefLoc = indexToLSPLocation(Sym.Definition, MainFilePath); if (!MaybeDefLoc) { log("locateSymbolNamedTextuallyAt: {0}", MaybeDefLoc.takeError()); return; } Located.PreferredDeclaration = *MaybeDefLoc; Located.Definition = *MaybeDefLoc; } if (ScoredResults.size() >= 5) { // If we have more than 5 results, don't return anything, // as confidence is too low. // FIXME: Alternatively, try a stricter query? TooMany = true; return; } SymbolQualitySignals Quality; Quality.merge(Sym); SymbolRelevanceSignals Relevance; Relevance.Name = Sym.Name; Relevance.Query = SymbolRelevanceSignals::Generic; Relevance.merge(Sym); auto Score = evaluateSymbolAndRelevance(Quality.evaluateHeuristics(), Relevance.evaluateHeuristics()); dlog("locateSymbolNamedTextuallyAt: {0}{1} = {2}\n{3}{4}\n", Sym.Scope, Sym.Name, Score, Quality, Relevance); ScoredResults.push_back({Score, std::move(Located)}); }); if (TooMany) { vlog("Heuristic index lookup for {0} returned too many candidates, ignored", Word.Text); return {}; } llvm::sort(ScoredResults, [](const ScoredLocatedSymbol &A, const ScoredLocatedSymbol &B) { return A.first > B.first; }); std::vector<LocatedSymbol> Results; for (auto &Res : std::move(ScoredResults)) Results.push_back(std::move(Res.second)); if (Results.empty()) vlog("No heuristic index definition for {0}", Word.Text); else log("Found definition heuristically in index for {0}", Word.Text); return Results; } const syntax::Token *findNearbyIdentifier(const SpelledWord &Word, const syntax::TokenBuffer &TB) { // Don't use heuristics if this is a real identifier. // Unlikely identifiers are OK if they were used as identifiers nearby. if (Word.ExpandedToken) return nullptr; // We don't want to handle words in string literals. (It'd be nice to list // *allowed* token kinds explicitly, but comment Tokens aren't retained). if (Word.PartOfSpelledToken && isStringLiteral(Word.PartOfSpelledToken->kind())) return {}; const SourceManager &SM = TB.sourceManager(); // We prefer the closest possible token, line-wise. Backwards is penalized. // Ties are implicitly broken by traversal order (first-one-wins). auto File = SM.getFileID(Word.Location); unsigned WordLine = SM.getSpellingLineNumber(Word.Location); auto Cost = [&](SourceLocation Loc) -> unsigned { assert(SM.getFileID(Loc) == File && "spelled token in wrong file?"); unsigned Line = SM.getSpellingLineNumber(Loc); return Line >= WordLine ? Line - WordLine : 2 * (WordLine - Line); }; const syntax::Token *BestTok = nullptr; unsigned BestCost = -1; // Search bounds are based on word length: // - forward: 2^N lines // - backward: 2^(N-1) lines. unsigned MaxDistance = 1U << std::min<unsigned>(Word.Text.size(), std::numeric_limits<unsigned>::digits - 1); // Line number for SM.translateLineCol() should be one-based, also // SM.translateLineCol() can handle line number greater than // number of lines in the file. // - LineMin = max(1, WordLine + 1 - 2^(N-1)) // - LineMax = WordLine + 1 + 2^N unsigned LineMin = WordLine + 1 <= MaxDistance / 2 ? 1 : WordLine + 1 - MaxDistance / 2; unsigned LineMax = WordLine + 1 + MaxDistance; SourceLocation LocMin = SM.translateLineCol(File, LineMin, 1); assert(LocMin.isValid()); SourceLocation LocMax = SM.translateLineCol(File, LineMax, 1); assert(LocMax.isValid()); // Updates BestTok and BestCost if Tok is a good candidate. // May return true if the cost is too high for this token. auto Consider = [&](const syntax::Token &Tok) { if (Tok.location() < LocMin || Tok.location() > LocMax) return true; // we are too far from the word, break the outer loop. if (!(Tok.kind() == tok::identifier && Tok.text(SM) == Word.Text)) return false; // No point guessing the same location we started with. if (Tok.location() == Word.Location) return false; // We've done cheap checks, compute cost so we can break the caller's loop. unsigned TokCost = Cost(Tok.location()); if (TokCost >= BestCost) return true; // causes the outer loop to break. // Allow locations that might be part of the AST, and macros (even if empty) // but not things like disabled preprocessor sections. if (!(tokenSpelledAt(Tok.location(), TB) || TB.expansionStartingAt(&Tok))) return false; // We already verified this token is an improvement. BestCost = TokCost; BestTok = &Tok; return false; }; auto SpelledTokens = TB.spelledTokens(File); // Find where the word occurred in the token stream, to search forward & back. auto *I = llvm::partition_point(SpelledTokens, [&](const syntax::Token &T) { assert(SM.getFileID(T.location()) == SM.getFileID(Word.Location)); return T.location() < Word.Location; // Comparison OK: same file. }); // Search for matches after the cursor. for (const syntax::Token &Tok : llvm::ArrayRef(I, SpelledTokens.end())) if (Consider(Tok)) break; // costs of later tokens are greater... // Search for matches before the cursor. for (const syntax::Token &Tok : llvm::reverse(llvm::ArrayRef(SpelledTokens.begin(), I))) if (Consider(Tok)) break; if (BestTok) vlog( "Word {0} under cursor {1} isn't a token (after PP), trying nearby {2}", Word.Text, Word.Location.printToString(SM), BestTok->location().printToString(SM)); return BestTok; } std::vector<LocatedSymbol> locateSymbolAt(ParsedAST &AST, Position Pos, const SymbolIndex *Index) { const auto &SM = AST.getSourceManager(); auto MainFilePath = AST.tuPath(); if (auto File = locateFileReferent(Pos, AST, MainFilePath)) return {std::move(*File)}; auto CurLoc = sourceLocationInMainFile(SM, Pos); if (!CurLoc) { elog("locateSymbolAt failed to convert position to source location: {0}", CurLoc.takeError()); return {}; } const syntax::Token *TouchedIdentifier = nullptr; auto TokensTouchingCursor = syntax::spelledTokensTouching(*CurLoc, AST.getTokens()); for (const syntax::Token &Tok : TokensTouchingCursor) { if (Tok.kind() == tok::identifier) { if (auto Macro = locateMacroReferent(Tok, AST, MainFilePath)) // Don't look at the AST or index if we have a macro result. // (We'd just return declarations referenced from the macro's // expansion.) return {*std::move(Macro)}; TouchedIdentifier = &Tok; break; } if (Tok.kind() == tok::kw_auto || Tok.kind() == tok::kw_decltype) { // go-to-definition on auto should find the definition of the deduced // type, if possible if (auto Deduced = getDeducedType(AST.getASTContext(), Tok.location())) { auto LocSym = locateSymbolForType(AST, *Deduced, Index); if (!LocSym.empty()) return LocSym; } } } ASTNodeKind NodeKind; auto ASTResults = locateASTReferent(*CurLoc, TouchedIdentifier, AST, MainFilePath, Index, NodeKind); if (!ASTResults.empty()) return ASTResults; // If the cursor can't be resolved directly, try fallback strategies. auto Word = SpelledWord::touching(*CurLoc, AST.getTokens(), AST.getLangOpts()); if (Word) { // Is the same word nearby a real identifier that might refer to something? if (const syntax::Token *NearbyIdent = findNearbyIdentifier(*Word, AST.getTokens())) { if (auto Macro = locateMacroReferent(*NearbyIdent, AST, MainFilePath)) { log("Found macro definition heuristically using nearby identifier {0}", Word->Text); return {*std::move(Macro)}; } ASTResults = locateASTReferent(NearbyIdent->location(), NearbyIdent, AST, MainFilePath, Index, NodeKind); if (!ASTResults.empty()) { log("Found definition heuristically using nearby identifier {0}", NearbyIdent->text(SM)); return ASTResults; } vlog("No definition found using nearby identifier {0} at {1}", Word->Text, Word->Location.printToString(SM)); } // No nearby word, or it didn't refer to anything either. Try the index. auto TextualResults = locateSymbolTextually(*Word, AST, Index, MainFilePath, NodeKind); if (!TextualResults.empty()) return TextualResults; } return {}; } std::vector<DocumentLink> getDocumentLinks(ParsedAST &AST) { const auto &SM = AST.getSourceManager(); std::vector<DocumentLink> Result; for (auto &Inc : AST.getIncludeStructure().MainFileIncludes) { if (Inc.Resolved.empty()) continue; auto HashLoc = SM.getComposedLoc(SM.getMainFileID(), Inc.HashOffset); const auto *HashTok = AST.getTokens().spelledTokenAt(HashLoc); assert(HashTok && "got inclusion at wrong offset"); const auto *IncludeTok = std::next(HashTok); const auto *FileTok = std::next(IncludeTok); // FileTok->range is not sufficient here, as raw lexing wouldn't yield // correct tokens for angled filenames. Hence we explicitly use // Inc.Written's length. auto FileRange = syntax::FileRange(SM, FileTok->location(), Inc.Written.length()) .toCharRange(SM); Result.push_back( DocumentLink({halfOpenToRange(SM, FileRange), URIForFile::canonicalize(Inc.Resolved, AST.tuPath())})); } return Result; } namespace { /// Collects references to symbols within the main file. class ReferenceFinder : public index::IndexDataConsumer { public: struct Reference { syntax::Token SpelledTok; index::SymbolRoleSet Role; const Decl *Container; Range range(const SourceManager &SM) const { return halfOpenToRange(SM, SpelledTok.range(SM).toCharRange(SM)); } }; ReferenceFinder(const ParsedAST &AST, const llvm::ArrayRef<const NamedDecl *> Targets, bool PerToken) : PerToken(PerToken), AST(AST) { for (const NamedDecl *ND : Targets) TargetDecls.insert(ND->getCanonicalDecl()); } std::vector<Reference> take() && { llvm::sort(References, [](const Reference &L, const Reference &R) { auto LTok = L.SpelledTok.location(); auto RTok = R.SpelledTok.location(); return std::tie(LTok, L.Role) < std::tie(RTok, R.Role); }); // We sometimes see duplicates when parts of the AST get traversed twice. References.erase(std::unique(References.begin(), References.end(), [](const Reference &L, const Reference &R) { auto LTok = L.SpelledTok.location(); auto RTok = R.SpelledTok.location(); return std::tie(LTok, L.Role) == std::tie(RTok, R.Role); }), References.end()); return std::move(References); } bool handleDeclOccurrence(const Decl *D, index::SymbolRoleSet Roles, llvm::ArrayRef<index::SymbolRelation> Relations, SourceLocation Loc, index::IndexDataConsumer::ASTNodeInfo ASTNode) override { if (!TargetDecls.contains(D->getCanonicalDecl())) return true; const SourceManager &SM = AST.getSourceManager(); if (!isInsideMainFile(Loc, SM)) return true; const auto &TB = AST.getTokens(); llvm::SmallVector<SourceLocation, 1> Locs; if (PerToken) { // Check whether this is one of the few constructs where the reference // can be split over several tokens. if (auto *OME = llvm::dyn_cast_or_null<ObjCMessageExpr>(ASTNode.OrigE)) { OME->getSelectorLocs(Locs); } else if (auto *OMD = llvm::dyn_cast_or_null<ObjCMethodDecl>(ASTNode.OrigD)) { OMD->getSelectorLocs(Locs); } // Sanity check: we expect the *first* token to match the reported loc. // Otherwise, maybe it was e.g. some other kind of reference to a Decl. if (!Locs.empty() && Locs.front() != Loc) Locs.clear(); // First token doesn't match, assume our guess was wrong. } if (Locs.empty()) Locs.push_back(Loc); SymbolCollector::Options CollectorOpts; CollectorOpts.CollectMainFileSymbols = true; for (SourceLocation L : Locs) { L = SM.getFileLoc(L); if (const auto *Tok = TB.spelledTokenAt(L)) References.push_back( {*Tok, Roles, SymbolCollector::getRefContainer(ASTNode.Parent, CollectorOpts)}); } return true; } private: bool PerToken; // If true, report 3 references for split ObjC selector names. std::vector<Reference> References; const ParsedAST &AST; llvm::DenseSet<const Decl *> TargetDecls; }; std::vector<ReferenceFinder::Reference> findRefs(const llvm::ArrayRef<const NamedDecl *> TargetDecls, ParsedAST &AST, bool PerToken) { ReferenceFinder RefFinder(AST, TargetDecls, PerToken); index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; IndexOpts.IndexFunctionLocals = true; IndexOpts.IndexParametersInDeclarations = true; IndexOpts.IndexTemplateParameters = true; indexTopLevelDecls(AST.getASTContext(), AST.getPreprocessor(), AST.getLocalTopLevelDecls(), RefFinder, IndexOpts); return std::move(RefFinder).take(); } const Stmt *getFunctionBody(DynTypedNode N) { if (const auto *FD = N.get<FunctionDecl>()) return FD->getBody(); if (const auto *FD = N.get<BlockDecl>()) return FD->getBody(); if (const auto *FD = N.get<LambdaExpr>()) return FD->getBody(); if (const auto *FD = N.get<ObjCMethodDecl>()) return FD->getBody(); return nullptr; } const Stmt *getLoopBody(DynTypedNode N) { if (const auto *LS = N.get<ForStmt>()) return LS->getBody(); if (const auto *LS = N.get<CXXForRangeStmt>()) return LS->getBody(); if (const auto *LS = N.get<WhileStmt>()) return LS->getBody(); if (const auto *LS = N.get<DoStmt>()) return LS->getBody(); return nullptr; } // AST traversal to highlight control flow statements under some root. // Once we hit further control flow we prune the tree (or at least restrict // what we highlight) so we capture e.g. breaks from the outer loop only. class FindControlFlow : public RecursiveASTVisitor<FindControlFlow> { // Types of control-flow statements we might highlight. enum Target { Break = 1, Continue = 2, Return = 4, Case = 8, Throw = 16, Goto = 32, All = Break | Continue | Return | Case | Throw | Goto, }; int Ignore = 0; // bitmask of Target - what are we *not* highlighting? SourceRange Bounds; // Half-open, restricts reported targets. std::vector<SourceLocation> &Result; const SourceManager &SM; // Masks out targets for a traversal into D. // Traverses the subtree using Delegate() if any targets remain. template <typename Func> bool filterAndTraverse(DynTypedNode D, const Func &Delegate) { auto RestoreIgnore = llvm::make_scope_exit( [OldIgnore(Ignore), this] { Ignore = OldIgnore; }); if (getFunctionBody(D)) Ignore = All; else if (getLoopBody(D)) Ignore |= Continue | Break; else if (D.get<SwitchStmt>()) Ignore |= Break | Case; // Prune tree if we're not looking for anything. return (Ignore == All) ? true : Delegate(); } void found(Target T, SourceLocation Loc) { if (T & Ignore) return; if (SM.isBeforeInTranslationUnit(Loc, Bounds.getBegin()) || SM.isBeforeInTranslationUnit(Bounds.getEnd(), Loc)) return; Result.push_back(Loc); } public: FindControlFlow(SourceRange Bounds, std::vector<SourceLocation> &Result, const SourceManager &SM) : Bounds(Bounds), Result(Result), SM(SM) {} // When traversing function or loops, limit targets to those that still // refer to the original root. bool TraverseDecl(Decl *D) { return !D || filterAndTraverse(DynTypedNode::create(*D), [&] { return RecursiveASTVisitor::TraverseDecl(D); }); } bool TraverseStmt(Stmt *S) { return !S || filterAndTraverse(DynTypedNode::create(*S), [&] { return RecursiveASTVisitor::TraverseStmt(S); }); } // Add leaves that we found and want. bool VisitReturnStmt(ReturnStmt *R) { found(Return, R->getReturnLoc()); return true; } bool VisitBreakStmt(BreakStmt *B) { found(Break, B->getBreakLoc()); return true; } bool VisitContinueStmt(ContinueStmt *C) { found(Continue, C->getContinueLoc()); return true; } bool VisitSwitchCase(SwitchCase *C) { found(Case, C->getKeywordLoc()); return true; } bool VisitCXXThrowExpr(CXXThrowExpr *T) { found(Throw, T->getThrowLoc()); return true; } bool VisitGotoStmt(GotoStmt *G) { // Goto is interesting if its target is outside the root. if (const auto *LD = G->getLabel()) { if (SM.isBeforeInTranslationUnit(LD->getLocation(), Bounds.getBegin()) || SM.isBeforeInTranslationUnit(Bounds.getEnd(), LD->getLocation())) found(Goto, G->getGotoLoc()); } return true; } }; // Given a location within a switch statement, return the half-open range that // covers the case it's contained in. // We treat `case X: case Y: ...` as one case, and assume no other fallthrough. SourceRange findCaseBounds(const SwitchStmt &Switch, SourceLocation Loc, const SourceManager &SM) { // Cases are not stored in order, sort them first. // (In fact they seem to be stored in reverse order, don't rely on this) std::vector<const SwitchCase *> Cases; for (const SwitchCase *Case = Switch.getSwitchCaseList(); Case; Case = Case->getNextSwitchCase()) Cases.push_back(Case); llvm::sort(Cases, [&](const SwitchCase *L, const SwitchCase *R) { return SM.isBeforeInTranslationUnit(L->getKeywordLoc(), R->getKeywordLoc()); }); // Find the first case after the target location, the end of our range. auto CaseAfter = llvm::partition_point(Cases, [&](const SwitchCase *C) { return !SM.isBeforeInTranslationUnit(Loc, C->getKeywordLoc()); }); SourceLocation End = CaseAfter == Cases.end() ? Switch.getEndLoc() : (*CaseAfter)->getKeywordLoc(); // Our target can be before the first case - cases are optional! if (CaseAfter == Cases.begin()) return SourceRange(Switch.getBeginLoc(), End); // The start of our range is usually the previous case, but... auto CaseBefore = std::prev(CaseAfter); // ... rewind CaseBefore to the first in a `case A: case B: ...` sequence. while (CaseBefore != Cases.begin() && (*std::prev(CaseBefore))->getSubStmt() == *CaseBefore) --CaseBefore; return SourceRange((*CaseBefore)->getKeywordLoc(), End); } // Returns the locations of control flow statements related to N. e.g.: // for => branches: break/continue/return/throw // break => controlling loop (forwhile/do), and its related control flow // return => all returns/throws from the same function // When an inner block is selected, we include branches bound to outer blocks // as these are exits from the inner block. e.g. return in a for loop. // FIXME: We don't analyze catch blocks, throw is treated the same as return. std::vector<SourceLocation> relatedControlFlow(const SelectionTree::Node &N) { const SourceManager &SM = N.getDeclContext().getParentASTContext().getSourceManager(); std::vector<SourceLocation> Result; // First, check if we're at a node that can resolve to a root. enum class Cur { None, Break, Continue, Return, Case, Throw } Cursor; if (N.ASTNode.get<BreakStmt>()) { Cursor = Cur::Break; } else if (N.ASTNode.get<ContinueStmt>()) { Cursor = Cur::Continue; } else if (N.ASTNode.get<ReturnStmt>()) { Cursor = Cur::Return; } else if (N.ASTNode.get<CXXThrowExpr>()) { Cursor = Cur::Throw; } else if (N.ASTNode.get<SwitchCase>()) { Cursor = Cur::Case; } else if (const GotoStmt *GS = N.ASTNode.get<GotoStmt>()) { // We don't know what root to associate with, but highlight the goto/label. Result.push_back(GS->getGotoLoc()); if (const auto *LD = GS->getLabel()) Result.push_back(LD->getLocation()); Cursor = Cur::None; } else { Cursor = Cur::None; } const Stmt *Root = nullptr; // Loop or function body to traverse. SourceRange Bounds; // Look up the tree for a root (or just at this node if we didn't find a leaf) for (const auto *P = &N; P; P = P->Parent) { // return associates with enclosing function if (const Stmt *FunctionBody = getFunctionBody(P->ASTNode)) { if (Cursor == Cur::Return || Cursor == Cur::Throw) { Root = FunctionBody; } break; // other leaves don't cross functions. } // break/continue associate with enclosing loop. if (const Stmt *LoopBody = getLoopBody(P->ASTNode)) { if (Cursor == Cur::None || Cursor == Cur::Break || Cursor == Cur::Continue) { Root = LoopBody; // Highlight the loop keyword itself. // FIXME: for do-while, this only covers the `do`.. Result.push_back(P->ASTNode.getSourceRange().getBegin()); break; } } // For switches, users think of case statements as control flow blocks. // We highlight only occurrences surrounded by the same case. // We don't detect fallthrough (other than 'case X, case Y'). if (const auto *SS = P->ASTNode.get<SwitchStmt>()) { if (Cursor == Cur::Break || Cursor == Cur::Case) { Result.push_back(SS->getSwitchLoc()); // Highlight the switch. Root = SS->getBody(); // Limit to enclosing case, if there is one. Bounds = findCaseBounds(*SS, N.ASTNode.getSourceRange().getBegin(), SM); break; } } // If we didn't start at some interesting node, we're done. if (Cursor == Cur::None) break; } if (Root) { if (!Bounds.isValid()) Bounds = Root->getSourceRange(); FindControlFlow(Bounds, Result, SM).TraverseStmt(const_cast<Stmt *>(Root)); } return Result; } DocumentHighlight toHighlight(const ReferenceFinder::Reference &Ref, const SourceManager &SM) { DocumentHighlight DH; DH.range = Ref.range(SM); if (Ref.Role & index::SymbolRoleSet(index::SymbolRole::Write)) DH.kind = DocumentHighlightKind::Write; else if (Ref.Role & index::SymbolRoleSet(index::SymbolRole::Read)) DH.kind = DocumentHighlightKind::Read; else DH.kind = DocumentHighlightKind::Text; return DH; } std::optional<DocumentHighlight> toHighlight(SourceLocation Loc, const syntax::TokenBuffer &TB) { Loc = TB.sourceManager().getFileLoc(Loc); if (const auto *Tok = TB.spelledTokenAt(Loc)) { DocumentHighlight Result; Result.range = halfOpenToRange( TB.sourceManager(), CharSourceRange::getCharRange(Tok->location(), Tok->endLocation())); return Result; } return std::nullopt; } } // namespace std::vector<DocumentHighlight> findDocumentHighlights(ParsedAST &AST, Position Pos) { const SourceManager &SM = AST.getSourceManager(); // FIXME: show references to macro within file? auto CurLoc = sourceLocationInMainFile(SM, Pos); if (!CurLoc) { llvm::consumeError(CurLoc.takeError()); return {}; } std::vector<DocumentHighlight> Result; auto TryTree = [&](SelectionTree ST) { if (const SelectionTree::Node *N = ST.commonAncestor()) { DeclRelationSet Relations = DeclRelation::TemplatePattern | DeclRelation::Alias; auto TargetDecls = targetDecl(N->ASTNode, Relations, AST.getHeuristicResolver()); if (!TargetDecls.empty()) { // FIXME: we may get multiple DocumentHighlights with the same location // and different kinds, deduplicate them. for (const auto &Ref : findRefs(TargetDecls, AST, /*PerToken=*/true)) Result.push_back(toHighlight(Ref, SM)); return true; } auto ControlFlow = relatedControlFlow(*N); if (!ControlFlow.empty()) { for (SourceLocation Loc : ControlFlow) if (auto Highlight = toHighlight(Loc, AST.getTokens())) Result.push_back(std::move(*Highlight)); return true; } } return false; }; unsigned Offset = AST.getSourceManager().getDecomposedSpellingLoc(*CurLoc).second; SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), Offset, Offset, TryTree); return Result; } std::vector<LocatedSymbol> findImplementations(ParsedAST &AST, Position Pos, const SymbolIndex *Index) { // We rely on index to find the implementations in subclasses. // FIXME: Index can be stale, so we may loose some latest results from the // main file. if (!Index) return {}; const SourceManager &SM = AST.getSourceManager(); auto CurLoc = sourceLocationInMainFile(SM, Pos); if (!CurLoc) { elog("Failed to convert position to source location: {0}", CurLoc.takeError()); return {}; } DeclRelationSet Relations = DeclRelation::TemplatePattern | DeclRelation::Alias; llvm::DenseSet<SymbolID> IDs; RelationKind QueryKind = RelationKind::OverriddenBy; for (const NamedDecl *ND : getDeclAtPosition(AST, *CurLoc, Relations)) { if (const auto *CXXMD = llvm::dyn_cast<CXXMethodDecl>(ND)) { if (CXXMD->isVirtual()) { IDs.insert(getSymbolID(ND)); QueryKind = RelationKind::OverriddenBy; } } else if (const auto *RD = dyn_cast<CXXRecordDecl>(ND)) { IDs.insert(getSymbolID(RD)); QueryKind = RelationKind::BaseOf; } } return findImplementors(std::move(IDs), QueryKind, Index, AST.tuPath()); } namespace { // Recursively finds all the overridden methods of `CMD` in complete type // hierarchy. void getOverriddenMethods(const CXXMethodDecl *CMD, llvm::DenseSet<SymbolID> &OverriddenMethods) { if (!CMD) return; for (const CXXMethodDecl *Base : CMD->overridden_methods()) { if (auto ID = getSymbolID(Base)) OverriddenMethods.insert(ID); getOverriddenMethods(Base, OverriddenMethods); } } std::optional<std::string> stringifyContainerForMainFileRef(const Decl *Container) { // FIXME We might also want to display the signature here // When doing so, remember to also add the Signature to index results! if (auto *ND = llvm::dyn_cast_if_present<NamedDecl>(Container)) return printQualifiedName(*ND); return {}; } std::optional<ReferencesResult> maybeFindIncludeReferences(ParsedAST &AST, Position Pos, URIForFile URIMainFile) { const auto &Includes = AST.getIncludeStructure().MainFileIncludes; auto IncludeOnLine = llvm::find_if(Includes, [&Pos](const Inclusion &Inc) { return Inc.HashLine == Pos.line; }); if (IncludeOnLine == Includes.end()) return std::nullopt; const SourceManager &SM = AST.getSourceManager(); ReferencesResult Results; auto Converted = convertIncludes(AST); include_cleaner::walkUsed( AST.getLocalTopLevelDecls(), collectMacroReferences(AST), AST.getPragmaIncludes().get(), SM, [&](const include_cleaner::SymbolReference &Ref, llvm::ArrayRef<include_cleaner::Header> Providers) { if (Ref.RT != include_cleaner::RefType::Explicit || !isPreferredProvider(*IncludeOnLine, Converted, Providers)) return; auto Loc = SM.getFileLoc(Ref.RefLocation); // File locations can be outside of the main file if macro is // expanded through an #include. while (SM.getFileID(Loc) != SM.getMainFileID()) Loc = SM.getIncludeLoc(SM.getFileID(Loc)); ReferencesResult::Reference Result; const auto *Token = AST.getTokens().spelledTokenAt(Loc); Result.Loc.range = Range{sourceLocToPosition(SM, Token->location()), sourceLocToPosition(SM, Token->endLocation())}; Result.Loc.uri = URIMainFile; Results.References.push_back(std::move(Result)); }); if (Results.References.empty()) return std::nullopt; // Add the #include line to the references list. ReferencesResult::Reference Result; Result.Loc.range = rangeTillEOL(SM.getBufferData(SM.getMainFileID()), IncludeOnLine->HashOffset); Result.Loc.uri = URIMainFile; Results.References.push_back(std::move(Result)); return Results; } } // namespace ReferencesResult findReferences(ParsedAST &AST, Position Pos, uint32_t Limit, const SymbolIndex *Index, bool AddContext) { ReferencesResult Results; const SourceManager &SM = AST.getSourceManager(); auto MainFilePath = AST.tuPath(); auto URIMainFile = URIForFile::canonicalize(MainFilePath, MainFilePath); auto CurLoc = sourceLocationInMainFile(SM, Pos); if (!CurLoc) { llvm::consumeError(CurLoc.takeError()); return {}; } const auto IncludeReferences = maybeFindIncludeReferences(AST, Pos, URIMainFile); if (IncludeReferences) return *IncludeReferences; llvm::DenseSet<SymbolID> IDsToQuery, OverriddenMethods; const auto *IdentifierAtCursor = syntax::spelledIdentifierTouching(*CurLoc, AST.getTokens()); std::optional<DefinedMacro> Macro; if (IdentifierAtCursor) Macro = locateMacroAt(*IdentifierAtCursor, AST.getPreprocessor()); if (Macro) { // Handle references to macro. if (auto MacroSID = getSymbolID(Macro->Name, Macro->Info, SM)) { // Collect macro references from main file. const auto &IDToRefs = AST.getMacros().MacroRefs; auto Refs = IDToRefs.find(MacroSID); if (Refs != IDToRefs.end()) { for (const auto &Ref : Refs->second) { ReferencesResult::Reference Result; Result.Loc.range = Ref.toRange(SM); Result.Loc.uri = URIMainFile; if (Ref.IsDefinition) { Result.Attributes |= ReferencesResult::Declaration; Result.Attributes |= ReferencesResult::Definition; } Results.References.push_back(std::move(Result)); } } IDsToQuery.insert(MacroSID); } } else { // Handle references to Decls. DeclRelationSet Relations = DeclRelation::TemplatePattern | DeclRelation::Alias; std::vector<const NamedDecl *> Decls = getDeclAtPosition(AST, *CurLoc, Relations); llvm::SmallVector<const NamedDecl *> TargetsInMainFile; for (const NamedDecl *D : Decls) { auto ID = getSymbolID(D); if (!ID) continue; TargetsInMainFile.push_back(D); // Not all symbols can be referenced from outside (e.g. function-locals). // TODO: we could skip TU-scoped symbols here (e.g. static functions) if // we know this file isn't a header. The details might be tricky. if (D->getParentFunctionOrMethod()) continue; IDsToQuery.insert(ID); } RelationsRequest OverriddenBy; if (Index) { OverriddenBy.Predicate = RelationKind::OverriddenBy; for (const NamedDecl *ND : Decls) { // Special case: For virtual methods, report decl/def of overrides and // references to all overridden methods in complete type hierarchy. if (const auto *CMD = llvm::dyn_cast<CXXMethodDecl>(ND)) { if (CMD->isVirtual()) { if (auto ID = getSymbolID(CMD)) OverriddenBy.Subjects.insert(ID); getOverriddenMethods(CMD, OverriddenMethods); } } } } // We traverse the AST to find references in the main file. auto MainFileRefs = findRefs(TargetsInMainFile, AST, /*PerToken=*/false); // We may get multiple refs with the same location and different Roles, as // cross-reference is only interested in locations, we deduplicate them // by the location to avoid emitting duplicated locations. MainFileRefs.erase(std::unique(MainFileRefs.begin(), MainFileRefs.end(), [](const ReferenceFinder::Reference &L, const ReferenceFinder::Reference &R) { return L.SpelledTok.location() == R.SpelledTok.location(); }), MainFileRefs.end()); for (const auto &Ref : MainFileRefs) { ReferencesResult::Reference Result; Result.Loc.range = Ref.range(SM); Result.Loc.uri = URIMainFile; if (AddContext) Result.Loc.containerName = stringifyContainerForMainFileRef(Ref.Container); if (Ref.Role & static_cast<unsigned>(index::SymbolRole::Declaration)) Result.Attributes |= ReferencesResult::Declaration; // clang-index doesn't report definitions as declarations, but they are. if (Ref.Role & static_cast<unsigned>(index::SymbolRole::Definition)) Result.Attributes |= ReferencesResult::Definition | ReferencesResult::Declaration; Results.References.push_back(std::move(Result)); } // Add decl/def of overridding methods. if (Index && !OverriddenBy.Subjects.empty()) { LookupRequest ContainerLookup; // Different overrides will always be contained in different classes, so // we have a one-to-one mapping between SymbolID and index here, thus we // don't need to use std::vector as the map's value type. llvm::DenseMap<SymbolID, size_t> RefIndexForContainer; Index->relations(OverriddenBy, [&](const SymbolID &Subject, const Symbol &Object) { if (Limit && Results.References.size() >= Limit) { Results.HasMore = true; return; } const auto LSPLocDecl = toLSPLocation(Object.CanonicalDeclaration, MainFilePath); const auto LSPLocDef = toLSPLocation(Object.Definition, MainFilePath); if (LSPLocDecl && LSPLocDecl != LSPLocDef) { ReferencesResult::Reference Result; Result.Loc = {std::move(*LSPLocDecl), std::nullopt}; Result.Attributes = ReferencesResult::Declaration | ReferencesResult::Override; RefIndexForContainer.insert({Object.ID, Results.References.size()}); ContainerLookup.IDs.insert(Object.ID); Results.References.push_back(std::move(Result)); } if (LSPLocDef) { ReferencesResult::Reference Result; Result.Loc = {std::move(*LSPLocDef), std::nullopt}; Result.Attributes = ReferencesResult::Declaration | ReferencesResult::Definition | ReferencesResult::Override; RefIndexForContainer.insert({Object.ID, Results.References.size()}); ContainerLookup.IDs.insert(Object.ID); Results.References.push_back(std::move(Result)); } }); if (!ContainerLookup.IDs.empty() && AddContext) Index->lookup(ContainerLookup, [&](const Symbol &Container) { auto Ref = RefIndexForContainer.find(Container.ID); assert(Ref != RefIndexForContainer.end()); Results.References[Ref->getSecond()].Loc.containerName = Container.Scope.str() + Container.Name.str(); }); } } // Now query the index for references from other files. auto QueryIndex = [&](llvm::DenseSet<SymbolID> IDs, bool AllowAttributes, bool AllowMainFileSymbols) { if (IDs.empty() || !Index || Results.HasMore) return; RefsRequest Req; Req.IDs = std::move(IDs); if (Limit) { if (Limit < Results.References.size()) { // We've already filled our quota, still check the index to correctly // return the `HasMore` info. Req.Limit = 0; } else { // Query index only for the remaining size. Req.Limit = Limit - Results.References.size(); } } LookupRequest ContainerLookup; llvm::DenseMap<SymbolID, std::vector<size_t>> RefIndicesForContainer; Results.HasMore |= Index->refs(Req, [&](const Ref &R) { auto LSPLoc = toLSPLocation(R.Location, MainFilePath); // Avoid indexed results for the main file - the AST is authoritative. if (!LSPLoc || (!AllowMainFileSymbols && LSPLoc->uri.file() == MainFilePath)) return; ReferencesResult::Reference Result; Result.Loc = {std::move(*LSPLoc), std::nullopt}; if (AllowAttributes) { if ((R.Kind & RefKind::Declaration) == RefKind::Declaration) Result.Attributes |= ReferencesResult::Declaration; // FIXME: our index should definitely store def | decl separately! if ((R.Kind & RefKind::Definition) == RefKind::Definition) Result.Attributes |= ReferencesResult::Declaration | ReferencesResult::Definition; } if (AddContext) { SymbolID Container = R.Container; ContainerLookup.IDs.insert(Container); RefIndicesForContainer[Container].push_back(Results.References.size()); } Results.References.push_back(std::move(Result)); }); if (!ContainerLookup.IDs.empty() && AddContext) Index->lookup(ContainerLookup, [&](const Symbol &Container) { auto Ref = RefIndicesForContainer.find(Container.ID); assert(Ref != RefIndicesForContainer.end()); auto ContainerName = Container.Scope.str() + Container.Name.str(); for (auto I : Ref->getSecond()) { Results.References[I].Loc.containerName = ContainerName; } }); }; QueryIndex(std::move(IDsToQuery), /*AllowAttributes=*/true, /*AllowMainFileSymbols=*/false); // For a virtual method: Occurrences of BaseMethod should be treated as refs // and not as decl/def. Allow symbols from main file since AST does not report // these. QueryIndex(std::move(OverriddenMethods), /*AllowAttributes=*/false, /*AllowMainFileSymbols=*/true); return Results; } std::vector<SymbolDetails> getSymbolInfo(ParsedAST &AST, Position Pos) { const SourceManager &SM = AST.getSourceManager(); auto CurLoc = sourceLocationInMainFile(SM, Pos); if (!CurLoc) { llvm::consumeError(CurLoc.takeError()); return {}; } auto MainFilePath = AST.tuPath(); std::vector<SymbolDetails> Results; // We also want the targets of using-decls, so we include // DeclRelation::Underlying. DeclRelationSet Relations = DeclRelation::TemplatePattern | DeclRelation::Alias | DeclRelation::Underlying; for (const NamedDecl *D : getDeclAtPosition(AST, *CurLoc, Relations)) { D = getPreferredDecl(D); SymbolDetails NewSymbol; std::string QName = printQualifiedName(*D); auto SplitQName = splitQualifiedName(QName); NewSymbol.containerName = std::string(SplitQName.first); NewSymbol.name = std::string(SplitQName.second); if (NewSymbol.containerName.empty()) { if (const auto *ParentND = dyn_cast_or_null<NamedDecl>(D->getDeclContext())) NewSymbol.containerName = printQualifiedName(*ParentND); } llvm::SmallString<32> USR; if (!index::generateUSRForDecl(D, USR)) { NewSymbol.USR = std::string(USR.str()); NewSymbol.ID = SymbolID(NewSymbol.USR); } if (const NamedDecl *Def = getDefinition(D)) NewSymbol.definitionRange = makeLocation( AST.getASTContext(), nameLocation(*Def, SM), MainFilePath); NewSymbol.declarationRange = makeLocation(AST.getASTContext(), nameLocation(*D, SM), MainFilePath); Results.push_back(std::move(NewSymbol)); } const auto *IdentifierAtCursor = syntax::spelledIdentifierTouching(*CurLoc, AST.getTokens()); if (!IdentifierAtCursor) return Results; if (auto M = locateMacroAt(*IdentifierAtCursor, AST.getPreprocessor())) { SymbolDetails NewMacro; NewMacro.name = std::string(M->Name); llvm::SmallString<32> USR; if (!index::generateUSRForMacro(NewMacro.name, M->Info->getDefinitionLoc(), SM, USR)) { NewMacro.USR = std::string(USR.str()); NewMacro.ID = SymbolID(NewMacro.USR); } Results.push_back(std::move(NewMacro)); } return Results; } llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const LocatedSymbol &S) { OS << S.Name << ": " << S.PreferredDeclaration; if (S.Definition) OS << " def=" << *S.Definition; return OS; } llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const ReferencesResult::Reference &R) { OS << R.Loc; if (R.Attributes & ReferencesResult::Declaration) OS << " [decl]"; if (R.Attributes & ReferencesResult::Definition) OS << " [def]"; if (R.Attributes & ReferencesResult::Override) OS << " [override]"; return OS; } template <typename HierarchyItem> static std::optional<HierarchyItem> declToHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) { ASTContext &Ctx = ND.getASTContext(); auto &SM = Ctx.getSourceManager(); SourceLocation NameLoc = nameLocation(ND, Ctx.getSourceManager()); SourceLocation BeginLoc = SM.getFileLoc(ND.getBeginLoc()); SourceLocation EndLoc = SM.getFileLoc(ND.getEndLoc()); const auto DeclRange = toHalfOpenFileRange(SM, Ctx.getLangOpts(), {BeginLoc, EndLoc}); if (!DeclRange) return std::nullopt; const auto FE = SM.getFileEntryRefForID(SM.getFileID(NameLoc)); if (!FE) return std::nullopt; auto FilePath = getCanonicalPath(*FE, SM.getFileManager()); if (!FilePath) return std::nullopt; // 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. SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind); HierarchyItem HI; HI.name = printName(Ctx, ND); HI.kind = SK; HI.range = Range{sourceLocToPosition(SM, DeclRange->getBegin()), sourceLocToPosition(SM, DeclRange->getEnd())}; HI.selectionRange = Range{NameBegin, NameEnd}; if (!HI.range.contains(HI.selectionRange)) { // 'selectionRange' must be contained in 'range', so in cases where clang // reports unrelated ranges we need to reconcile somehow. HI.range = HI.selectionRange; } HI.uri = URIForFile::canonicalize(*FilePath, TUPath); return HI; } static std::optional<TypeHierarchyItem> declToTypeHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) { auto Result = declToHierarchyItem<TypeHierarchyItem>(ND, TUPath); if (Result) { 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; } static std::optional<CallHierarchyItem> declToCallHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) { auto Result = declToHierarchyItem<CallHierarchyItem>(ND, TUPath); if (!Result) return Result; if (ND.isDeprecated()) Result->tags.push_back(SymbolTag::Deprecated); if (auto ID = getSymbolID(&ND)) Result->data = ID.str(); return Result; } template <typename HierarchyItem> static std::optional<HierarchyItem> symbolToHierarchyItem(const Symbol &S, PathRef TUPath) { auto Loc = symbolToLocation(S, TUPath); if (!Loc) { elog("Failed to convert symbol to hierarchy item: {0}", Loc.takeError()); return std::nullopt; } HierarchyItem HI; HI.name = std::string(S.Name); HI.kind = indexSymbolKindToSymbolKind(S.SymInfo.Kind); HI.selectionRange = Loc->range; // FIXME: Populate 'range' correctly // (https://github.com/clangd/clangd/issues/59). HI.range = HI.selectionRange; HI.uri = Loc->uri; return HI; } static std::optional<TypeHierarchyItem> symbolToTypeHierarchyItem(const Symbol &S, PathRef TUPath) { auto Result = symbolToHierarchyItem<TypeHierarchyItem>(S, TUPath); if (Result) { Result->deprecated = (S.Flags & Symbol::Deprecated); Result->data.symbolID = S.ID; } return Result; } static std::optional<CallHierarchyItem> symbolToCallHierarchyItem(const Symbol &S, PathRef TUPath) { auto Result = symbolToHierarchyItem<CallHierarchyItem>(S, TUPath); if (!Result) return Result; Result->data = S.ID.str(); if (S.Flags & Symbol::Deprecated) Result->tags.push_back(SymbolTag::Deprecated); return Result; } static void fillSubTypes(const SymbolID &ID, std::vector<TypeHierarchyItem> &SubTypes, const SymbolIndex *Index, int Levels, PathRef TUPath) { RelationsRequest Req; Req.Subjects.insert(ID); Req.Predicate = RelationKind::BaseOf; Index->relations(Req, [&](const SymbolID &Subject, const Symbol &Object) { if (std::optional<TypeHierarchyItem> ChildSym = symbolToTypeHierarchyItem(Object, TUPath)) { if (Levels > 1) { ChildSym->children.emplace(); fillSubTypes(Object.ID, *ChildSym->children, Index, Levels - 1, TUPath); } SubTypes.emplace_back(std::move(*ChildSym)); } }); } 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, TypeHierarchyItem &Item, RecursionProtectionSet &RPSet) { Item.parents.emplace(); Item.data.parents.emplace(); // typeParents() will replace dependent template specializations // with their class template, so to avoid infinite recursion for // certain types of hierarchies, keep the templates encountered // along the parent chain in a set, and stop the recursion if one // starts to repeat. auto *Pattern = CXXRD.getDescribedTemplate() ? &CXXRD : nullptr; if (Pattern) { if (!RPSet.insert(Pattern).second) { return; } } for (const CXXRecordDecl *ParentDecl : typeParents(&CXXRD)) { if (std::optional<TypeHierarchyItem> ParentSym = declToTypeHierarchyItem(*ParentDecl, TUPath)) { fillSuperTypes(*ParentDecl, TUPath, *ParentSym, RPSet); Item.data.parents->emplace_back(ParentSym->data); Item.parents->emplace_back(std::move(*ParentSym)); } } if (Pattern) { RPSet.erase(Pattern); } } std::vector<const CXXRecordDecl *> findRecordTypeAt(ParsedAST &AST, Position Pos) { auto RecordFromNode = [&AST](const SelectionTree::Node *N) { std::vector<const CXXRecordDecl *> Records; if (!N) return Records; // Note: explicitReferenceTargets() will search for both template // instantiations and template patterns, and prefer the former if available // (generally, one will be available for non-dependent specializations of a // class template). auto Decls = explicitReferenceTargets(N->ASTNode, DeclRelation::Underlying, AST.getHeuristicResolver()); for (const NamedDecl *D : Decls) { 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 CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(D)) { // If this is a method, use the type of the class. 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; }; const SourceManager &SM = AST.getSourceManager(); std::vector<const CXXRecordDecl *> Result; auto Offset = positionToOffset(SM.getBufferData(SM.getMainFileID()), Pos); if (!Offset) { llvm::consumeError(Offset.takeError()); return Result; } SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), *Offset, *Offset, [&](SelectionTree ST) { Result = RecordFromNode(ST.commonAncestor()); return !Result.empty(); }); return Result; } // Return the type most associated with an AST node. // This isn't precisely defined: we want "go to type" to do something useful. static QualType typeForNode(const SelectionTree::Node *N) { // If we're looking at a namespace qualifier, walk up to what it's qualifying. // (If we're pointing at a *class* inside a NNS, N will be a TypeLoc). while (N && N->ASTNode.get<NestedNameSpecifierLoc>()) N = N->Parent; if (!N) return QualType(); // If we're pointing at a type => return it. if (const TypeLoc *TL = N->ASTNode.get<TypeLoc>()) { if (llvm::isa<DeducedType>(TL->getTypePtr())) if (auto Deduced = getDeducedType( N->getDeclContext().getParentASTContext(), TL->getBeginLoc())) return *Deduced; // Exception: an alias => underlying type. if (llvm::isa<TypedefType>(TL->getTypePtr())) return TL->getTypePtr()->getLocallyUnqualifiedSingleStepDesugaredType(); return TL->getType(); } // Constructor initializers => the type of thing being initialized. if (const auto *CCI = N->ASTNode.get<CXXCtorInitializer>()) { if (const FieldDecl *FD = CCI->getAnyMember()) return FD->getType(); if (const Type *Base = CCI->getBaseClass()) return QualType(Base, 0); } // Base specifier => the base type. if (const auto *CBS = N->ASTNode.get<CXXBaseSpecifier>()) return CBS->getType(); if (const Decl *D = N->ASTNode.get<Decl>()) { struct Visitor : ConstDeclVisitor<Visitor, QualType> { QualType VisitValueDecl(const ValueDecl *D) { return D->getType(); } // Declaration of a type => that type. QualType VisitTypeDecl(const TypeDecl *D) { return QualType(D->getTypeForDecl(), 0); } // Exception: alias declaration => the underlying type, not the alias. QualType VisitTypedefNameDecl(const TypedefNameDecl *D) { return D->getUnderlyingType(); } // Look inside templates. QualType VisitTemplateDecl(const TemplateDecl *D) { return Visit(D->getTemplatedDecl()); } } V; return V.Visit(D); } if (const Stmt *S = N->ASTNode.get<Stmt>()) { struct Visitor : ConstStmtVisitor<Visitor, QualType> { // Null-safe version of visit simplifies recursive calls below. QualType type(const Stmt *S) { return S ? Visit(S) : QualType(); } // In general, expressions => type of expression. QualType VisitExpr(const Expr *S) { return S->IgnoreImplicitAsWritten()->getType(); } QualType VisitMemberExpr(const MemberExpr *S) { // The `foo` in `s.foo()` pretends not to have a real type! if (S->getType()->isSpecificBuiltinType(BuiltinType::BoundMember)) return Expr::findBoundMemberType(S); return VisitExpr(S); } // Exceptions for void expressions that operate on a type in some way. QualType VisitCXXDeleteExpr(const CXXDeleteExpr *S) { return S->getDestroyedType(); } QualType VisitCXXPseudoDestructorExpr(const CXXPseudoDestructorExpr *S) { return S->getDestroyedType(); } QualType VisitCXXThrowExpr(const CXXThrowExpr *S) { return S->getSubExpr()->getType(); } QualType VisitCoyieldExpr(const CoyieldExpr *S) { return type(S->getOperand()); } // Treat a designated initializer like a reference to the field. QualType VisitDesignatedInitExpr(const DesignatedInitExpr *S) { // In .foo.bar we want to jump to bar's type, so find *last* field. for (auto &D : llvm::reverse(S->designators())) if (D.isFieldDesignator()) if (const auto *FD = D.getFieldDecl()) return FD->getType(); return QualType(); } // Control flow statements that operate on data: use the data type. QualType VisitSwitchStmt(const SwitchStmt *S) { return type(S->getCond()); } QualType VisitWhileStmt(const WhileStmt *S) { return type(S->getCond()); } QualType VisitDoStmt(const DoStmt *S) { return type(S->getCond()); } QualType VisitIfStmt(const IfStmt *S) { return type(S->getCond()); } QualType VisitCaseStmt(const CaseStmt *S) { return type(S->getLHS()); } QualType VisitCXXForRangeStmt(const CXXForRangeStmt *S) { return S->getLoopVariable()->getType(); } QualType VisitReturnStmt(const ReturnStmt *S) { return type(S->getRetValue()); } QualType VisitCoreturnStmt(const CoreturnStmt *S) { return type(S->getOperand()); } QualType VisitCXXCatchStmt(const CXXCatchStmt *S) { return S->getCaughtType(); } QualType VisitObjCAtThrowStmt(const ObjCAtThrowStmt *S) { return type(S->getThrowExpr()); } QualType VisitObjCAtCatchStmt(const ObjCAtCatchStmt *S) { return S->getCatchParamDecl() ? S->getCatchParamDecl()->getType() : QualType(); } } V; return V.Visit(S); } return QualType(); } // Given a type targeted by the cursor, return one or more types that are more interesting // to target. static void unwrapFindType( QualType T, const HeuristicResolver* H, llvm::SmallVector<QualType>& Out) { if (T.isNull()) return; // If there's a specific type alias, point at that rather than unwrapping. if (const auto* TDT = T->getAs<TypedefType>()) return Out.push_back(QualType(TDT, 0)); // Pointers etc => pointee type. if (const auto *PT = T->getAs<PointerType>()) return unwrapFindType(PT->getPointeeType(), H, Out); if (const auto *RT = T->getAs<ReferenceType>()) return unwrapFindType(RT->getPointeeType(), H, Out); if (const auto *AT = T->getAsArrayTypeUnsafe()) return unwrapFindType(AT->getElementType(), H, Out); // Function type => return type. if (auto *FT = T->getAs<FunctionType>()) return unwrapFindType(FT->getReturnType(), H, Out); if (auto *CRD = T->getAsCXXRecordDecl()) { if (CRD->isLambda()) return unwrapFindType(CRD->getLambdaCallOperator()->getReturnType(), H, Out); // FIXME: more cases we'd prefer the return type of the call operator? // std::function etc? } // For smart pointer types, add the underlying type if (H) if (const auto* PointeeType = H->getPointeeType(T.getNonReferenceType().getTypePtr())) { unwrapFindType(QualType(PointeeType, 0), H, Out); return Out.push_back(T); } return Out.push_back(T); } // Convenience overload, to allow calling this without the out-parameter static llvm::SmallVector<QualType> unwrapFindType( QualType T, const HeuristicResolver* H) { llvm::SmallVector<QualType> Result; unwrapFindType(T, H, Result); return Result; } std::vector<LocatedSymbol> findType(ParsedAST &AST, Position Pos, const SymbolIndex *Index) { const SourceManager &SM = AST.getSourceManager(); auto Offset = positionToOffset(SM.getBufferData(SM.getMainFileID()), Pos); std::vector<LocatedSymbol> Result; if (!Offset) { elog("failed to convert position {0} for findTypes: {1}", Pos, Offset.takeError()); return Result; } // The general scheme is: position -> AST node -> type -> declaration. auto SymbolsFromNode = [&](const SelectionTree::Node *N) -> std::vector<LocatedSymbol> { std::vector<LocatedSymbol> LocatedSymbols; // NOTE: unwrapFindType might return duplicates for something like // unique_ptr<unique_ptr<T>>. Let's *not* remove them, because it gives you some // information about the type you may have not known before // (since unique_ptr<unique_ptr<T>> != unique_ptr<T>). for (const QualType& Type : unwrapFindType(typeForNode(N), AST.getHeuristicResolver())) llvm::copy(locateSymbolForType(AST, Type, Index), std::back_inserter(LocatedSymbols)); return LocatedSymbols; }; SelectionTree::createEach(AST.getASTContext(), AST.getTokens(), *Offset, *Offset, [&](SelectionTree ST) { Result = SymbolsFromNode(ST.commonAncestor()); return !Result.empty(); }); return Result; } std::vector<const CXXRecordDecl *> typeParents(const CXXRecordDecl *CXXRD) { std::vector<const CXXRecordDecl *> Result; // If this is an invalid instantiation, instantiation of the bases // may not have succeeded, so fall back to the template pattern. if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(CXXRD)) { if (CTSD->isInvalidDecl()) CXXRD = CTSD->getSpecializedTemplate()->getTemplatedDecl(); } // Can't query bases without a definition. if (!CXXRD->hasDefinition()) return Result; for (auto Base : CXXRD->bases()) { const CXXRecordDecl *ParentDecl = nullptr; const Type *Type = Base.getType().getTypePtr(); if (const RecordType *RT = Type->getAs<RecordType>()) { ParentDecl = RT->getAsCXXRecordDecl(); } if (!ParentDecl) { // Handle a dependent base such as "Base<T>" by using the primary // template. if (const TemplateSpecializationType *TS = Type->getAs<TemplateSpecializationType>()) { TemplateName TN = TS->getTemplateName(); if (TemplateDecl *TD = TN.getAsTemplateDecl()) { ParentDecl = dyn_cast<CXXRecordDecl>(TD->getTemplatedDecl()); } } } if (ParentDecl) Result.push_back(ParentDecl); } return Result; } std::vector<TypeHierarchyItem> getTypeHierarchy(ParsedAST &AST, Position Pos, int ResolveLevels, TypeHierarchyDirection Direction, const SymbolIndex *Index, PathRef TUPath) { std::vector<TypeHierarchyItem> Results; for (const auto *CXXRD : findRecordTypeAt(AST, Pos)) { bool WantChildren = Direction == TypeHierarchyDirection::Children || Direction == TypeHierarchyDirection::Both; // If we're looking for children, we're doing the lookup in the index. // The index does not store relationships between implicit // specializations, so if we have one, use the template pattern instead. // Note that this needs to be done before the declToTypeHierarchyItem(), // otherwise the type hierarchy item would misleadingly contain the // specialization parameters, while the children would involve classes // that derive from other specializations of the template. if (WantChildren) { if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(CXXRD)) CXXRD = CTSD->getTemplateInstantiationPattern(); } std::optional<TypeHierarchyItem> Result = declToTypeHierarchyItem(*CXXRD, AST.tuPath()); if (!Result) continue; RecursionProtectionSet RPSet; fillSuperTypes(*CXXRD, AST.tuPath(), *Result, RPSet); if (WantChildren && ResolveLevels > 0) { Result->children.emplace(); if (Index) { if (auto ID = getSymbolID(CXXRD)) fillSubTypes(ID, *Result->children, Index, ResolveLevels, TUPath); } } Results.emplace_back(std::move(*Result)); } return Results; } std::optional<std::vector<TypeHierarchyItem>> superTypes(const TypeHierarchyItem &Item, const SymbolIndex *Index) { std::vector<TypeHierarchyItem> Results; if (!Item.data.parents) return std::nullopt; 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, TypeHierarchyDirection Direction, const SymbolIndex *Index) { // We only support typeHierarchy/resolve for children, because for parents // we ignore ResolveLevels and return all levels of parents eagerly. if (!Index || Direction == TypeHierarchyDirection::Parents || ResolveLevels == 0) return; Item.children.emplace(); fillSubTypes(Item.data.symbolID, *Item.children, Index, ResolveLevels, Item.uri.file()); } std::vector<CallHierarchyItem> prepareCallHierarchy(ParsedAST &AST, Position Pos, PathRef TUPath) { std::vector<CallHierarchyItem> Result; const auto &SM = AST.getSourceManager(); auto Loc = sourceLocationInMainFile(SM, Pos); if (!Loc) { elog("prepareCallHierarchy failed to convert position to source location: " "{0}", Loc.takeError()); return Result; } for (const NamedDecl *Decl : getDeclAtPosition(AST, *Loc, {})) { if (!(isa<DeclContext>(Decl) && cast<DeclContext>(Decl)->isFunctionOrMethod()) && Decl->getKind() != Decl::Kind::FunctionTemplate) continue; if (auto CHI = declToCallHierarchyItem(*Decl, AST.tuPath())) Result.emplace_back(std::move(*CHI)); } return Result; } std::vector<CallHierarchyIncomingCall> incomingCalls(const CallHierarchyItem &Item, const SymbolIndex *Index) { std::vector<CallHierarchyIncomingCall> Results; if (!Index || Item.data.empty()) return Results; auto ID = SymbolID::fromStr(Item.data); if (!ID) { elog("incomingCalls failed to find symbol: {0}", ID.takeError()); return Results; } // In this function, we find incoming calls based on the index only. // In principle, the AST could have more up-to-date information about // occurrences within the current file. However, going from a SymbolID // to an AST node isn't cheap, particularly when the declaration isn't // in the main file. // FIXME: Consider also using AST information when feasible. RefsRequest Request; Request.IDs.insert(*ID); Request.WantContainer = true; // We could restrict more specifically to calls by introducing a new RefKind, // but non-call references (such as address-of-function) can still be // interesting as they can indicate indirect calls. Request.Filter = RefKind::Reference; // Initially store the ranges in a map keyed by SymbolID of the caller. // This allows us to group different calls with the same caller // into the same CallHierarchyIncomingCall. llvm::DenseMap<SymbolID, std::vector<Range>> CallsIn; // We can populate the ranges based on a refs request only. As we do so, we // also accumulate the container IDs into a lookup request. LookupRequest ContainerLookup; Index->refs(Request, [&](const Ref &R) { auto Loc = indexToLSPLocation(R.Location, Item.uri.file()); if (!Loc) { elog("incomingCalls failed to convert location: {0}", Loc.takeError()); return; } auto It = CallsIn.try_emplace(R.Container, std::vector<Range>{}).first; It->second.push_back(Loc->range); ContainerLookup.IDs.insert(R.Container); }); // Perform the lookup request and combine its results with CallsIn to // get complete CallHierarchyIncomingCall objects. Index->lookup(ContainerLookup, [&](const Symbol &Caller) { auto It = CallsIn.find(Caller.ID); assert(It != CallsIn.end()); if (auto CHI = symbolToCallHierarchyItem(Caller, Item.uri.file())) Results.push_back( CallHierarchyIncomingCall{std::move(*CHI), std::move(It->second)}); }); // Sort results by name of container. llvm::sort(Results, [](const CallHierarchyIncomingCall &A, const CallHierarchyIncomingCall &B) { return A.from.name < B.from.name; }); return Results; } llvm::DenseSet<const Decl *> getNonLocalDeclRefs(ParsedAST &AST, const FunctionDecl *FD) { if (!FD->hasBody()) return {}; llvm::DenseSet<const Decl *> DeclRefs; findExplicitReferences( FD, [&](ReferenceLoc Ref) { for (const Decl *D : Ref.Targets) { if (!index::isFunctionLocalSymbol(D) && !D->isTemplateParameter() && !Ref.IsDecl) DeclRefs.insert(D); } }, AST.getHeuristicResolver()); return DeclRefs; } } // namespace clangd } // namespace clang