blob: ed3b9b5c762aa333e07142653361aa1a81c4ab73 [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 }
Simon Pilgrim0c9e1c82018-06-06 12:48:27 +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) {
97 SemaCCPriority = SemaCCResult.Priority;
Sam McCallc5707b62018-05-15 17:43:27 +000098 if (SemaCCResult.Availability == CXAvailability_Deprecated)
99 Deprecated = true;
Sam McCall4a3c69b2018-06-06 08:53:36 +0000100
101 if (SemaCCResult.Declaration)
102 Category = categorize(*SemaCCResult.Declaration);
103 else if (SemaCCResult.Kind == CodeCompletionResult::RK_Macro)
104 Category = Macro;
Sam McCallc5707b62018-05-15 17:43:27 +0000105}
106
107void SymbolQualitySignals::merge(const Symbol &IndexResult) {
108 References = std::max(IndexResult.References, References);
Sam McCall4a3c69b2018-06-06 08:53:36 +0000109 Category = categorize(IndexResult.SymInfo);
Sam McCallc5707b62018-05-15 17:43:27 +0000110}
111
112float SymbolQualitySignals::evaluate() const {
113 float Score = 1;
114
115 // This avoids a sharp gradient for tail symbols, and also neatly avoids the
116 // question of whether 0 references means a bad symbol or missing data.
117 if (References >= 3)
118 Score *= std::log(References);
119
120 if (SemaCCPriority)
121 // Map onto a 0-2 interval, so we don't reward/penalize non-Sema results.
122 // Priority 80 is a really bad score.
123 Score *= 2 - std::min<float>(80, SemaCCPriority) / 40;
124
125 if (Deprecated)
Aaron Ballman215e4712018-05-18 13:18:41 +0000126 Score *= 0.1f;
Sam McCallc5707b62018-05-15 17:43:27 +0000127
Sam McCall4a3c69b2018-06-06 08:53:36 +0000128 switch (Category) {
129 case Type:
130 case Function:
131 case Variable:
Simon Pilgrim0c9e1c82018-06-06 12:48:27 +0000132 Score *= 1.1f;
Sam McCall4a3c69b2018-06-06 08:53:36 +0000133 break;
134 case Namespace:
Simon Pilgrim0c9e1c82018-06-06 12:48:27 +0000135 Score *= 0.8f;
Sam McCallbc7cbb72018-06-06 12:38:37 +0000136 break;
Sam McCall4a3c69b2018-06-06 08:53:36 +0000137 case Macro:
Simon Pilgrim0c9e1c82018-06-06 12:48:27 +0000138 Score *= 0.2f;
Sam McCall4a3c69b2018-06-06 08:53:36 +0000139 break;
140 case Unknown:
141 break;
142 }
143
Sam McCallc5707b62018-05-15 17:43:27 +0000144 return Score;
145}
146
147raw_ostream &operator<<(raw_ostream &OS, const SymbolQualitySignals &S) {
148 OS << formatv("=== Symbol quality: {0}\n", S.evaluate());
149 if (S.SemaCCPriority)
150 OS << formatv("\tSemaCCPriority: {0}\n", S.SemaCCPriority);
151 OS << formatv("\tReferences: {0}\n", S.References);
152 OS << formatv("\tDeprecated: {0}\n", S.Deprecated);
Sam McCall4a3c69b2018-06-06 08:53:36 +0000153 OS << formatv("\tCategory: {0}\n", static_cast<int>(S.Category));
Sam McCallc5707b62018-05-15 17:43:27 +0000154 return OS;
155}
156
Sam McCalld9b54f02018-06-05 16:30:25 +0000157static SymbolRelevanceSignals::AccessibleScope
158ComputeScope(const NamedDecl &D) {
Sam McCall89f52932018-06-05 18:00:48 +0000159 bool InClass = false;
Sam McCalld9b54f02018-06-05 16:30:25 +0000160 for (const DeclContext *DC = D.getDeclContext(); !DC->isFileContext();
161 DC = DC->getParent()) {
162 if (DC->isFunctionOrMethod())
163 return SymbolRelevanceSignals::FunctionScope;
164 InClass = InClass || DC->isRecord();
165 }
166 if (InClass)
167 return SymbolRelevanceSignals::ClassScope;
168 // This threshold could be tweaked, e.g. to treat module-visible as global.
169 if (D.getLinkageInternal() < ExternalLinkage)
170 return SymbolRelevanceSignals::FileScope;
171 return SymbolRelevanceSignals::GlobalScope;
172}
173
174void SymbolRelevanceSignals::merge(const Symbol &IndexResult) {
175 // FIXME: Index results always assumed to be at global scope. If Scope becomes
176 // relevant to non-completion requests, we should recognize class members etc.
177}
178
Sam McCallc5707b62018-05-15 17:43:27 +0000179void SymbolRelevanceSignals::merge(const CodeCompletionResult &SemaCCResult) {
180 if (SemaCCResult.Availability == CXAvailability_NotAvailable ||
181 SemaCCResult.Availability == CXAvailability_NotAccessible)
182 Forbidden = true;
Ilya Biryukovf0296462018-06-04 14:50:59 +0000183
184 if (SemaCCResult.Declaration) {
185 // We boost things that have decls in the main file.
186 // The real proximity scores would be more general when we have them.
187 float DeclProximity =
188 hasDeclInMainFile(*SemaCCResult.Declaration) ? 1.0 : 0.0;
189 ProximityScore = std::max(DeclProximity, ProximityScore);
190 }
Sam McCalld9b54f02018-06-05 16:30:25 +0000191
192 // Declarations are scoped, others (like macros) are assumed global.
Sam McCall661d89c2018-06-05 17:58:12 +0000193 if (SemaCCResult.Declaration)
Sam McCalld9b54f02018-06-05 16:30:25 +0000194 Scope = std::min(Scope, ComputeScope(*SemaCCResult.Declaration));
Sam McCallc5707b62018-05-15 17:43:27 +0000195}
196
197float SymbolRelevanceSignals::evaluate() const {
Sam McCalld9b54f02018-06-05 16:30:25 +0000198 float Score = 1;
199
Sam McCallc5707b62018-05-15 17:43:27 +0000200 if (Forbidden)
201 return 0;
Ilya Biryukovf0296462018-06-04 14:50:59 +0000202
Sam McCalld9b54f02018-06-05 16:30:25 +0000203 Score *= NameMatch;
204
Ilya Biryukovf0296462018-06-04 14:50:59 +0000205 // Proximity scores are [0,1] and we translate them into a multiplier in the
206 // range from 1 to 2.
207 Score *= 1 + ProximityScore;
Sam McCalld9b54f02018-06-05 16:30:25 +0000208
209 // Symbols like local variables may only be referenced within their scope.
210 // Conversely if we're in that scope, it's likely we'll reference them.
211 if (Query == CodeComplete) {
212 // The narrower the scope where a symbol is visible, the more likely it is
213 // to be relevant when it is available.
214 switch (Scope) {
215 case GlobalScope:
216 break;
217 case FileScope:
218 Score *= 1.5;
219 case ClassScope:
220 Score *= 2;
221 case FunctionScope:
222 Score *= 4;
223 }
224 }
225
Ilya Biryukovf0296462018-06-04 14:50:59 +0000226 return Score;
Sam McCallc5707b62018-05-15 17:43:27 +0000227}
228raw_ostream &operator<<(raw_ostream &OS, const SymbolRelevanceSignals &S) {
229 OS << formatv("=== Symbol relevance: {0}\n", S.evaluate());
230 OS << formatv("\tName match: {0}\n", S.NameMatch);
231 OS << formatv("\tForbidden: {0}\n", S.Forbidden);
Sam McCall661d89c2018-06-05 17:58:12 +0000232 OS << formatv("\tProximity: {0}\n", S.ProximityScore);
233 OS << formatv("\tQuery type: {0}\n", static_cast<int>(S.Query));
234 OS << formatv("\tScope: {0}\n", static_cast<int>(S.Scope));
Sam McCallc5707b62018-05-15 17:43:27 +0000235 return OS;
236}
237
238float evaluateSymbolAndRelevance(float SymbolQuality, float SymbolRelevance) {
239 return SymbolQuality * SymbolRelevance;
240}
241
242// Produces an integer that sorts in the same order as F.
243// That is: a < b <==> encodeFloat(a) < encodeFloat(b).
244static uint32_t encodeFloat(float F) {
245 static_assert(std::numeric_limits<float>::is_iec559, "");
246 constexpr uint32_t TopBit = ~(~uint32_t{0} >> 1);
247
248 // Get the bits of the float. Endianness is the same as for integers.
249 uint32_t U = FloatToBits(F);
250 // IEEE 754 floats compare like sign-magnitude integers.
251 if (U & TopBit) // Negative float.
252 return 0 - U; // Map onto the low half of integers, order reversed.
253 return U + TopBit; // Positive floats map onto the high half of integers.
254}
255
256std::string sortText(float Score, llvm::StringRef Name) {
257 // We convert -Score to an integer, and hex-encode for readability.
258 // Example: [0.5, "foo"] -> "41000000foo"
259 std::string S;
260 llvm::raw_string_ostream OS(S);
261 write_hex(OS, encodeFloat(-Score), llvm::HexPrintStyle::Lower,
262 /*Width=*/2 * sizeof(Score));
263 OS << Name;
264 OS.flush();
265 return S;
266}
267
268} // namespace clangd
269} // namespace clang