forked from OSchip/llvm-project
327 lines
12 KiB
C++
327 lines
12 KiB
C++
//===--- FindSymbols.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 "FindSymbols.h"
|
|
|
|
#include "AST.h"
|
|
#include "ClangdUnit.h"
|
|
#include "FuzzyMatch.h"
|
|
#include "Logger.h"
|
|
#include "Quality.h"
|
|
#include "SourceCode.h"
|
|
#include "index/Index.h"
|
|
#include "clang/AST/DeclTemplate.h"
|
|
#include "clang/Index/IndexDataConsumer.h"
|
|
#include "clang/Index/IndexSymbol.h"
|
|
#include "clang/Index/IndexingAction.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/ScopedPrinter.h"
|
|
|
|
#define DEBUG_TYPE "FindSymbols"
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
namespace {
|
|
|
|
// Convert a index::SymbolKind to clangd::SymbolKind (LSP)
|
|
// Note, some are not perfect matches and should be improved when this LSP
|
|
// issue is addressed:
|
|
// https://github.com/Microsoft/language-server-protocol/issues/344
|
|
SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) {
|
|
switch (Kind) {
|
|
case index::SymbolKind::Unknown:
|
|
return SymbolKind::Variable;
|
|
case index::SymbolKind::Module:
|
|
return SymbolKind::Module;
|
|
case index::SymbolKind::Namespace:
|
|
return SymbolKind::Namespace;
|
|
case index::SymbolKind::NamespaceAlias:
|
|
return SymbolKind::Namespace;
|
|
case index::SymbolKind::Macro:
|
|
return SymbolKind::String;
|
|
case index::SymbolKind::Enum:
|
|
return SymbolKind::Enum;
|
|
case index::SymbolKind::Struct:
|
|
return SymbolKind::Struct;
|
|
case index::SymbolKind::Class:
|
|
return SymbolKind::Class;
|
|
case index::SymbolKind::Protocol:
|
|
return SymbolKind::Interface;
|
|
case index::SymbolKind::Extension:
|
|
return SymbolKind::Interface;
|
|
case index::SymbolKind::Union:
|
|
return SymbolKind::Class;
|
|
case index::SymbolKind::TypeAlias:
|
|
return SymbolKind::Class;
|
|
case index::SymbolKind::Function:
|
|
return SymbolKind::Function;
|
|
case index::SymbolKind::Variable:
|
|
return SymbolKind::Variable;
|
|
case index::SymbolKind::Field:
|
|
return SymbolKind::Field;
|
|
case index::SymbolKind::EnumConstant:
|
|
return SymbolKind::EnumMember;
|
|
case index::SymbolKind::InstanceMethod:
|
|
case index::SymbolKind::ClassMethod:
|
|
case index::SymbolKind::StaticMethod:
|
|
return SymbolKind::Method;
|
|
case index::SymbolKind::InstanceProperty:
|
|
case index::SymbolKind::ClassProperty:
|
|
case index::SymbolKind::StaticProperty:
|
|
return SymbolKind::Property;
|
|
case index::SymbolKind::Constructor:
|
|
case index::SymbolKind::Destructor:
|
|
return SymbolKind::Method;
|
|
case index::SymbolKind::ConversionFunction:
|
|
return SymbolKind::Function;
|
|
case index::SymbolKind::Parameter:
|
|
return SymbolKind::Variable;
|
|
case index::SymbolKind::Using:
|
|
return SymbolKind::Namespace;
|
|
}
|
|
llvm_unreachable("invalid symbol kind");
|
|
}
|
|
|
|
using ScoredSymbolInfo = std::pair<float, SymbolInformation>;
|
|
struct ScoredSymbolGreater {
|
|
bool operator()(const ScoredSymbolInfo &L, const ScoredSymbolInfo &R) {
|
|
if (L.first != R.first)
|
|
return L.first > R.first;
|
|
return L.second.name < R.second.name; // Earlier name is better.
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
llvm::Expected<std::vector<SymbolInformation>>
|
|
getWorkspaceSymbols(llvm::StringRef Query, int Limit,
|
|
const SymbolIndex *const Index, llvm::StringRef HintPath) {
|
|
std::vector<SymbolInformation> Result;
|
|
if (Query.empty() || !Index)
|
|
return Result;
|
|
|
|
auto Names = splitQualifiedName(Query);
|
|
|
|
FuzzyFindRequest Req;
|
|
Req.Query = Names.second;
|
|
|
|
// FuzzyFind doesn't want leading :: qualifier
|
|
bool IsGlobalQuery = Names.first.consume_front("::");
|
|
// Restrict results to the scope in the query string if present (global or
|
|
// not).
|
|
if (IsGlobalQuery || !Names.first.empty())
|
|
Req.Scopes = {Names.first};
|
|
else
|
|
Req.AnyScope = true;
|
|
if (Limit)
|
|
Req.Limit = Limit;
|
|
TopN<ScoredSymbolInfo, ScoredSymbolGreater> Top(
|
|
Req.Limit ? *Req.Limit : std::numeric_limits<size_t>::max());
|
|
FuzzyMatcher Filter(Req.Query);
|
|
Index->fuzzyFind(Req, [HintPath, &Top, &Filter](const Symbol &Sym) {
|
|
// Prefer the definition over e.g. a function declaration in a header
|
|
auto &CD = Sym.Definition ? Sym.Definition : Sym.CanonicalDeclaration;
|
|
auto Uri = URI::parse(CD.FileURI);
|
|
if (!Uri) {
|
|
log("Workspace symbol: Could not parse URI '{0}' for symbol '{1}'.",
|
|
CD.FileURI, Sym.Name);
|
|
return;
|
|
}
|
|
auto Path = URI::resolve(*Uri, HintPath);
|
|
if (!Path) {
|
|
log("Workspace symbol: Could not resolve path for URI '{0}' for symbol "
|
|
"'{1}'.",
|
|
Uri->toString(), Sym.Name);
|
|
return;
|
|
}
|
|
Location L;
|
|
// Use HintPath as TUPath since there is no TU associated with this
|
|
// request.
|
|
L.uri = URIForFile::canonicalize(*Path, HintPath);
|
|
Position Start, End;
|
|
Start.line = CD.Start.line();
|
|
Start.character = CD.Start.column();
|
|
End.line = CD.End.line();
|
|
End.character = CD.End.column();
|
|
L.range = {Start, End};
|
|
SymbolKind SK = indexSymbolKindToSymbolKind(Sym.SymInfo.Kind);
|
|
std::string Scope = Sym.Scope;
|
|
llvm::StringRef ScopeRef = Scope;
|
|
ScopeRef.consume_back("::");
|
|
SymbolInformation Info = {Sym.Name, SK, L, ScopeRef};
|
|
|
|
SymbolQualitySignals Quality;
|
|
Quality.merge(Sym);
|
|
SymbolRelevanceSignals Relevance;
|
|
Relevance.Query = SymbolRelevanceSignals::Generic;
|
|
if (auto NameMatch = Filter.match(Sym.Name))
|
|
Relevance.NameMatch = *NameMatch;
|
|
else {
|
|
log("Workspace symbol: {0} didn't match query {1}", Sym.Name,
|
|
Filter.pattern());
|
|
return;
|
|
}
|
|
Relevance.merge(Sym);
|
|
auto Score =
|
|
evaluateSymbolAndRelevance(Quality.evaluate(), Relevance.evaluate());
|
|
dlog("FindSymbols: {0}{1} = {2}\n{3}{4}\n", Sym.Scope, Sym.Name, Score,
|
|
Quality, Relevance);
|
|
|
|
Top.push({Score, std::move(Info)});
|
|
});
|
|
for (auto &R : std::move(Top).items())
|
|
Result.push_back(std::move(R.second));
|
|
return Result;
|
|
}
|
|
|
|
namespace {
|
|
llvm::Optional<DocumentSymbol> declToSym(ASTContext &Ctx, const NamedDecl &ND) {
|
|
auto &SM = Ctx.getSourceManager();
|
|
|
|
SourceLocation NameLoc = findNameLoc(&ND);
|
|
// getFileLoc is a good choice for us, but we also need to make sure
|
|
// sourceLocToPosition won't switch files, so we call getSpellingLoc on top of
|
|
// that to make sure it does not switch files.
|
|
// FIXME: sourceLocToPosition should not switch files!
|
|
SourceLocation BeginLoc = SM.getSpellingLoc(SM.getFileLoc(ND.getBeginLoc()));
|
|
SourceLocation EndLoc = SM.getSpellingLoc(SM.getFileLoc(ND.getEndLoc()));
|
|
if (NameLoc.isInvalid() || BeginLoc.isInvalid() || EndLoc.isInvalid())
|
|
return llvm::None;
|
|
|
|
if (!SM.isWrittenInMainFile(NameLoc) || !SM.isWrittenInMainFile(BeginLoc) ||
|
|
!SM.isWrittenInMainFile(EndLoc))
|
|
return llvm::None;
|
|
|
|
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);
|
|
|
|
DocumentSymbol SI;
|
|
SI.name = printName(Ctx, ND);
|
|
SI.kind = SK;
|
|
SI.deprecated = ND.isDeprecated();
|
|
SI.range =
|
|
Range{sourceLocToPosition(SM, BeginLoc), sourceLocToPosition(SM, EndLoc)};
|
|
SI.selectionRange = Range{NameBegin, NameEnd};
|
|
if (!SI.range.contains(SI.selectionRange)) {
|
|
// 'selectionRange' must be contained in 'range', so in cases where clang
|
|
// reports unrelated ranges we need to reconcile somehow.
|
|
SI.range = SI.selectionRange;
|
|
}
|
|
return SI;
|
|
}
|
|
|
|
/// A helper class to build an outline for the parse AST. It traverse the AST
|
|
/// directly instead of using RecursiveASTVisitor (RAV) for three main reasons:
|
|
/// - there is no way to keep RAV from traversing subtrees we're not
|
|
/// interested in. E.g. not traversing function locals or implicit template
|
|
/// instantiations.
|
|
/// - it's easier to combine results of recursive passes, e.g.
|
|
/// - visiting decls is actually simple, so we don't hit the complicated
|
|
/// cases that RAV mostly helps with (types and expressions, etc.)
|
|
class DocumentOutline {
|
|
public:
|
|
DocumentOutline(ParsedAST &AST) : AST(AST) {}
|
|
|
|
/// Builds the document outline for the generated AST.
|
|
std::vector<DocumentSymbol> build() {
|
|
std::vector<DocumentSymbol> Results;
|
|
for (auto &TopLevel : AST.getLocalTopLevelDecls())
|
|
traverseDecl(TopLevel, Results);
|
|
return Results;
|
|
}
|
|
|
|
private:
|
|
enum class VisitKind { No, OnlyDecl, DeclAndChildren };
|
|
|
|
void traverseDecl(Decl *D, std::vector<DocumentSymbol> &Results) {
|
|
if (auto *Templ = llvm::dyn_cast<TemplateDecl>(D))
|
|
D = Templ->getTemplatedDecl();
|
|
auto *ND = llvm::dyn_cast<NamedDecl>(D);
|
|
if (!ND)
|
|
return;
|
|
VisitKind Visit = shouldVisit(ND);
|
|
if (Visit == VisitKind::No)
|
|
return;
|
|
llvm::Optional<DocumentSymbol> Sym = declToSym(AST.getASTContext(), *ND);
|
|
if (!Sym)
|
|
return;
|
|
if (Visit == VisitKind::DeclAndChildren)
|
|
traverseChildren(D, Sym->children);
|
|
Results.push_back(std::move(*Sym));
|
|
}
|
|
|
|
void traverseChildren(Decl *D, std::vector<DocumentSymbol> &Results) {
|
|
auto *Scope = llvm::dyn_cast<DeclContext>(D);
|
|
if (!Scope)
|
|
return;
|
|
for (auto *C : Scope->decls())
|
|
traverseDecl(C, Results);
|
|
}
|
|
|
|
VisitKind shouldVisit(NamedDecl *D) {
|
|
if (D->isImplicit())
|
|
return VisitKind::No;
|
|
|
|
if (auto Func = llvm::dyn_cast<FunctionDecl>(D)) {
|
|
// Some functions are implicit template instantiations, those should be
|
|
// ignored.
|
|
if (auto *Info = Func->getTemplateSpecializationInfo()) {
|
|
if (!Info->isExplicitInstantiationOrSpecialization())
|
|
return VisitKind::No;
|
|
}
|
|
// Only visit the function itself, do not visit the children (i.e.
|
|
// function parameters, etc.)
|
|
return VisitKind::OnlyDecl;
|
|
}
|
|
// Handle template instantiations. We have three cases to consider:
|
|
// - explicit instantiations, e.g. 'template class std::vector<int>;'
|
|
// Visit the decl itself (it's present in the code), but not the
|
|
// children.
|
|
// - implicit instantiations, i.e. not written by the user.
|
|
// Do not visit at all, they are not present in the code.
|
|
// - explicit specialization, e.g. 'template <> class vector<bool> {};'
|
|
// Visit both the decl and its children, both are written in the code.
|
|
if (auto *TemplSpec = llvm::dyn_cast<ClassTemplateSpecializationDecl>(D)) {
|
|
if (TemplSpec->isExplicitInstantiationOrSpecialization())
|
|
return TemplSpec->isExplicitSpecialization()
|
|
? VisitKind::DeclAndChildren
|
|
: VisitKind::OnlyDecl;
|
|
return VisitKind::No;
|
|
}
|
|
if (auto *TemplSpec = llvm::dyn_cast<VarTemplateSpecializationDecl>(D)) {
|
|
if (TemplSpec->isExplicitInstantiationOrSpecialization())
|
|
return TemplSpec->isExplicitSpecialization()
|
|
? VisitKind::DeclAndChildren
|
|
: VisitKind::OnlyDecl;
|
|
return VisitKind::No;
|
|
}
|
|
// For all other cases, visit both the children and the decl.
|
|
return VisitKind::DeclAndChildren;
|
|
}
|
|
|
|
ParsedAST &AST;
|
|
};
|
|
|
|
std::vector<DocumentSymbol> collectDocSymbols(ParsedAST &AST) {
|
|
return DocumentOutline(AST).build();
|
|
}
|
|
} // namespace
|
|
|
|
llvm::Expected<std::vector<DocumentSymbol>> getDocumentSymbols(ParsedAST &AST) {
|
|
return collectDocSymbols(AST);
|
|
}
|
|
|
|
} // namespace clangd
|
|
} // namespace clang
|