blob: 1dcd20522d3802a5a92a745e0476d5c810261a30 [file] [log] [blame]
Sam McCallc5707b62018-05-15 17:43:27 +00001//===--- Quality.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 "Quality.h"
Sam McCall3f0243f2018-07-03 08:09:29 +000010#include "FileDistance.h"
Eric Liu09c3c372018-06-15 08:58:12 +000011#include "URI.h"
Sam McCallc5707b62018-05-15 17:43:27 +000012#include "index/Index.h"
Ilya Biryukovf0296462018-06-04 14:50:59 +000013#include "clang/AST/ASTContext.h"
Eric Liu5d2a8072018-07-23 10:56:37 +000014#include "clang/AST/Decl.h"
Eric Liu8944f0e2018-07-05 08:14:04 +000015#include "clang/AST/DeclCXX.h"
Eric Liu5d2a8072018-07-23 10:56:37 +000016#include "clang/AST/DeclTemplate.h"
Sam McCall4a3c69b2018-06-06 08:53:36 +000017#include "clang/AST/DeclVisitor.h"
Sam McCall3f0243f2018-07-03 08:09:29 +000018#include "clang/Basic/CharInfo.h"
Ilya Biryukovf0296462018-06-04 14:50:59 +000019#include "clang/Basic/SourceManager.h"
Sam McCallc5707b62018-05-15 17:43:27 +000020#include "clang/Sema/CodeCompleteConsumer.h"
Eric Liu8944f0e2018-07-05 08:14:04 +000021#include "llvm/Support/Casting.h"
Sam McCallc5707b62018-05-15 17:43:27 +000022#include "llvm/Support/FormatVariadic.h"
23#include "llvm/Support/MathExtras.h"
24#include "llvm/Support/raw_ostream.h"
Sam McCall3f0243f2018-07-03 08:09:29 +000025#include <cmath>
Sam McCallc5707b62018-05-15 17:43:27 +000026
27namespace clang {
28namespace clangd {
29using namespace llvm;
Sam McCalle018b362018-06-08 09:36:34 +000030static bool IsReserved(StringRef Name) {
31 // FIXME: Should we exclude _Bool and others recognized by the standard?
32 return Name.size() >= 2 && Name[0] == '_' &&
33 (isUppercase(Name[1]) || Name[1] == '_');
34}
Sam McCallc5707b62018-05-15 17:43:27 +000035
Ilya Biryukovf0296462018-06-04 14:50:59 +000036static bool hasDeclInMainFile(const Decl &D) {
37 auto &SourceMgr = D.getASTContext().getSourceManager();
38 for (auto *Redecl : D.redecls()) {
39 auto Loc = SourceMgr.getSpellingLoc(Redecl->getLocation());
40 if (SourceMgr.isWrittenInMainFile(Loc))
41 return true;
42 }
43 return false;
44}
45
Kirill Bobyrev47d7f522018-07-11 14:49:49 +000046static bool hasUsingDeclInMainFile(const CodeCompletionResult &R) {
47 const auto &Context = R.Declaration->getASTContext();
48 const auto &SourceMgr = Context.getSourceManager();
49 if (R.ShadowDecl) {
50 const auto Loc = SourceMgr.getExpansionLoc(R.ShadowDecl->getLocation());
51 if (SourceMgr.isWrittenInMainFile(Loc))
52 return true;
53 }
54 return false;
55}
56
Sam McCall4a3c69b2018-06-06 08:53:36 +000057static SymbolQualitySignals::SymbolCategory categorize(const NamedDecl &ND) {
58 class Switch
59 : public ConstDeclVisitor<Switch, SymbolQualitySignals::SymbolCategory> {
60 public:
61#define MAP(DeclType, Category) \
62 SymbolQualitySignals::SymbolCategory Visit##DeclType(const DeclType *) { \
63 return SymbolQualitySignals::Category; \
64 }
65 MAP(NamespaceDecl, Namespace);
66 MAP(NamespaceAliasDecl, Namespace);
67 MAP(TypeDecl, Type);
68 MAP(TypeAliasTemplateDecl, Type);
69 MAP(ClassTemplateDecl, Type);
70 MAP(ValueDecl, Variable);
71 MAP(VarTemplateDecl, Variable);
72 MAP(FunctionDecl, Function);
73 MAP(FunctionTemplateDecl, Function);
74 MAP(Decl, Unknown);
75#undef MAP
76 };
77 return Switch().Visit(&ND);
78}
79
Kirill Bobyrev7cf29bc2018-07-05 09:37:26 +000080static SymbolQualitySignals::SymbolCategory
81categorize(const CodeCompletionResult &R) {
Sam McCallc3b5bad2018-06-14 13:42:21 +000082 if (R.Declaration)
83 return categorize(*R.Declaration);
84 if (R.Kind == CodeCompletionResult::RK_Macro)
85 return SymbolQualitySignals::Macro;
86 // Everything else is a keyword or a pattern. Patterns are mostly keywords
87 // too, except a few which we recognize by cursor kind.
88 switch (R.CursorKind) {
Kirill Bobyrev7cf29bc2018-07-05 09:37:26 +000089 case CXCursor_CXXMethod:
90 return SymbolQualitySignals::Function;
91 case CXCursor_ModuleImportDecl:
92 return SymbolQualitySignals::Namespace;
93 case CXCursor_MacroDefinition:
94 return SymbolQualitySignals::Macro;
95 case CXCursor_TypeRef:
96 return SymbolQualitySignals::Type;
97 case CXCursor_MemberRef:
98 return SymbolQualitySignals::Variable;
99 default:
100 return SymbolQualitySignals::Keyword;
Sam McCallc3b5bad2018-06-14 13:42:21 +0000101 }
102}
103
Sam McCall4a3c69b2018-06-06 08:53:36 +0000104static SymbolQualitySignals::SymbolCategory
105categorize(const index::SymbolInfo &D) {
106 switch (D.Kind) {
Kirill Bobyrev7cf29bc2018-07-05 09:37:26 +0000107 case index::SymbolKind::Namespace:
108 case index::SymbolKind::NamespaceAlias:
109 return SymbolQualitySignals::Namespace;
110 case index::SymbolKind::Macro:
111 return SymbolQualitySignals::Macro;
112 case index::SymbolKind::Enum:
113 case index::SymbolKind::Struct:
114 case index::SymbolKind::Class:
115 case index::SymbolKind::Protocol:
116 case index::SymbolKind::Extension:
117 case index::SymbolKind::Union:
118 case index::SymbolKind::TypeAlias:
119 return SymbolQualitySignals::Type;
120 case index::SymbolKind::Function:
121 case index::SymbolKind::ClassMethod:
122 case index::SymbolKind::InstanceMethod:
123 case index::SymbolKind::StaticMethod:
124 case index::SymbolKind::InstanceProperty:
125 case index::SymbolKind::ClassProperty:
126 case index::SymbolKind::StaticProperty:
127 case index::SymbolKind::Constructor:
128 case index::SymbolKind::Destructor:
129 case index::SymbolKind::ConversionFunction:
130 return SymbolQualitySignals::Function;
131 case index::SymbolKind::Variable:
132 case index::SymbolKind::Field:
133 case index::SymbolKind::EnumConstant:
134 case index::SymbolKind::Parameter:
135 return SymbolQualitySignals::Variable;
136 case index::SymbolKind::Using:
137 case index::SymbolKind::Module:
138 case index::SymbolKind::Unknown:
139 return SymbolQualitySignals::Unknown;
Sam McCall4a3c69b2018-06-06 08:53:36 +0000140 }
Tim Northover0698e962018-06-06 13:28:49 +0000141 llvm_unreachable("Unknown index::SymbolKind");
Sam McCall4a3c69b2018-06-06 08:53:36 +0000142}
143
Eric Liu5d2a8072018-07-23 10:56:37 +0000144static bool isInstanceMember(const NamedDecl *ND) {
145 if (!ND)
146 return false;
147 if (const auto *TP = dyn_cast<FunctionTemplateDecl>(ND))
148 ND = TP->TemplateDecl::getTemplatedDecl();
149 if (const auto *CM = dyn_cast<CXXMethodDecl>(ND))
150 return !CM->isStatic();
151 return isa<FieldDecl>(ND); // Note that static fields are VarDecl.
152}
153
154static bool isInstanceMember(const index::SymbolInfo &D) {
155 switch (D.Kind) {
156 case index::SymbolKind::InstanceMethod:
157 case index::SymbolKind::InstanceProperty:
158 case index::SymbolKind::Field:
159 return true;
160 default:
161 return false;
162 }
163}
164
Sam McCallc5707b62018-05-15 17:43:27 +0000165void SymbolQualitySignals::merge(const CodeCompletionResult &SemaCCResult) {
Sam McCallc5707b62018-05-15 17:43:27 +0000166 if (SemaCCResult.Availability == CXAvailability_Deprecated)
167 Deprecated = true;
Sam McCall4a3c69b2018-06-06 08:53:36 +0000168
Sam McCallc3b5bad2018-06-14 13:42:21 +0000169 Category = categorize(SemaCCResult);
Sam McCalle018b362018-06-08 09:36:34 +0000170
171 if (SemaCCResult.Declaration) {
172 if (auto *ID = SemaCCResult.Declaration->getIdentifier())
173 ReservedName = ReservedName || IsReserved(ID->getName());
174 } else if (SemaCCResult.Kind == CodeCompletionResult::RK_Macro)
175 ReservedName = ReservedName || IsReserved(SemaCCResult.Macro->getName());
Sam McCallc5707b62018-05-15 17:43:27 +0000176}
177
178void SymbolQualitySignals::merge(const Symbol &IndexResult) {
179 References = std::max(IndexResult.References, References);
Sam McCall4a3c69b2018-06-06 08:53:36 +0000180 Category = categorize(IndexResult.SymInfo);
Sam McCalle018b362018-06-08 09:36:34 +0000181 ReservedName = ReservedName || IsReserved(IndexResult.Name);
Sam McCallc5707b62018-05-15 17:43:27 +0000182}
183
184float SymbolQualitySignals::evaluate() const {
185 float Score = 1;
186
187 // This avoids a sharp gradient for tail symbols, and also neatly avoids the
188 // question of whether 0 references means a bad symbol or missing data.
Eric Liucdc5f6a2018-06-28 16:51:12 +0000189 if (References >= 10)
190 Score *= std::log10(References);
Sam McCallc5707b62018-05-15 17:43:27 +0000191
Sam McCallc5707b62018-05-15 17:43:27 +0000192 if (Deprecated)
Aaron Ballman215e4712018-05-18 13:18:41 +0000193 Score *= 0.1f;
Sam McCalle018b362018-06-08 09:36:34 +0000194 if (ReservedName)
195 Score *= 0.1f;
Sam McCallc5707b62018-05-15 17:43:27 +0000196
Sam McCall4a3c69b2018-06-06 08:53:36 +0000197 switch (Category) {
Kirill Bobyrev7cf29bc2018-07-05 09:37:26 +0000198 case Keyword: // Often relevant, but misses most signals.
199 Score *= 4; // FIXME: important keywords should have specific boosts.
200 break;
201 case Type:
202 case Function:
203 case Variable:
204 Score *= 1.1f;
205 break;
206 case Namespace:
207 Score *= 0.8f;
208 break;
209 case Macro:
210 Score *= 0.2f;
211 break;
212 case Unknown:
213 break;
Sam McCall4a3c69b2018-06-06 08:53:36 +0000214 }
215
Sam McCallc5707b62018-05-15 17:43:27 +0000216 return Score;
217}
218
219raw_ostream &operator<<(raw_ostream &OS, const SymbolQualitySignals &S) {
220 OS << formatv("=== Symbol quality: {0}\n", S.evaluate());
Sam McCallc5707b62018-05-15 17:43:27 +0000221 OS << formatv("\tReferences: {0}\n", S.References);
222 OS << formatv("\tDeprecated: {0}\n", S.Deprecated);
Sam McCalle018b362018-06-08 09:36:34 +0000223 OS << formatv("\tReserved name: {0}\n", S.ReservedName);
Sam McCall4a3c69b2018-06-06 08:53:36 +0000224 OS << formatv("\tCategory: {0}\n", static_cast<int>(S.Category));
Sam McCallc5707b62018-05-15 17:43:27 +0000225 return OS;
226}
227
Sam McCalld9b54f02018-06-05 16:30:25 +0000228static SymbolRelevanceSignals::AccessibleScope
Sam McCallabe37372018-06-27 11:43:54 +0000229ComputeScope(const NamedDecl *D) {
230 // Injected "Foo" within the class "Foo" has file scope, not class scope.
231 const DeclContext *DC = D->getDeclContext();
232 if (auto *R = dyn_cast_or_null<RecordDecl>(D))
233 if (R->isInjectedClassName())
234 DC = DC->getParent();
Eric Liu8944f0e2018-07-05 08:14:04 +0000235 // Class constructor should have the same scope as the class.
Simon Pilgrim4a032012018-07-05 09:35:12 +0000236 if (isa<CXXConstructorDecl>(D))
Eric Liu8944f0e2018-07-05 08:14:04 +0000237 DC = DC->getParent();
Sam McCall89f52932018-06-05 18:00:48 +0000238 bool InClass = false;
Sam McCallabe37372018-06-27 11:43:54 +0000239 for (; !DC->isFileContext(); DC = DC->getParent()) {
Sam McCalld9b54f02018-06-05 16:30:25 +0000240 if (DC->isFunctionOrMethod())
241 return SymbolRelevanceSignals::FunctionScope;
242 InClass = InClass || DC->isRecord();
243 }
244 if (InClass)
245 return SymbolRelevanceSignals::ClassScope;
246 // This threshold could be tweaked, e.g. to treat module-visible as global.
Sam McCallabe37372018-06-27 11:43:54 +0000247 if (D->getLinkageInternal() < ExternalLinkage)
Sam McCalld9b54f02018-06-05 16:30:25 +0000248 return SymbolRelevanceSignals::FileScope;
249 return SymbolRelevanceSignals::GlobalScope;
250}
251
252void SymbolRelevanceSignals::merge(const Symbol &IndexResult) {
253 // FIXME: Index results always assumed to be at global scope. If Scope becomes
254 // relevant to non-completion requests, we should recognize class members etc.
Eric Liu09c3c372018-06-15 08:58:12 +0000255
256 SymbolURI = IndexResult.CanonicalDeclaration.FileURI;
Eric Liu5d2a8072018-07-23 10:56:37 +0000257 IsInstanceMember |= isInstanceMember(IndexResult.SymInfo);
Sam McCalld9b54f02018-06-05 16:30:25 +0000258}
259
Sam McCallc5707b62018-05-15 17:43:27 +0000260void SymbolRelevanceSignals::merge(const CodeCompletionResult &SemaCCResult) {
261 if (SemaCCResult.Availability == CXAvailability_NotAvailable ||
262 SemaCCResult.Availability == CXAvailability_NotAccessible)
263 Forbidden = true;
Ilya Biryukovf0296462018-06-04 14:50:59 +0000264
265 if (SemaCCResult.Declaration) {
Eric Liu09c3c372018-06-15 08:58:12 +0000266 // We boost things that have decls in the main file. We give a fixed score
267 // for all other declarations in sema as they are already included in the
268 // translation unit.
Kirill Bobyrev47d7f522018-07-11 14:49:49 +0000269 float DeclProximity = (hasDeclInMainFile(*SemaCCResult.Declaration) ||
270 hasUsingDeclInMainFile(SemaCCResult))
271 ? 1.0
272 : 0.6;
Eric Liu09c3c372018-06-15 08:58:12 +0000273 SemaProximityScore = std::max(DeclProximity, SemaProximityScore);
Eric Liu5d2a8072018-07-23 10:56:37 +0000274 IsInstanceMember |= isInstanceMember(SemaCCResult.Declaration);
Ilya Biryukovf0296462018-06-04 14:50:59 +0000275 }
Sam McCalld9b54f02018-06-05 16:30:25 +0000276
277 // Declarations are scoped, others (like macros) are assumed global.
Sam McCall661d89c2018-06-05 17:58:12 +0000278 if (SemaCCResult.Declaration)
Sam McCallabe37372018-06-27 11:43:54 +0000279 Scope = std::min(Scope, ComputeScope(SemaCCResult.Declaration));
Sam McCallc5707b62018-05-15 17:43:27 +0000280}
281
Sam McCall3f0243f2018-07-03 08:09:29 +0000282static std::pair<float, unsigned> proximityScore(llvm::StringRef SymbolURI,
283 URIDistance *D) {
284 if (!D || SymbolURI.empty())
285 return {0.f, 0u};
286 unsigned Distance = D->distance(SymbolURI);
287 // Assume approximately default options are used for sensible scoring.
288 return {std::exp(Distance * -0.4f / FileDistanceOptions().UpCost), Distance};
289}
290
Sam McCallc5707b62018-05-15 17:43:27 +0000291float SymbolRelevanceSignals::evaluate() const {
Sam McCalld9b54f02018-06-05 16:30:25 +0000292 float Score = 1;
293
Sam McCallc5707b62018-05-15 17:43:27 +0000294 if (Forbidden)
295 return 0;
Ilya Biryukovf0296462018-06-04 14:50:59 +0000296
Sam McCalld9b54f02018-06-05 16:30:25 +0000297 Score *= NameMatch;
298
Ilya Biryukovf0296462018-06-04 14:50:59 +0000299 // Proximity scores are [0,1] and we translate them into a multiplier in the
Sam McCall3f0243f2018-07-03 08:09:29 +0000300 // range from 1 to 3.
301 Score *= 1 + 2 * std::max(proximityScore(SymbolURI, FileProximityMatch).first,
302 SemaProximityScore);
Sam McCalld9b54f02018-06-05 16:30:25 +0000303
304 // Symbols like local variables may only be referenced within their scope.
305 // Conversely if we're in that scope, it's likely we'll reference them.
306 if (Query == CodeComplete) {
307 // The narrower the scope where a symbol is visible, the more likely it is
308 // to be relevant when it is available.
309 switch (Scope) {
310 case GlobalScope:
311 break;
312 case FileScope:
313 Score *= 1.5;
Sam McCallc22c9aa2018-06-07 08:16:36 +0000314 break;
Sam McCalld9b54f02018-06-05 16:30:25 +0000315 case ClassScope:
316 Score *= 2;
Sam McCallc22c9aa2018-06-07 08:16:36 +0000317 break;
Sam McCalld9b54f02018-06-05 16:30:25 +0000318 case FunctionScope:
319 Score *= 4;
Sam McCallc22c9aa2018-06-07 08:16:36 +0000320 break;
Sam McCalld9b54f02018-06-05 16:30:25 +0000321 }
322 }
323
Eric Liu5d2a8072018-07-23 10:56:37 +0000324 // Penalize non-instance members when they are accessed via a class instance.
325 if (!IsInstanceMember &&
326 (Context == CodeCompletionContext::CCC_DotMemberAccess ||
327 Context == CodeCompletionContext::CCC_ArrowMemberAccess)) {
328 Score *= 0.5;
329 }
330
Ilya Biryukovf0296462018-06-04 14:50:59 +0000331 return Score;
Sam McCallc5707b62018-05-15 17:43:27 +0000332}
Eric Liu09c3c372018-06-15 08:58:12 +0000333
Sam McCallc5707b62018-05-15 17:43:27 +0000334raw_ostream &operator<<(raw_ostream &OS, const SymbolRelevanceSignals &S) {
335 OS << formatv("=== Symbol relevance: {0}\n", S.evaluate());
336 OS << formatv("\tName match: {0}\n", S.NameMatch);
337 OS << formatv("\tForbidden: {0}\n", S.Forbidden);
Eric Liu5d2a8072018-07-23 10:56:37 +0000338 OS << formatv("\tIsInstanceMember: {0}\n", S.IsInstanceMember);
339 OS << formatv("\tContext: {0}\n", getCompletionKindString(S.Context));
Eric Liu09c3c372018-06-15 08:58:12 +0000340 OS << formatv("\tSymbol URI: {0}\n", S.SymbolURI);
341 if (S.FileProximityMatch) {
Sam McCall3f0243f2018-07-03 08:09:29 +0000342 auto Score = proximityScore(S.SymbolURI, S.FileProximityMatch);
343 OS << formatv("\tIndex proximity: {0} (distance={1})\n", Score.first,
344 Score.second);
Eric Liu09c3c372018-06-15 08:58:12 +0000345 }
346 OS << formatv("\tSema proximity: {0}\n", S.SemaProximityScore);
Sam McCall661d89c2018-06-05 17:58:12 +0000347 OS << formatv("\tQuery type: {0}\n", static_cast<int>(S.Query));
348 OS << formatv("\tScope: {0}\n", static_cast<int>(S.Scope));
Sam McCallc5707b62018-05-15 17:43:27 +0000349 return OS;
350}
351
352float evaluateSymbolAndRelevance(float SymbolQuality, float SymbolRelevance) {
353 return SymbolQuality * SymbolRelevance;
354}
355
356// Produces an integer that sorts in the same order as F.
357// That is: a < b <==> encodeFloat(a) < encodeFloat(b).
358static uint32_t encodeFloat(float F) {
359 static_assert(std::numeric_limits<float>::is_iec559, "");
360 constexpr uint32_t TopBit = ~(~uint32_t{0} >> 1);
361
362 // Get the bits of the float. Endianness is the same as for integers.
363 uint32_t U = FloatToBits(F);
364 // IEEE 754 floats compare like sign-magnitude integers.
365 if (U & TopBit) // Negative float.
366 return 0 - U; // Map onto the low half of integers, order reversed.
367 return U + TopBit; // Positive floats map onto the high half of integers.
368}
369
370std::string sortText(float Score, llvm::StringRef Name) {
371 // We convert -Score to an integer, and hex-encode for readability.
372 // Example: [0.5, "foo"] -> "41000000foo"
373 std::string S;
374 llvm::raw_string_ostream OS(S);
375 write_hex(OS, encodeFloat(-Score), llvm::HexPrintStyle::Lower,
376 /*Width=*/2 * sizeof(Score));
377 OS << Name;
378 OS.flush();
379 return S;
380}
381
382} // namespace clangd
383} // namespace clang