blob: 92769bb317b32aaaef8f5c11c68b5fe21d36d0f7 [file] [log] [blame]
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +00001//===--- FindSymbols.cpp ------------------------------------*- C++-*------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9#include "FindSymbols.h"
10
Marc-Andre Laperle1be69702018-07-05 19:35:01 +000011#include "AST.h"
12#include "ClangdUnit.h"
Sam McCall203cdee2018-06-07 06:55:59 +000013#include "FuzzyMatch.h"
Marc-Andre Laperle1be69702018-07-05 19:35:01 +000014#include "Logger.h"
Sam McCall203cdee2018-06-07 06:55:59 +000015#include "Quality.h"
Marc-Andre Laperle1be69702018-07-05 19:35:01 +000016#include "SourceCode.h"
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +000017#include "index/Index.h"
Ilya Biryukov19d75602018-11-23 15:21:19 +000018#include "clang/AST/DeclTemplate.h"
Marc-Andre Laperle1be69702018-07-05 19:35:01 +000019#include "clang/Index/IndexDataConsumer.h"
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +000020#include "clang/Index/IndexSymbol.h"
Marc-Andre Laperle1be69702018-07-05 19:35:01 +000021#include "clang/Index/IndexingAction.h"
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +000022#include "llvm/Support/FormatVariadic.h"
23#include "llvm/Support/Path.h"
Ilya Biryukov19d75602018-11-23 15:21:19 +000024#include "llvm/Support/ScopedPrinter.h"
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +000025
Sam McCall203cdee2018-06-07 06:55:59 +000026#define DEBUG_TYPE "FindSymbols"
27
Sam McCallc008af62018-10-20 15:30:37 +000028using namespace llvm;
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +000029namespace clang {
30namespace clangd {
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +000031namespace {
32
33// Convert a index::SymbolKind to clangd::SymbolKind (LSP)
34// Note, some are not perfect matches and should be improved when this LSP
35// issue is addressed:
36// https://github.com/Microsoft/language-server-protocol/issues/344
37SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) {
38 switch (Kind) {
39 case index::SymbolKind::Unknown:
40 return SymbolKind::Variable;
41 case index::SymbolKind::Module:
42 return SymbolKind::Module;
43 case index::SymbolKind::Namespace:
44 return SymbolKind::Namespace;
45 case index::SymbolKind::NamespaceAlias:
46 return SymbolKind::Namespace;
47 case index::SymbolKind::Macro:
48 return SymbolKind::String;
49 case index::SymbolKind::Enum:
50 return SymbolKind::Enum;
51 case index::SymbolKind::Struct:
52 return SymbolKind::Struct;
53 case index::SymbolKind::Class:
54 return SymbolKind::Class;
55 case index::SymbolKind::Protocol:
56 return SymbolKind::Interface;
57 case index::SymbolKind::Extension:
58 return SymbolKind::Interface;
59 case index::SymbolKind::Union:
60 return SymbolKind::Class;
61 case index::SymbolKind::TypeAlias:
62 return SymbolKind::Class;
63 case index::SymbolKind::Function:
64 return SymbolKind::Function;
65 case index::SymbolKind::Variable:
66 return SymbolKind::Variable;
67 case index::SymbolKind::Field:
68 return SymbolKind::Field;
69 case index::SymbolKind::EnumConstant:
70 return SymbolKind::EnumMember;
71 case index::SymbolKind::InstanceMethod:
72 case index::SymbolKind::ClassMethod:
73 case index::SymbolKind::StaticMethod:
74 return SymbolKind::Method;
75 case index::SymbolKind::InstanceProperty:
76 case index::SymbolKind::ClassProperty:
77 case index::SymbolKind::StaticProperty:
78 return SymbolKind::Property;
79 case index::SymbolKind::Constructor:
80 case index::SymbolKind::Destructor:
81 return SymbolKind::Method;
82 case index::SymbolKind::ConversionFunction:
83 return SymbolKind::Function;
84 case index::SymbolKind::Parameter:
85 return SymbolKind::Variable;
86 case index::SymbolKind::Using:
87 return SymbolKind::Namespace;
88 }
89 llvm_unreachable("invalid symbol kind");
90}
91
Sam McCall203cdee2018-06-07 06:55:59 +000092using ScoredSymbolInfo = std::pair<float, SymbolInformation>;
93struct ScoredSymbolGreater {
94 bool operator()(const ScoredSymbolInfo &L, const ScoredSymbolInfo &R) {
95 if (L.first != R.first)
96 return L.first > R.first;
97 return L.second.name < R.second.name; // Earlier name is better.
98 }
99};
100
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000101} // namespace
102
Sam McCallc008af62018-10-20 15:30:37 +0000103Expected<std::vector<SymbolInformation>>
Eric Liu53425f292018-06-19 09:33:53 +0000104getWorkspaceSymbols(StringRef Query, int Limit, const SymbolIndex *const Index,
105 StringRef HintPath) {
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000106 std::vector<SymbolInformation> Result;
107 if (Query.empty() || !Index)
108 return Result;
109
110 auto Names = splitQualifiedName(Query);
111
112 FuzzyFindRequest Req;
113 Req.Query = Names.second;
114
115 // FuzzyFind doesn't want leading :: qualifier
116 bool IsGlobalQuery = Names.first.consume_front("::");
117 // Restrict results to the scope in the query string if present (global or
118 // not).
119 if (IsGlobalQuery || !Names.first.empty())
120 Req.Scopes = {Names.first};
Eric Liub04869a2018-11-06 11:08:17 +0000121 else
122 Req.AnyScope = true;
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000123 if (Limit)
Kirill Bobyreve6dd0802018-09-13 14:27:03 +0000124 Req.Limit = Limit;
125 TopN<ScoredSymbolInfo, ScoredSymbolGreater> Top(
126 Req.Limit ? *Req.Limit : std::numeric_limits<size_t>::max());
Sam McCall203cdee2018-06-07 06:55:59 +0000127 FuzzyMatcher Filter(Req.Query);
Eric Liu53425f292018-06-19 09:33:53 +0000128 Index->fuzzyFind(Req, [HintPath, &Top, &Filter](const Symbol &Sym) {
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000129 // Prefer the definition over e.g. a function declaration in a header
130 auto &CD = Sym.Definition ? Sym.Definition : Sym.CanonicalDeclaration;
131 auto Uri = URI::parse(CD.FileURI);
132 if (!Uri) {
Sam McCallbed58852018-07-11 10:35:11 +0000133 log("Workspace symbol: Could not parse URI '{0}' for symbol '{1}'.",
134 CD.FileURI, Sym.Name);
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000135 return;
136 }
Eric Liu53425f292018-06-19 09:33:53 +0000137 auto Path = URI::resolve(*Uri, HintPath);
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000138 if (!Path) {
Sam McCallbed58852018-07-11 10:35:11 +0000139 log("Workspace symbol: Could not resolve path for URI '{0}' for symbol "
140 "'{1}'.",
141 Uri->toString(), Sym.Name);
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000142 return;
143 }
144 Location L;
Eric Liu4d814a92018-11-28 10:30:42 +0000145 // Use HintPath as TUPath since there is no TU associated with this
146 // request.
147 L.uri = URIForFile::canonicalize(*Path, HintPath);
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000148 Position Start, End;
Haojian Wub515fab2018-10-18 10:43:50 +0000149 Start.line = CD.Start.line();
150 Start.character = CD.Start.column();
151 End.line = CD.End.line();
152 End.character = CD.End.column();
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000153 L.range = {Start, End};
154 SymbolKind SK = indexSymbolKindToSymbolKind(Sym.SymInfo.Kind);
155 std::string Scope = Sym.Scope;
156 StringRef ScopeRef = Scope;
157 ScopeRef.consume_back("::");
Sam McCall203cdee2018-06-07 06:55:59 +0000158 SymbolInformation Info = {Sym.Name, SK, L, ScopeRef};
159
160 SymbolQualitySignals Quality;
161 Quality.merge(Sym);
162 SymbolRelevanceSignals Relevance;
163 Relevance.Query = SymbolRelevanceSignals::Generic;
164 if (auto NameMatch = Filter.match(Sym.Name))
165 Relevance.NameMatch = *NameMatch;
166 else {
Sam McCallbed58852018-07-11 10:35:11 +0000167 log("Workspace symbol: {0} didn't match query {1}", Sym.Name,
168 Filter.pattern());
Sam McCall203cdee2018-06-07 06:55:59 +0000169 return;
170 }
171 Relevance.merge(Sym);
172 auto Score =
173 evaluateSymbolAndRelevance(Quality.evaluate(), Relevance.evaluate());
Sam McCallbed58852018-07-11 10:35:11 +0000174 dlog("FindSymbols: {0}{1} = {2}\n{3}{4}\n", Sym.Scope, Sym.Name, Score,
175 Quality, Relevance);
Sam McCall203cdee2018-06-07 06:55:59 +0000176
177 Top.push({Score, std::move(Info)});
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000178 });
Sam McCall203cdee2018-06-07 06:55:59 +0000179 for (auto &R : std::move(Top).items())
180 Result.push_back(std::move(R.second));
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000181 return Result;
182}
183
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000184namespace {
Ilya Biryukov19d75602018-11-23 15:21:19 +0000185llvm::Optional<DocumentSymbol> declToSym(ASTContext &Ctx, const NamedDecl &ND) {
186 auto &SM = Ctx.getSourceManager();
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000187
Ilya Biryukov19d75602018-11-23 15:21:19 +0000188 SourceLocation NameLoc = findNameLoc(&ND);
189 // getFileLoc is a good choice for us, but we also need to make sure
190 // sourceLocToPosition won't switch files, so we call getSpellingLoc on top of
191 // that to make sure it does not switch files.
192 // FIXME: sourceLocToPosition should not switch files!
Ilya Biryukov22fa4652019-01-03 13:28:05 +0000193 SourceLocation BeginLoc = SM.getSpellingLoc(SM.getFileLoc(ND.getBeginLoc()));
Ilya Biryukov19d75602018-11-23 15:21:19 +0000194 SourceLocation EndLoc = SM.getSpellingLoc(SM.getFileLoc(ND.getEndLoc()));
195 if (NameLoc.isInvalid() || BeginLoc.isInvalid() || EndLoc.isInvalid())
196 return llvm::None;
197
198 if (!SM.isWrittenInMainFile(NameLoc) || !SM.isWrittenInMainFile(BeginLoc) ||
199 !SM.isWrittenInMainFile(EndLoc))
200 return llvm::None;
201
202 Position NameBegin = sourceLocToPosition(SM, NameLoc);
203 Position NameEnd = sourceLocToPosition(
204 SM, Lexer::getLocForEndOfToken(NameLoc, 0, SM, Ctx.getLangOpts()));
205
206 index::SymbolInfo SymInfo = index::getSymbolInfo(&ND);
207 // FIXME: this is not classifying constructors, destructors and operators
208 // correctly (they're all "methods").
209 SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind);
210
211 DocumentSymbol SI;
212 SI.name = printName(Ctx, ND);
213 SI.kind = SK;
214 SI.deprecated = ND.isDeprecated();
215 SI.range =
216 Range{sourceLocToPosition(SM, BeginLoc), sourceLocToPosition(SM, EndLoc)};
217 SI.selectionRange = Range{NameBegin, NameEnd};
218 if (!SI.range.contains(SI.selectionRange)) {
219 // 'selectionRange' must be contained in 'range', so in cases where clang
220 // reports unrelated ranges we need to reconcile somehow.
221 SI.range = SI.selectionRange;
222 }
223 return SI;
224}
225
226/// A helper class to build an outline for the parse AST. It traverse the AST
227/// directly instead of using RecursiveASTVisitor (RAV) for three main reasons:
228/// - there is no way to keep RAV from traversing subtrees we're not
229/// interested in. E.g. not traversing function locals or implicit template
230/// instantiations.
231/// - it's easier to combine results of recursive passes, e.g.
232/// - visiting decls is actually simple, so we don't hit the complicated
233/// cases that RAV mostly helps with (types and expressions, etc.)
234class DocumentOutline {
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000235public:
Ilya Biryukov19d75602018-11-23 15:21:19 +0000236 DocumentOutline(ParsedAST &AST) : AST(AST) {}
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000237
Ilya Biryukov19d75602018-11-23 15:21:19 +0000238 /// Builds the document outline for the generated AST.
239 std::vector<DocumentSymbol> build() {
240 std::vector<DocumentSymbol> Results;
241 for (auto &TopLevel : AST.getLocalTopLevelDecls())
242 traverseDecl(TopLevel, Results);
243 return Results;
244 }
245
246private:
247 enum class VisitKind { No, OnlyDecl, DeclAndChildren };
248
249 void traverseDecl(Decl *D, std::vector<DocumentSymbol> &Results) {
250 if (auto *Templ = llvm::dyn_cast<TemplateDecl>(D))
251 D = Templ->getTemplatedDecl();
252 auto *ND = llvm::dyn_cast<NamedDecl>(D);
253 if (!ND)
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000254 return;
Ilya Biryukov19d75602018-11-23 15:21:19 +0000255 VisitKind Visit = shouldVisit(ND);
256 if (Visit == VisitKind::No)
257 return;
258 llvm::Optional<DocumentSymbol> Sym = declToSym(AST.getASTContext(), *ND);
259 if (!Sym)
260 return;
261 if (Visit == VisitKind::DeclAndChildren)
262 traverseChildren(D, Sym->children);
263 Results.push_back(std::move(*Sym));
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000264 }
265
Ilya Biryukov19d75602018-11-23 15:21:19 +0000266 void traverseChildren(Decl *D, std::vector<DocumentSymbol> &Results) {
267 auto *Scope = llvm::dyn_cast<DeclContext>(D);
268 if (!Scope)
269 return;
270 for (auto *C : Scope->decls())
271 traverseDecl(C, Results);
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000272 }
273
Ilya Biryukov19d75602018-11-23 15:21:19 +0000274 VisitKind shouldVisit(NamedDecl *D) {
275 if (D->isImplicit())
276 return VisitKind::No;
277
278 if (auto Func = llvm::dyn_cast<FunctionDecl>(D)) {
279 // Some functions are implicit template instantiations, those should be
280 // ignored.
281 if (auto *Info = Func->getTemplateSpecializationInfo()) {
282 if (!Info->isExplicitInstantiationOrSpecialization())
283 return VisitKind::No;
284 }
285 // Only visit the function itself, do not visit the children (i.e.
286 // function parameters, etc.)
287 return VisitKind::OnlyDecl;
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000288 }
Ilya Biryukov19d75602018-11-23 15:21:19 +0000289 // Handle template instantiations. We have three cases to consider:
290 // - explicit instantiations, e.g. 'template class std::vector<int>;'
291 // Visit the decl itself (it's present in the code), but not the
292 // children.
293 // - implicit instantiations, i.e. not written by the user.
294 // Do not visit at all, they are not present in the code.
295 // - explicit specialization, e.g. 'template <> class vector<bool> {};'
296 // Visit both the decl and its children, both are written in the code.
297 if (auto *TemplSpec = llvm::dyn_cast<ClassTemplateSpecializationDecl>(D)) {
298 if (TemplSpec->isExplicitInstantiationOrSpecialization())
299 return TemplSpec->isExplicitSpecialization()
300 ? VisitKind::DeclAndChildren
301 : VisitKind::OnlyDecl;
302 return VisitKind::No;
303 }
304 if (auto *TemplSpec = llvm::dyn_cast<VarTemplateSpecializationDecl>(D)) {
305 if (TemplSpec->isExplicitInstantiationOrSpecialization())
306 return TemplSpec->isExplicitSpecialization()
307 ? VisitKind::DeclAndChildren
308 : VisitKind::OnlyDecl;
309 return VisitKind::No;
310 }
311 // For all other cases, visit both the children and the decl.
312 return VisitKind::DeclAndChildren;
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000313 }
Ilya Biryukov19d75602018-11-23 15:21:19 +0000314
315 ParsedAST &AST;
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000316};
Ilya Biryukov19d75602018-11-23 15:21:19 +0000317
318std::vector<DocumentSymbol> collectDocSymbols(ParsedAST &AST) {
319 return DocumentOutline(AST).build();
320}
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000321} // namespace
322
Ilya Biryukov19d75602018-11-23 15:21:19 +0000323llvm::Expected<std::vector<DocumentSymbol>> getDocumentSymbols(ParsedAST &AST) {
324 return collectDocSymbols(AST);
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000325}
326
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000327} // namespace clangd
328} // namespace clang