blob: 1021456660b856266179ba2f40fd86e5ec82f3b6 [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:
134 case Macro:
135 Score *= 0.2;
136 break;
137 case Unknown:
138 break;
139 }
140
Sam McCallc5707b62018-05-15 17:43:27 +0000141 return Score;
142}
143
144raw_ostream &operator<<(raw_ostream &OS, const SymbolQualitySignals &S) {
145 OS << formatv("=== Symbol quality: {0}\n", S.evaluate());
146 if (S.SemaCCPriority)
147 OS << formatv("\tSemaCCPriority: {0}\n", S.SemaCCPriority);
148 OS << formatv("\tReferences: {0}\n", S.References);
149 OS << formatv("\tDeprecated: {0}\n", S.Deprecated);
Sam McCall4a3c69b2018-06-06 08:53:36 +0000150 OS << formatv("\tCategory: {0}\n", static_cast<int>(S.Category));
Sam McCallc5707b62018-05-15 17:43:27 +0000151 return OS;
152}
153
Sam McCalld9b54f02018-06-05 16:30:25 +0000154static SymbolRelevanceSignals::AccessibleScope
155ComputeScope(const NamedDecl &D) {
Sam McCall89f52932018-06-05 18:00:48 +0000156 bool InClass = false;
Sam McCalld9b54f02018-06-05 16:30:25 +0000157 for (const DeclContext *DC = D.getDeclContext(); !DC->isFileContext();
158 DC = DC->getParent()) {
159 if (DC->isFunctionOrMethod())
160 return SymbolRelevanceSignals::FunctionScope;
161 InClass = InClass || DC->isRecord();
162 }
163 if (InClass)
164 return SymbolRelevanceSignals::ClassScope;
165 // This threshold could be tweaked, e.g. to treat module-visible as global.
166 if (D.getLinkageInternal() < ExternalLinkage)
167 return SymbolRelevanceSignals::FileScope;
168 return SymbolRelevanceSignals::GlobalScope;
169}
170
171void SymbolRelevanceSignals::merge(const Symbol &IndexResult) {
172 // FIXME: Index results always assumed to be at global scope. If Scope becomes
173 // relevant to non-completion requests, we should recognize class members etc.
174}
175
Sam McCallc5707b62018-05-15 17:43:27 +0000176void SymbolRelevanceSignals::merge(const CodeCompletionResult &SemaCCResult) {
177 if (SemaCCResult.Availability == CXAvailability_NotAvailable ||
178 SemaCCResult.Availability == CXAvailability_NotAccessible)
179 Forbidden = true;
Ilya Biryukovf0296462018-06-04 14:50:59 +0000180
181 if (SemaCCResult.Declaration) {
182 // We boost things that have decls in the main file.
183 // The real proximity scores would be more general when we have them.
184 float DeclProximity =
185 hasDeclInMainFile(*SemaCCResult.Declaration) ? 1.0 : 0.0;
186 ProximityScore = std::max(DeclProximity, ProximityScore);
187 }
Sam McCalld9b54f02018-06-05 16:30:25 +0000188
189 // Declarations are scoped, others (like macros) are assumed global.
Sam McCall661d89c2018-06-05 17:58:12 +0000190 if (SemaCCResult.Declaration)
Sam McCalld9b54f02018-06-05 16:30:25 +0000191 Scope = std::min(Scope, ComputeScope(*SemaCCResult.Declaration));
Sam McCallc5707b62018-05-15 17:43:27 +0000192}
193
194float SymbolRelevanceSignals::evaluate() const {
Sam McCalld9b54f02018-06-05 16:30:25 +0000195 float Score = 1;
196
Sam McCallc5707b62018-05-15 17:43:27 +0000197 if (Forbidden)
198 return 0;
Ilya Biryukovf0296462018-06-04 14:50:59 +0000199
Sam McCalld9b54f02018-06-05 16:30:25 +0000200 Score *= NameMatch;
201
Ilya Biryukovf0296462018-06-04 14:50:59 +0000202 // Proximity scores are [0,1] and we translate them into a multiplier in the
203 // range from 1 to 2.
204 Score *= 1 + ProximityScore;
Sam McCalld9b54f02018-06-05 16:30:25 +0000205
206 // Symbols like local variables may only be referenced within their scope.
207 // Conversely if we're in that scope, it's likely we'll reference them.
208 if (Query == CodeComplete) {
209 // The narrower the scope where a symbol is visible, the more likely it is
210 // to be relevant when it is available.
211 switch (Scope) {
212 case GlobalScope:
213 break;
214 case FileScope:
215 Score *= 1.5;
216 case ClassScope:
217 Score *= 2;
218 case FunctionScope:
219 Score *= 4;
220 }
221 }
222
Ilya Biryukovf0296462018-06-04 14:50:59 +0000223 return Score;
Sam McCallc5707b62018-05-15 17:43:27 +0000224}
225raw_ostream &operator<<(raw_ostream &OS, const SymbolRelevanceSignals &S) {
226 OS << formatv("=== Symbol relevance: {0}\n", S.evaluate());
227 OS << formatv("\tName match: {0}\n", S.NameMatch);
228 OS << formatv("\tForbidden: {0}\n", S.Forbidden);
Sam McCall661d89c2018-06-05 17:58:12 +0000229 OS << formatv("\tProximity: {0}\n", S.ProximityScore);
230 OS << formatv("\tQuery type: {0}\n", static_cast<int>(S.Query));
231 OS << formatv("\tScope: {0}\n", static_cast<int>(S.Scope));
Sam McCallc5707b62018-05-15 17:43:27 +0000232 return OS;
233}
234
235float evaluateSymbolAndRelevance(float SymbolQuality, float SymbolRelevance) {
236 return SymbolQuality * SymbolRelevance;
237}
238
239// Produces an integer that sorts in the same order as F.
240// That is: a < b <==> encodeFloat(a) < encodeFloat(b).
241static uint32_t encodeFloat(float F) {
242 static_assert(std::numeric_limits<float>::is_iec559, "");
243 constexpr uint32_t TopBit = ~(~uint32_t{0} >> 1);
244
245 // Get the bits of the float. Endianness is the same as for integers.
246 uint32_t U = FloatToBits(F);
247 // IEEE 754 floats compare like sign-magnitude integers.
248 if (U & TopBit) // Negative float.
249 return 0 - U; // Map onto the low half of integers, order reversed.
250 return U + TopBit; // Positive floats map onto the high half of integers.
251}
252
253std::string sortText(float Score, llvm::StringRef Name) {
254 // We convert -Score to an integer, and hex-encode for readability.
255 // Example: [0.5, "foo"] -> "41000000foo"
256 std::string S;
257 llvm::raw_string_ostream OS(S);
258 write_hex(OS, encodeFloat(-Score), llvm::HexPrintStyle::Lower,
259 /*Width=*/2 * sizeof(Score));
260 OS << Name;
261 OS.flush();
262 return S;
263}
264
265} // namespace clangd
266} // namespace clang