blob: 00490274ad35a4a1fe09a3ca02517d1c6b1b9592 [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 }
Tim Northover0698e962018-06-06 13:28:49 +000093 llvm_unreachable("Unknown index::SymbolKind");
Sam McCall4a3c69b2018-06-06 08:53:36 +000094}
95
Sam McCallc5707b62018-05-15 17:43:27 +000096void SymbolQualitySignals::merge(const CodeCompletionResult &SemaCCResult) {
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
Sam McCallc5707b62018-05-15 17:43:27 +0000119 if (Deprecated)
Aaron Ballman215e4712018-05-18 13:18:41 +0000120 Score *= 0.1f;
Sam McCallc5707b62018-05-15 17:43:27 +0000121
Sam McCall4a3c69b2018-06-06 08:53:36 +0000122 switch (Category) {
123 case Type:
124 case Function:
125 case Variable:
Simon Pilgrim0c9e1c82018-06-06 12:48:27 +0000126 Score *= 1.1f;
Sam McCall4a3c69b2018-06-06 08:53:36 +0000127 break;
128 case Namespace:
Simon Pilgrim0c9e1c82018-06-06 12:48:27 +0000129 Score *= 0.8f;
Sam McCallbc7cbb72018-06-06 12:38:37 +0000130 break;
Sam McCall4a3c69b2018-06-06 08:53:36 +0000131 case Macro:
Simon Pilgrim0c9e1c82018-06-06 12:48:27 +0000132 Score *= 0.2f;
Sam McCall4a3c69b2018-06-06 08:53:36 +0000133 break;
134 case Unknown:
135 break;
136 }
137
Sam McCallc5707b62018-05-15 17:43:27 +0000138 return Score;
139}
140
141raw_ostream &operator<<(raw_ostream &OS, const SymbolQualitySignals &S) {
142 OS << formatv("=== Symbol quality: {0}\n", S.evaluate());
Sam McCallc5707b62018-05-15 17:43:27 +0000143 OS << formatv("\tReferences: {0}\n", S.References);
144 OS << formatv("\tDeprecated: {0}\n", S.Deprecated);
Sam McCall4a3c69b2018-06-06 08:53:36 +0000145 OS << formatv("\tCategory: {0}\n", static_cast<int>(S.Category));
Sam McCallc5707b62018-05-15 17:43:27 +0000146 return OS;
147}
148
Sam McCalld9b54f02018-06-05 16:30:25 +0000149static SymbolRelevanceSignals::AccessibleScope
150ComputeScope(const NamedDecl &D) {
Sam McCall89f52932018-06-05 18:00:48 +0000151 bool InClass = false;
Sam McCalld9b54f02018-06-05 16:30:25 +0000152 for (const DeclContext *DC = D.getDeclContext(); !DC->isFileContext();
153 DC = DC->getParent()) {
154 if (DC->isFunctionOrMethod())
155 return SymbolRelevanceSignals::FunctionScope;
156 InClass = InClass || DC->isRecord();
157 }
158 if (InClass)
159 return SymbolRelevanceSignals::ClassScope;
160 // This threshold could be tweaked, e.g. to treat module-visible as global.
161 if (D.getLinkageInternal() < ExternalLinkage)
162 return SymbolRelevanceSignals::FileScope;
163 return SymbolRelevanceSignals::GlobalScope;
164}
165
166void SymbolRelevanceSignals::merge(const Symbol &IndexResult) {
167 // FIXME: Index results always assumed to be at global scope. If Scope becomes
168 // relevant to non-completion requests, we should recognize class members etc.
169}
170
Sam McCallc5707b62018-05-15 17:43:27 +0000171void SymbolRelevanceSignals::merge(const CodeCompletionResult &SemaCCResult) {
172 if (SemaCCResult.Availability == CXAvailability_NotAvailable ||
173 SemaCCResult.Availability == CXAvailability_NotAccessible)
174 Forbidden = true;
Ilya Biryukovf0296462018-06-04 14:50:59 +0000175
176 if (SemaCCResult.Declaration) {
177 // We boost things that have decls in the main file.
178 // The real proximity scores would be more general when we have them.
179 float DeclProximity =
180 hasDeclInMainFile(*SemaCCResult.Declaration) ? 1.0 : 0.0;
181 ProximityScore = std::max(DeclProximity, ProximityScore);
182 }
Sam McCalld9b54f02018-06-05 16:30:25 +0000183
184 // Declarations are scoped, others (like macros) are assumed global.
Sam McCall661d89c2018-06-05 17:58:12 +0000185 if (SemaCCResult.Declaration)
Sam McCalld9b54f02018-06-05 16:30:25 +0000186 Scope = std::min(Scope, ComputeScope(*SemaCCResult.Declaration));
Sam McCallc5707b62018-05-15 17:43:27 +0000187}
188
189float SymbolRelevanceSignals::evaluate() const {
Sam McCalld9b54f02018-06-05 16:30:25 +0000190 float Score = 1;
191
Sam McCallc5707b62018-05-15 17:43:27 +0000192 if (Forbidden)
193 return 0;
Ilya Biryukovf0296462018-06-04 14:50:59 +0000194
Sam McCalld9b54f02018-06-05 16:30:25 +0000195 Score *= NameMatch;
196
Ilya Biryukovf0296462018-06-04 14:50:59 +0000197 // Proximity scores are [0,1] and we translate them into a multiplier in the
198 // range from 1 to 2.
199 Score *= 1 + ProximityScore;
Sam McCalld9b54f02018-06-05 16:30:25 +0000200
201 // Symbols like local variables may only be referenced within their scope.
202 // Conversely if we're in that scope, it's likely we'll reference them.
203 if (Query == CodeComplete) {
204 // The narrower the scope where a symbol is visible, the more likely it is
205 // to be relevant when it is available.
206 switch (Scope) {
207 case GlobalScope:
208 break;
209 case FileScope:
210 Score *= 1.5;
Sam McCallc22c9aa2018-06-07 08:16:36 +0000211 break;
Sam McCalld9b54f02018-06-05 16:30:25 +0000212 case ClassScope:
213 Score *= 2;
Sam McCallc22c9aa2018-06-07 08:16:36 +0000214 break;
Sam McCalld9b54f02018-06-05 16:30:25 +0000215 case FunctionScope:
216 Score *= 4;
Sam McCallc22c9aa2018-06-07 08:16:36 +0000217 break;
Sam McCalld9b54f02018-06-05 16:30:25 +0000218 }
219 }
220
Ilya Biryukovf0296462018-06-04 14:50:59 +0000221 return Score;
Sam McCallc5707b62018-05-15 17:43:27 +0000222}
223raw_ostream &operator<<(raw_ostream &OS, const SymbolRelevanceSignals &S) {
224 OS << formatv("=== Symbol relevance: {0}\n", S.evaluate());
225 OS << formatv("\tName match: {0}\n", S.NameMatch);
226 OS << formatv("\tForbidden: {0}\n", S.Forbidden);
Sam McCall661d89c2018-06-05 17:58:12 +0000227 OS << formatv("\tProximity: {0}\n", S.ProximityScore);
228 OS << formatv("\tQuery type: {0}\n", static_cast<int>(S.Query));
229 OS << formatv("\tScope: {0}\n", static_cast<int>(S.Scope));
Sam McCallc5707b62018-05-15 17:43:27 +0000230 return OS;
231}
232
233float evaluateSymbolAndRelevance(float SymbolQuality, float SymbolRelevance) {
234 return SymbolQuality * SymbolRelevance;
235}
236
237// Produces an integer that sorts in the same order as F.
238// That is: a < b <==> encodeFloat(a) < encodeFloat(b).
239static uint32_t encodeFloat(float F) {
240 static_assert(std::numeric_limits<float>::is_iec559, "");
241 constexpr uint32_t TopBit = ~(~uint32_t{0} >> 1);
242
243 // Get the bits of the float. Endianness is the same as for integers.
244 uint32_t U = FloatToBits(F);
245 // IEEE 754 floats compare like sign-magnitude integers.
246 if (U & TopBit) // Negative float.
247 return 0 - U; // Map onto the low half of integers, order reversed.
248 return U + TopBit; // Positive floats map onto the high half of integers.
249}
250
251std::string sortText(float Score, llvm::StringRef Name) {
252 // We convert -Score to an integer, and hex-encode for readability.
253 // Example: [0.5, "foo"] -> "41000000foo"
254 std::string S;
255 llvm::raw_string_ostream OS(S);
256 write_hex(OS, encodeFloat(-Score), llvm::HexPrintStyle::Lower,
257 /*Width=*/2 * sizeof(Score));
258 OS << Name;
259 OS.flush();
260 return S;
261}
262
263} // namespace clangd
264} // namespace clang