blob: 208e78c65b947fb1b86e4b6090f7725ee23f3ec3 [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
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +000028namespace clang {
29namespace clangd {
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +000030namespace {
31
32// Convert a index::SymbolKind to clangd::SymbolKind (LSP)
33// Note, some are not perfect matches and should be improved when this LSP
34// issue is addressed:
35// https://github.com/Microsoft/language-server-protocol/issues/344
36SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) {
37 switch (Kind) {
38 case index::SymbolKind::Unknown:
39 return SymbolKind::Variable;
40 case index::SymbolKind::Module:
41 return SymbolKind::Module;
42 case index::SymbolKind::Namespace:
43 return SymbolKind::Namespace;
44 case index::SymbolKind::NamespaceAlias:
45 return SymbolKind::Namespace;
46 case index::SymbolKind::Macro:
47 return SymbolKind::String;
48 case index::SymbolKind::Enum:
49 return SymbolKind::Enum;
50 case index::SymbolKind::Struct:
51 return SymbolKind::Struct;
52 case index::SymbolKind::Class:
53 return SymbolKind::Class;
54 case index::SymbolKind::Protocol:
55 return SymbolKind::Interface;
56 case index::SymbolKind::Extension:
57 return SymbolKind::Interface;
58 case index::SymbolKind::Union:
59 return SymbolKind::Class;
60 case index::SymbolKind::TypeAlias:
61 return SymbolKind::Class;
62 case index::SymbolKind::Function:
63 return SymbolKind::Function;
64 case index::SymbolKind::Variable:
65 return SymbolKind::Variable;
66 case index::SymbolKind::Field:
67 return SymbolKind::Field;
68 case index::SymbolKind::EnumConstant:
69 return SymbolKind::EnumMember;
70 case index::SymbolKind::InstanceMethod:
71 case index::SymbolKind::ClassMethod:
72 case index::SymbolKind::StaticMethod:
73 return SymbolKind::Method;
74 case index::SymbolKind::InstanceProperty:
75 case index::SymbolKind::ClassProperty:
76 case index::SymbolKind::StaticProperty:
77 return SymbolKind::Property;
78 case index::SymbolKind::Constructor:
79 case index::SymbolKind::Destructor:
80 return SymbolKind::Method;
81 case index::SymbolKind::ConversionFunction:
82 return SymbolKind::Function;
83 case index::SymbolKind::Parameter:
84 return SymbolKind::Variable;
85 case index::SymbolKind::Using:
86 return SymbolKind::Namespace;
87 }
88 llvm_unreachable("invalid symbol kind");
89}
90
Sam McCall203cdee2018-06-07 06:55:59 +000091using ScoredSymbolInfo = std::pair<float, SymbolInformation>;
92struct ScoredSymbolGreater {
93 bool operator()(const ScoredSymbolInfo &L, const ScoredSymbolInfo &R) {
94 if (L.first != R.first)
95 return L.first > R.first;
96 return L.second.name < R.second.name; // Earlier name is better.
97 }
98};
99
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000100} // namespace
101
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000102llvm::Expected<std::vector<SymbolInformation>>
103getWorkspaceSymbols(llvm::StringRef Query, int Limit,
104 const SymbolIndex *const Index, llvm::StringRef HintPath) {
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000105 std::vector<SymbolInformation> Result;
106 if (Query.empty() || !Index)
107 return Result;
108
109 auto Names = splitQualifiedName(Query);
110
111 FuzzyFindRequest Req;
112 Req.Query = Names.second;
113
114 // FuzzyFind doesn't want leading :: qualifier
115 bool IsGlobalQuery = Names.first.consume_front("::");
116 // Restrict results to the scope in the query string if present (global or
117 // not).
118 if (IsGlobalQuery || !Names.first.empty())
119 Req.Scopes = {Names.first};
Eric Liub04869a2018-11-06 11:08:17 +0000120 else
121 Req.AnyScope = true;
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000122 if (Limit)
Kirill Bobyreve6dd0802018-09-13 14:27:03 +0000123 Req.Limit = Limit;
124 TopN<ScoredSymbolInfo, ScoredSymbolGreater> Top(
125 Req.Limit ? *Req.Limit : std::numeric_limits<size_t>::max());
Sam McCall203cdee2018-06-07 06:55:59 +0000126 FuzzyMatcher Filter(Req.Query);
Eric Liu53425f292018-06-19 09:33:53 +0000127 Index->fuzzyFind(Req, [HintPath, &Top, &Filter](const Symbol &Sym) {
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000128 // Prefer the definition over e.g. a function declaration in a header
129 auto &CD = Sym.Definition ? Sym.Definition : Sym.CanonicalDeclaration;
130 auto Uri = URI::parse(CD.FileURI);
131 if (!Uri) {
Sam McCallbed58852018-07-11 10:35:11 +0000132 log("Workspace symbol: Could not parse URI '{0}' for symbol '{1}'.",
133 CD.FileURI, Sym.Name);
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000134 return;
135 }
Eric Liu53425f292018-06-19 09:33:53 +0000136 auto Path = URI::resolve(*Uri, HintPath);
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000137 if (!Path) {
Sam McCallbed58852018-07-11 10:35:11 +0000138 log("Workspace symbol: Could not resolve path for URI '{0}' for symbol "
139 "'{1}'.",
140 Uri->toString(), Sym.Name);
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000141 return;
142 }
143 Location L;
Eric Liu4d814a92018-11-28 10:30:42 +0000144 // Use HintPath as TUPath since there is no TU associated with this
145 // request.
146 L.uri = URIForFile::canonicalize(*Path, HintPath);
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000147 Position Start, End;
Haojian Wub515fab2018-10-18 10:43:50 +0000148 Start.line = CD.Start.line();
149 Start.character = CD.Start.column();
150 End.line = CD.End.line();
151 End.character = CD.End.column();
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000152 L.range = {Start, End};
153 SymbolKind SK = indexSymbolKindToSymbolKind(Sym.SymInfo.Kind);
154 std::string Scope = Sym.Scope;
Ilya Biryukovf2001aa2019-01-07 15:45:19 +0000155 llvm::StringRef ScopeRef = Scope;
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000156 ScopeRef.consume_back("::");
Sam McCall203cdee2018-06-07 06:55:59 +0000157 SymbolInformation Info = {Sym.Name, SK, L, ScopeRef};
158
159 SymbolQualitySignals Quality;
160 Quality.merge(Sym);
161 SymbolRelevanceSignals Relevance;
162 Relevance.Query = SymbolRelevanceSignals::Generic;
163 if (auto NameMatch = Filter.match(Sym.Name))
164 Relevance.NameMatch = *NameMatch;
165 else {
Sam McCallbed58852018-07-11 10:35:11 +0000166 log("Workspace symbol: {0} didn't match query {1}", Sym.Name,
167 Filter.pattern());
Sam McCall203cdee2018-06-07 06:55:59 +0000168 return;
169 }
170 Relevance.merge(Sym);
171 auto Score =
172 evaluateSymbolAndRelevance(Quality.evaluate(), Relevance.evaluate());
Sam McCallbed58852018-07-11 10:35:11 +0000173 dlog("FindSymbols: {0}{1} = {2}\n{3}{4}\n", Sym.Scope, Sym.Name, Score,
174 Quality, Relevance);
Sam McCall203cdee2018-06-07 06:55:59 +0000175
176 Top.push({Score, std::move(Info)});
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000177 });
Sam McCall203cdee2018-06-07 06:55:59 +0000178 for (auto &R : std::move(Top).items())
179 Result.push_back(std::move(R.second));
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000180 return Result;
181}
182
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000183namespace {
Ilya Biryukov19d75602018-11-23 15:21:19 +0000184llvm::Optional<DocumentSymbol> declToSym(ASTContext &Ctx, const NamedDecl &ND) {
185 auto &SM = Ctx.getSourceManager();
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000186
Ilya Biryukov19d75602018-11-23 15:21:19 +0000187 SourceLocation NameLoc = findNameLoc(&ND);
188 // getFileLoc is a good choice for us, but we also need to make sure
189 // sourceLocToPosition won't switch files, so we call getSpellingLoc on top of
190 // that to make sure it does not switch files.
191 // FIXME: sourceLocToPosition should not switch files!
Ilya Biryukov22fa4652019-01-03 13:28:05 +0000192 SourceLocation BeginLoc = SM.getSpellingLoc(SM.getFileLoc(ND.getBeginLoc()));
Ilya Biryukov19d75602018-11-23 15:21:19 +0000193 SourceLocation EndLoc = SM.getSpellingLoc(SM.getFileLoc(ND.getEndLoc()));
194 if (NameLoc.isInvalid() || BeginLoc.isInvalid() || EndLoc.isInvalid())
195 return llvm::None;
196
197 if (!SM.isWrittenInMainFile(NameLoc) || !SM.isWrittenInMainFile(BeginLoc) ||
198 !SM.isWrittenInMainFile(EndLoc))
199 return llvm::None;
200
201 Position NameBegin = sourceLocToPosition(SM, NameLoc);
202 Position NameEnd = sourceLocToPosition(
203 SM, Lexer::getLocForEndOfToken(NameLoc, 0, SM, Ctx.getLangOpts()));
204
205 index::SymbolInfo SymInfo = index::getSymbolInfo(&ND);
206 // FIXME: this is not classifying constructors, destructors and operators
207 // correctly (they're all "methods").
208 SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind);
209
210 DocumentSymbol SI;
211 SI.name = printName(Ctx, ND);
212 SI.kind = SK;
213 SI.deprecated = ND.isDeprecated();
214 SI.range =
215 Range{sourceLocToPosition(SM, BeginLoc), sourceLocToPosition(SM, EndLoc)};
216 SI.selectionRange = Range{NameBegin, NameEnd};
217 if (!SI.range.contains(SI.selectionRange)) {
218 // 'selectionRange' must be contained in 'range', so in cases where clang
219 // reports unrelated ranges we need to reconcile somehow.
220 SI.range = SI.selectionRange;
221 }
222 return SI;
223}
224
225/// A helper class to build an outline for the parse AST. It traverse the AST
226/// directly instead of using RecursiveASTVisitor (RAV) for three main reasons:
227/// - there is no way to keep RAV from traversing subtrees we're not
228/// interested in. E.g. not traversing function locals or implicit template
229/// instantiations.
230/// - it's easier to combine results of recursive passes, e.g.
231/// - visiting decls is actually simple, so we don't hit the complicated
232/// cases that RAV mostly helps with (types and expressions, etc.)
233class DocumentOutline {
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000234public:
Ilya Biryukov19d75602018-11-23 15:21:19 +0000235 DocumentOutline(ParsedAST &AST) : AST(AST) {}
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000236
Ilya Biryukov19d75602018-11-23 15:21:19 +0000237 /// Builds the document outline for the generated AST.
238 std::vector<DocumentSymbol> build() {
239 std::vector<DocumentSymbol> Results;
240 for (auto &TopLevel : AST.getLocalTopLevelDecls())
241 traverseDecl(TopLevel, Results);
242 return Results;
243 }
244
245private:
246 enum class VisitKind { No, OnlyDecl, DeclAndChildren };
247
248 void traverseDecl(Decl *D, std::vector<DocumentSymbol> &Results) {
249 if (auto *Templ = llvm::dyn_cast<TemplateDecl>(D))
250 D = Templ->getTemplatedDecl();
251 auto *ND = llvm::dyn_cast<NamedDecl>(D);
252 if (!ND)
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000253 return;
Ilya Biryukov19d75602018-11-23 15:21:19 +0000254 VisitKind Visit = shouldVisit(ND);
255 if (Visit == VisitKind::No)
256 return;
257 llvm::Optional<DocumentSymbol> Sym = declToSym(AST.getASTContext(), *ND);
258 if (!Sym)
259 return;
260 if (Visit == VisitKind::DeclAndChildren)
261 traverseChildren(D, Sym->children);
262 Results.push_back(std::move(*Sym));
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000263 }
264
Ilya Biryukov19d75602018-11-23 15:21:19 +0000265 void traverseChildren(Decl *D, std::vector<DocumentSymbol> &Results) {
266 auto *Scope = llvm::dyn_cast<DeclContext>(D);
267 if (!Scope)
268 return;
269 for (auto *C : Scope->decls())
270 traverseDecl(C, Results);
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000271 }
272
Ilya Biryukov19d75602018-11-23 15:21:19 +0000273 VisitKind shouldVisit(NamedDecl *D) {
274 if (D->isImplicit())
275 return VisitKind::No;
276
277 if (auto Func = llvm::dyn_cast<FunctionDecl>(D)) {
278 // Some functions are implicit template instantiations, those should be
279 // ignored.
280 if (auto *Info = Func->getTemplateSpecializationInfo()) {
281 if (!Info->isExplicitInstantiationOrSpecialization())
282 return VisitKind::No;
283 }
284 // Only visit the function itself, do not visit the children (i.e.
285 // function parameters, etc.)
286 return VisitKind::OnlyDecl;
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000287 }
Ilya Biryukov19d75602018-11-23 15:21:19 +0000288 // Handle template instantiations. We have three cases to consider:
289 // - explicit instantiations, e.g. 'template class std::vector<int>;'
290 // Visit the decl itself (it's present in the code), but not the
291 // children.
292 // - implicit instantiations, i.e. not written by the user.
293 // Do not visit at all, they are not present in the code.
294 // - explicit specialization, e.g. 'template <> class vector<bool> {};'
295 // Visit both the decl and its children, both are written in the code.
296 if (auto *TemplSpec = llvm::dyn_cast<ClassTemplateSpecializationDecl>(D)) {
297 if (TemplSpec->isExplicitInstantiationOrSpecialization())
298 return TemplSpec->isExplicitSpecialization()
299 ? VisitKind::DeclAndChildren
300 : VisitKind::OnlyDecl;
301 return VisitKind::No;
302 }
303 if (auto *TemplSpec = llvm::dyn_cast<VarTemplateSpecializationDecl>(D)) {
304 if (TemplSpec->isExplicitInstantiationOrSpecialization())
305 return TemplSpec->isExplicitSpecialization()
306 ? VisitKind::DeclAndChildren
307 : VisitKind::OnlyDecl;
308 return VisitKind::No;
309 }
310 // For all other cases, visit both the children and the decl.
311 return VisitKind::DeclAndChildren;
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000312 }
Ilya Biryukov19d75602018-11-23 15:21:19 +0000313
314 ParsedAST &AST;
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000315};
Ilya Biryukov19d75602018-11-23 15:21:19 +0000316
317std::vector<DocumentSymbol> collectDocSymbols(ParsedAST &AST) {
318 return DocumentOutline(AST).build();
319}
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000320} // namespace
321
Ilya Biryukov19d75602018-11-23 15:21:19 +0000322llvm::Expected<std::vector<DocumentSymbol>> getDocumentSymbols(ParsedAST &AST) {
323 return collectDocSymbols(AST);
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000324}
325
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000326} // namespace clangd
327} // namespace clang