blob: 6034faaf814e89c400d50c15967bc2780795bc2a [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"
10#include "index/Index.h"
Ilya Biryukovf0296462018-06-04 14:50:59 +000011#include "clang/AST/ASTContext.h"
Sam McCall4a3c69b2018-06-06 08:53:36 +000012#include "clang/AST/DeclVisitor.h"
Ilya Biryukovf0296462018-06-04 14:50:59 +000013#include "clang/Basic/SourceManager.h"
Sam McCallc5707b62018-05-15 17:43:27 +000014#include "clang/Sema/CodeCompleteConsumer.h"
15#include "llvm/Support/FormatVariadic.h"
16#include "llvm/Support/MathExtras.h"
17#include "llvm/Support/raw_ostream.h"
18
19namespace clang {
20namespace clangd {
21using namespace llvm;
22
Ilya Biryukovf0296462018-06-04 14:50:59 +000023static bool hasDeclInMainFile(const Decl &D) {
24 auto &SourceMgr = D.getASTContext().getSourceManager();
25 for (auto *Redecl : D.redecls()) {
26 auto Loc = SourceMgr.getSpellingLoc(Redecl->getLocation());
27 if (SourceMgr.isWrittenInMainFile(Loc))
28 return true;
29 }
30 return false;
31}
32
Sam McCall4a3c69b2018-06-06 08:53:36 +000033static SymbolQualitySignals::SymbolCategory categorize(const NamedDecl &ND) {
34 class Switch
35 : public ConstDeclVisitor<Switch, SymbolQualitySignals::SymbolCategory> {
36 public:
37#define MAP(DeclType, Category) \
38 SymbolQualitySignals::SymbolCategory Visit##DeclType(const DeclType *) { \
39 return SymbolQualitySignals::Category; \
40 }
41 MAP(NamespaceDecl, Namespace);
42 MAP(NamespaceAliasDecl, Namespace);
43 MAP(TypeDecl, Type);
44 MAP(TypeAliasTemplateDecl, Type);
45 MAP(ClassTemplateDecl, Type);
46 MAP(ValueDecl, Variable);
47 MAP(VarTemplateDecl, Variable);
48 MAP(FunctionDecl, Function);
49 MAP(FunctionTemplateDecl, Function);
50 MAP(Decl, Unknown);
51#undef MAP
52 };
53 return Switch().Visit(&ND);
54}
55
56static SymbolQualitySignals::SymbolCategory
57categorize(const index::SymbolInfo &D) {
58 switch (D.Kind) {
59 case index::SymbolKind::Namespace:
60 case index::SymbolKind::NamespaceAlias:
61 return SymbolQualitySignals::Namespace;
62 case index::SymbolKind::Macro:
63 return SymbolQualitySignals::Macro;
64 case index::SymbolKind::Enum:
65 case index::SymbolKind::Struct:
66 case index::SymbolKind::Class:
67 case index::SymbolKind::Protocol:
68 case index::SymbolKind::Extension:
69 case index::SymbolKind::Union:
70 case index::SymbolKind::TypeAlias:
71 return SymbolQualitySignals::Type;
72 case index::SymbolKind::Function:
73 case index::SymbolKind::ClassMethod:
74 case index::SymbolKind::InstanceMethod:
75 case index::SymbolKind::StaticMethod:
76 case index::SymbolKind::InstanceProperty:
77 case index::SymbolKind::ClassProperty:
78 case index::SymbolKind::StaticProperty:
79 case index::SymbolKind::Constructor:
80 case index::SymbolKind::Destructor:
81 case index::SymbolKind::ConversionFunction:
82 return SymbolQualitySignals::Function;
83 case index::SymbolKind::Variable:
84 case index::SymbolKind::Field:
85 case index::SymbolKind::EnumConstant:
86 case index::SymbolKind::Parameter:
87 return SymbolQualitySignals::Variable;
88 case index::SymbolKind::Using:
89 case index::SymbolKind::Module:
90 case index::SymbolKind::Unknown:
91 return SymbolQualitySignals::Unknown;
92 }
93}
94
Sam McCallc5707b62018-05-15 17:43:27 +000095void SymbolQualitySignals::merge(const CodeCompletionResult &SemaCCResult) {
96 SemaCCPriority = SemaCCResult.Priority;
Sam McCallc5707b62018-05-15 17:43:27 +000097 if (SemaCCResult.Availability == CXAvailability_Deprecated)
98 Deprecated = true;
Sam McCall4a3c69b2018-06-06 08:53:36 +000099
100 if (SemaCCResult.Declaration)
101 Category = categorize(*SemaCCResult.Declaration);
102 else if (SemaCCResult.Kind == CodeCompletionResult::RK_Macro)
103 Category = Macro;
Sam McCallc5707b62018-05-15 17:43:27 +0000104}
105
106void SymbolQualitySignals::merge(const Symbol &IndexResult) {
107 References = std::max(IndexResult.References, References);
Sam McCall4a3c69b2018-06-06 08:53:36 +0000108 Category = categorize(IndexResult.SymInfo);
Sam McCallc5707b62018-05-15 17:43:27 +0000109}
110
111float SymbolQualitySignals::evaluate() const {
112 float Score = 1;
113
114 // This avoids a sharp gradient for tail symbols, and also neatly avoids the
115 // question of whether 0 references means a bad symbol or missing data.
116 if (References >= 3)
117 Score *= std::log(References);
118
119 if (SemaCCPriority)
120 // Map onto a 0-2 interval, so we don't reward/penalize non-Sema results.
121 // Priority 80 is a really bad score.
122 Score *= 2 - std::min<float>(80, SemaCCPriority) / 40;
123
124 if (Deprecated)
Aaron Ballman215e4712018-05-18 13:18:41 +0000125 Score *= 0.1f;
Sam McCallc5707b62018-05-15 17:43:27 +0000126
Sam McCall4a3c69b2018-06-06 08:53:36 +0000127 switch (Category) {
128 case Type:
129 case Function:
130 case Variable:
131 Score *= 1.1;
132 break;
133 case Namespace:
Sam McCallbc7cbb72018-06-06 12:38:37 +0000134 Score *= 0.8;
135 break;
Sam McCall4a3c69b2018-06-06 08:53:36 +0000136 case Macro:
137 Score *= 0.2;
138 break;
139 case Unknown:
140 break;
141 }
142
Sam McCallc5707b62018-05-15 17:43:27 +0000143 return Score;
144}
145
146raw_ostream &operator<<(raw_ostream &OS, const SymbolQualitySignals &S) {
147 OS << formatv("=== Symbol quality: {0}\n", S.evaluate());
148 if (S.SemaCCPriority)
149 OS << formatv("\tSemaCCPriority: {0}\n", S.SemaCCPriority);
150 OS << formatv("\tReferences: {0}\n", S.References);
151 OS << formatv("\tDeprecated: {0}\n", S.Deprecated);
Sam McCall4a3c69b2018-06-06 08:53:36 +0000152 OS << formatv("\tCategory: {0}\n", static_cast<int>(S.Category));
Sam McCallc5707b62018-05-15 17:43:27 +0000153 return OS;
154}
155
Sam McCalld9b54f02018-06-05 16:30:25 +0000156static SymbolRelevanceSignals::AccessibleScope
157ComputeScope(const NamedDecl &D) {
Sam McCall89f52932018-06-05 18:00:48 +0000158 bool InClass = false;
Sam McCalld9b54f02018-06-05 16:30:25 +0000159 for (const DeclContext *DC = D.getDeclContext(); !DC->isFileContext();
160 DC = DC->getParent()) {
161 if (DC->isFunctionOrMethod())
162 return SymbolRelevanceSignals::FunctionScope;
163 InClass = InClass || DC->isRecord();
164 }
165 if (InClass)
166 return SymbolRelevanceSignals::ClassScope;
167 // This threshold could be tweaked, e.g. to treat module-visible as global.
168 if (D.getLinkageInternal() < ExternalLinkage)
169 return SymbolRelevanceSignals::FileScope;
170 return SymbolRelevanceSignals::GlobalScope;
171}
172
173void SymbolRelevanceSignals::merge(const Symbol &IndexResult) {
174 // FIXME: Index results always assumed to be at global scope. If Scope becomes
175 // relevant to non-completion requests, we should recognize class members etc.
176}
177
Sam McCallc5707b62018-05-15 17:43:27 +0000178void SymbolRelevanceSignals::merge(const CodeCompletionResult &SemaCCResult) {
179 if (SemaCCResult.Availability == CXAvailability_NotAvailable ||
180 SemaCCResult.Availability == CXAvailability_NotAccessible)
181 Forbidden = true;
Ilya Biryukovf0296462018-06-04 14:50:59 +0000182
183 if (SemaCCResult.Declaration) {
184 // We boost things that have decls in the main file.
185 // The real proximity scores would be more general when we have them.
186 float DeclProximity =
187 hasDeclInMainFile(*SemaCCResult.Declaration) ? 1.0 : 0.0;
188 ProximityScore = std::max(DeclProximity, ProximityScore);
189 }
Sam McCalld9b54f02018-06-05 16:30:25 +0000190
191 // Declarations are scoped, others (like macros) are assumed global.
Sam McCall661d89c2018-06-05 17:58:12 +0000192 if (SemaCCResult.Declaration)
Sam McCalld9b54f02018-06-05 16:30:25 +0000193 Scope = std::min(Scope, ComputeScope(*SemaCCResult.Declaration));
Sam McCallc5707b62018-05-15 17:43:27 +0000194}
195
196float SymbolRelevanceSignals::evaluate() const {
Sam McCalld9b54f02018-06-05 16:30:25 +0000197 float Score = 1;
198
Sam McCallc5707b62018-05-15 17:43:27 +0000199 if (Forbidden)
200 return 0;
Ilya Biryukovf0296462018-06-04 14:50:59 +0000201
Sam McCalld9b54f02018-06-05 16:30:25 +0000202 Score *= NameMatch;
203
Ilya Biryukovf0296462018-06-04 14:50:59 +0000204 // Proximity scores are [0,1] and we translate them into a multiplier in the
205 // range from 1 to 2.
206 Score *= 1 + ProximityScore;
Sam McCalld9b54f02018-06-05 16:30:25 +0000207
208 // Symbols like local variables may only be referenced within their scope.
209 // Conversely if we're in that scope, it's likely we'll reference them.
210 if (Query == CodeComplete) {
211 // The narrower the scope where a symbol is visible, the more likely it is
212 // to be relevant when it is available.
213 switch (Scope) {
214 case GlobalScope:
215 break;
216 case FileScope:
217 Score *= 1.5;
218 case ClassScope:
219 Score *= 2;
220 case FunctionScope:
221 Score *= 4;
222 }
223 }
224
Ilya Biryukovf0296462018-06-04 14:50:59 +0000225 return Score;
Sam McCallc5707b62018-05-15 17:43:27 +0000226}
227raw_ostream &operator<<(raw_ostream &OS, const SymbolRelevanceSignals &S) {
228 OS << formatv("=== Symbol relevance: {0}\n", S.evaluate());
229 OS << formatv("\tName match: {0}\n", S.NameMatch);
230 OS << formatv("\tForbidden: {0}\n", S.Forbidden);
Sam McCall661d89c2018-06-05 17:58:12 +0000231 OS << formatv("\tProximity: {0}\n", S.ProximityScore);
232 OS << formatv("\tQuery type: {0}\n", static_cast<int>(S.Query));
233 OS << formatv("\tScope: {0}\n", static_cast<int>(S.Scope));
Sam McCallc5707b62018-05-15 17:43:27 +0000234 return OS;
235}
236
237float evaluateSymbolAndRelevance(float SymbolQuality, float SymbolRelevance) {
238 return SymbolQuality * SymbolRelevance;
239}
240
241// Produces an integer that sorts in the same order as F.
242// That is: a < b <==> encodeFloat(a) < encodeFloat(b).
243static uint32_t encodeFloat(float F) {
244 static_assert(std::numeric_limits<float>::is_iec559, "");
245 constexpr uint32_t TopBit = ~(~uint32_t{0} >> 1);
246
247 // Get the bits of the float. Endianness is the same as for integers.
248 uint32_t U = FloatToBits(F);
249 // IEEE 754 floats compare like sign-magnitude integers.
250 if (U & TopBit) // Negative float.
251 return 0 - U; // Map onto the low half of integers, order reversed.
252 return U + TopBit; // Positive floats map onto the high half of integers.
253}
254
255std::string sortText(float Score, llvm::StringRef Name) {
256 // We convert -Score to an integer, and hex-encode for readability.
257 // Example: [0.5, "foo"] -> "41000000foo"
258 std::string S;
259 llvm::raw_string_ostream OS(S);
260 write_hex(OS, encodeFloat(-Score), llvm::HexPrintStyle::Lower,
261 /*Width=*/2 * sizeof(Score));
262 OS << Name;
263 OS.flush();
264 return S;
265}
266
267} // namespace clangd
268} // namespace clang