forked from OSchip/llvm-project
[clangd] Support scope proximity in code completion.
Summary: This should make all-scope completion more usable. Scope proximity for indexes will be added in followup patch. Reviewers: sammccall Reviewed By: sammccall Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, cfe-commits Differential Revision: https://reviews.llvm.org/D53131 llvm-svn: 344688
This commit is contained in:
parent
e2566b5d87
commit
3fac4ef1fd
|
@ -54,6 +54,14 @@ std::string printQualifiedName(const NamedDecl &ND) {
|
||||||
return QName;
|
return QName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string printNamespaceScope(const DeclContext &DC) {
|
||||||
|
for (const auto *Ctx = &DC; Ctx != nullptr; Ctx = Ctx->getParent())
|
||||||
|
if (const auto *NS = dyn_cast<NamespaceDecl>(Ctx))
|
||||||
|
if (!NS->isAnonymousNamespace() && !NS->isInlineNamespace())
|
||||||
|
return printQualifiedName(*NS) + "::";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
llvm::Optional<SymbolID> getSymbolID(const Decl *D) {
|
llvm::Optional<SymbolID> getSymbolID(const Decl *D) {
|
||||||
llvm::SmallString<128> USR;
|
llvm::SmallString<128> USR;
|
||||||
if (index::generateUSRForDecl(D, USR))
|
if (index::generateUSRForDecl(D, USR))
|
||||||
|
|
|
@ -34,6 +34,9 @@ SourceLocation findNameLoc(const clang::Decl *D);
|
||||||
/// like inline namespaces.
|
/// like inline namespaces.
|
||||||
std::string printQualifiedName(const NamedDecl &ND);
|
std::string printQualifiedName(const NamedDecl &ND);
|
||||||
|
|
||||||
|
/// Returns the first enclosing namespace scope starting from \p DC.
|
||||||
|
std::string printNamespaceScope(const DeclContext &DC);
|
||||||
|
|
||||||
/// Gets the symbol ID for a declaration, if possible.
|
/// Gets the symbol ID for a declaration, if possible.
|
||||||
llvm::Optional<SymbolID> getSymbolID(const Decl *D);
|
llvm::Optional<SymbolID> getSymbolID(const Decl *D);
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,8 @@
|
||||||
#include "Trace.h"
|
#include "Trace.h"
|
||||||
#include "URI.h"
|
#include "URI.h"
|
||||||
#include "index/Index.h"
|
#include "index/Index.h"
|
||||||
|
#include "clang/AST/Decl.h"
|
||||||
|
#include "clang/AST/DeclBase.h"
|
||||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||||
#include "clang/Basic/LangOptions.h"
|
#include "clang/Basic/LangOptions.h"
|
||||||
#include "clang/Basic/SourceLocation.h"
|
#include "clang/Basic/SourceLocation.h"
|
||||||
|
@ -44,6 +46,7 @@
|
||||||
#include "clang/Sema/CodeCompleteConsumer.h"
|
#include "clang/Sema/CodeCompleteConsumer.h"
|
||||||
#include "clang/Sema/Sema.h"
|
#include "clang/Sema/Sema.h"
|
||||||
#include "llvm/ADT/ArrayRef.h"
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
|
#include "llvm/ADT/None.h"
|
||||||
#include "llvm/ADT/Optional.h"
|
#include "llvm/ADT/Optional.h"
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
#include "llvm/Support/Error.h"
|
#include "llvm/Support/Error.h"
|
||||||
|
@ -549,9 +552,10 @@ struct SpecifiedScope {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get all scopes that will be queried in indexes and whether symbols from
|
// Get all scopes that will be queried in indexes and whether symbols from
|
||||||
// any scope is allowed.
|
// any scope is allowed. The first scope in the list is the preferred scope
|
||||||
|
// (e.g. enclosing namespace).
|
||||||
std::pair<std::vector<std::string>, bool>
|
std::pair<std::vector<std::string>, bool>
|
||||||
getQueryScopes(CodeCompletionContext &CCContext, const SourceManager &SM,
|
getQueryScopes(CodeCompletionContext &CCContext, const Sema &CCSema,
|
||||||
const CodeCompleteOptions &Opts) {
|
const CodeCompleteOptions &Opts) {
|
||||||
auto GetAllAccessibleScopes = [](CodeCompletionContext &CCContext) {
|
auto GetAllAccessibleScopes = [](CodeCompletionContext &CCContext) {
|
||||||
SpecifiedScope Info;
|
SpecifiedScope Info;
|
||||||
|
@ -559,7 +563,7 @@ getQueryScopes(CodeCompletionContext &CCContext, const SourceManager &SM,
|
||||||
if (isa<TranslationUnitDecl>(Context))
|
if (isa<TranslationUnitDecl>(Context))
|
||||||
Info.AccessibleScopes.push_back(""); // global namespace
|
Info.AccessibleScopes.push_back(""); // global namespace
|
||||||
else if (const auto *NS = dyn_cast<NamespaceDecl>(Context))
|
else if (const auto *NS = dyn_cast<NamespaceDecl>(Context))
|
||||||
Info.AccessibleScopes.push_back(NS->getQualifiedNameAsString() + "::");
|
Info.AccessibleScopes.push_back(printNamespaceScope(*Context));
|
||||||
}
|
}
|
||||||
return Info;
|
return Info;
|
||||||
};
|
};
|
||||||
|
@ -568,12 +572,13 @@ getQueryScopes(CodeCompletionContext &CCContext, const SourceManager &SM,
|
||||||
|
|
||||||
// Unqualified completion (e.g. "vec^").
|
// Unqualified completion (e.g. "vec^").
|
||||||
if (!SS) {
|
if (!SS) {
|
||||||
// FIXME: Once we can insert namespace qualifiers and use the in-scope
|
std::vector<std::string> Scopes;
|
||||||
// namespaces for scoring, search in all namespaces.
|
std::string EnclosingScope = printNamespaceScope(*CCSema.CurContext);
|
||||||
// FIXME: Capture scopes and use for scoring, for example,
|
Scopes.push_back(EnclosingScope);
|
||||||
// "using namespace std; namespace foo {v^}" =>
|
for (auto &S : GetAllAccessibleScopes(CCContext).scopesForIndexQuery()) {
|
||||||
// foo::value > std::vector > boost::variant
|
if (EnclosingScope != S)
|
||||||
auto Scopes = GetAllAccessibleScopes(CCContext).scopesForIndexQuery();
|
Scopes.push_back(std::move(S));
|
||||||
|
}
|
||||||
// Allow AllScopes completion only for there is no explicit scope qualifier.
|
// Allow AllScopes completion only for there is no explicit scope qualifier.
|
||||||
return {Scopes, Opts.AllScopes};
|
return {Scopes, Opts.AllScopes};
|
||||||
}
|
}
|
||||||
|
@ -592,8 +597,8 @@ getQueryScopes(CodeCompletionContext &CCContext, const SourceManager &SM,
|
||||||
Info.AccessibleScopes.push_back(""); // global namespace
|
Info.AccessibleScopes.push_back(""); // global namespace
|
||||||
|
|
||||||
Info.UnresolvedQualifier =
|
Info.UnresolvedQualifier =
|
||||||
Lexer::getSourceText(CharSourceRange::getCharRange((*SS)->getRange()), SM,
|
Lexer::getSourceText(CharSourceRange::getCharRange((*SS)->getRange()),
|
||||||
clang::LangOptions())
|
CCSema.SourceMgr, clang::LangOptions())
|
||||||
.ltrim("::");
|
.ltrim("::");
|
||||||
// Sema excludes the trailing "::".
|
// Sema excludes the trailing "::".
|
||||||
if (!Info.UnresolvedQualifier->empty())
|
if (!Info.UnresolvedQualifier->empty())
|
||||||
|
@ -1216,6 +1221,8 @@ class CodeCompleteFlow {
|
||||||
bool Incomplete = false; // Would more be available with a higher limit?
|
bool Incomplete = false; // Would more be available with a higher limit?
|
||||||
llvm::Optional<FuzzyMatcher> Filter; // Initialized once Sema runs.
|
llvm::Optional<FuzzyMatcher> Filter; // Initialized once Sema runs.
|
||||||
std::vector<std::string> QueryScopes; // Initialized once Sema runs.
|
std::vector<std::string> QueryScopes; // Initialized once Sema runs.
|
||||||
|
// Initialized once QueryScopes is initialized, if there are scopes.
|
||||||
|
llvm::Optional<ScopeDistance> ScopeProximity;
|
||||||
// Whether to query symbols from any scope. Initialized once Sema runs.
|
// Whether to query symbols from any scope. Initialized once Sema runs.
|
||||||
bool AllScopes = false;
|
bool AllScopes = false;
|
||||||
// Include-insertion and proximity scoring rely on the include structure.
|
// Include-insertion and proximity scoring rely on the include structure.
|
||||||
|
@ -1341,8 +1348,10 @@ private:
|
||||||
}
|
}
|
||||||
Filter = FuzzyMatcher(
|
Filter = FuzzyMatcher(
|
||||||
Recorder->CCSema->getPreprocessor().getCodeCompletionFilter());
|
Recorder->CCSema->getPreprocessor().getCodeCompletionFilter());
|
||||||
std::tie(QueryScopes, AllScopes) = getQueryScopes(
|
std::tie(QueryScopes, AllScopes) =
|
||||||
Recorder->CCContext, Recorder->CCSema->getSourceManager(), Opts);
|
getQueryScopes(Recorder->CCContext, *Recorder->CCSema, Opts);
|
||||||
|
if (!QueryScopes.empty())
|
||||||
|
ScopeProximity.emplace(QueryScopes);
|
||||||
// Sema provides the needed context to query the index.
|
// Sema provides the needed context to query the index.
|
||||||
// FIXME: in addition to querying for extra/overlapping symbols, we should
|
// FIXME: in addition to querying for extra/overlapping symbols, we should
|
||||||
// explicitly request symbols corresponding to Sema results.
|
// explicitly request symbols corresponding to Sema results.
|
||||||
|
@ -1479,7 +1488,8 @@ private:
|
||||||
Relevance.Context = Recorder->CCContext.getKind();
|
Relevance.Context = Recorder->CCContext.getKind();
|
||||||
Relevance.Query = SymbolRelevanceSignals::CodeComplete;
|
Relevance.Query = SymbolRelevanceSignals::CodeComplete;
|
||||||
Relevance.FileProximityMatch = FileProximity.getPointer();
|
Relevance.FileProximityMatch = FileProximity.getPointer();
|
||||||
// FIXME: incorparate scope proximity into relevance score.
|
if (ScopeProximity)
|
||||||
|
Relevance.ScopeProximityMatch = ScopeProximity.getPointer();
|
||||||
|
|
||||||
auto &First = Bundle.front();
|
auto &First = Bundle.front();
|
||||||
if (auto FuzzyScore = fuzzyScore(First))
|
if (auto FuzzyScore = fuzzyScore(First))
|
||||||
|
|
|
@ -176,5 +176,46 @@ FileDistance &URIDistance::forScheme(llvm::StringRef Scheme) {
|
||||||
return *Delegate;
|
return *Delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::pair<std::string, int> scopeToPath(llvm::StringRef Scope) {
|
||||||
|
SmallVector<StringRef, 4> Split;
|
||||||
|
Scope.split(Split, "::", /*MaxSplit=*/-1, /*KeepEmpty=*/false);
|
||||||
|
return {"/" + llvm::join(Split, "/"), Split.size()};
|
||||||
|
}
|
||||||
|
|
||||||
|
static FileDistance
|
||||||
|
createScopeFileDistance(llvm::ArrayRef<std::string> QueryScopes) {
|
||||||
|
FileDistanceOptions Opts;
|
||||||
|
Opts.UpCost = 2;
|
||||||
|
Opts.DownCost = 4;
|
||||||
|
Opts.AllowDownTraversalFromRoot = false;
|
||||||
|
|
||||||
|
StringMap<SourceParams> Sources;
|
||||||
|
StringRef Preferred = QueryScopes.empty() ? "" : QueryScopes.front().c_str();
|
||||||
|
for (StringRef S : QueryScopes) {
|
||||||
|
SourceParams Param;
|
||||||
|
// Penalize the global scope even it's preferred, as all projects can define
|
||||||
|
// symbols in it, and there is pattern where using-namespace is used in
|
||||||
|
// place of enclosing namespaces (e.g. in implementation files).
|
||||||
|
if (S == Preferred)
|
||||||
|
Param.Cost = S == "" ? 2 : 0;
|
||||||
|
else if (Preferred.startswith(S) && !S.empty())
|
||||||
|
continue; // just rely on up-traversals.
|
||||||
|
else
|
||||||
|
Param.Cost = S == "" ? 5 : 2;
|
||||||
|
auto Path = scopeToPath(S);
|
||||||
|
// The global namespace is not 'near' its children.
|
||||||
|
Param.MaxUpTraversals = std::max(Path.second - 1, 0);
|
||||||
|
Sources[Path.first] = std::move(Param);
|
||||||
|
}
|
||||||
|
return FileDistance(Sources, Opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopeDistance::ScopeDistance(llvm::ArrayRef<std::string> QueryScopes)
|
||||||
|
: Distance(createScopeFileDistance(QueryScopes)) {}
|
||||||
|
|
||||||
|
unsigned ScopeDistance::distance(llvm::StringRef SymbolScope) {
|
||||||
|
return Distance.distance(scopeToPath(SymbolScope).first);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace clangd
|
} // namespace clangd
|
||||||
} // namespace clang
|
} // namespace clang
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
#include "llvm/Support/Allocator.h"
|
#include "llvm/Support/Allocator.h"
|
||||||
#include "llvm/Support/Path.h"
|
#include "llvm/Support/Path.h"
|
||||||
#include "llvm/Support/StringSaver.h"
|
#include "llvm/Support/StringSaver.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
namespace clang {
|
namespace clang {
|
||||||
namespace clangd {
|
namespace clangd {
|
||||||
|
@ -111,6 +112,19 @@ private:
|
||||||
FileDistanceOptions Opts;
|
FileDistanceOptions Opts;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Support lookups like FileDistance, but the lookup keys are symbol scopes.
|
||||||
|
/// For example, a scope "na::nb::" is converted to "/na/nb".
|
||||||
|
class ScopeDistance {
|
||||||
|
public:
|
||||||
|
/// QueryScopes[0] is the preferred scope.
|
||||||
|
ScopeDistance(llvm::ArrayRef<std::string> QueryScopes);
|
||||||
|
|
||||||
|
unsigned distance(llvm::StringRef SymbolScope);
|
||||||
|
|
||||||
|
private:
|
||||||
|
FileDistance Distance;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace clangd
|
} // namespace clangd
|
||||||
} // namespace clang
|
} // namespace clang
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,16 @@
|
||||||
#include "clang/Basic/CharInfo.h"
|
#include "clang/Basic/CharInfo.h"
|
||||||
#include "clang/Basic/SourceManager.h"
|
#include "clang/Basic/SourceManager.h"
|
||||||
#include "clang/Sema/CodeCompleteConsumer.h"
|
#include "clang/Sema/CodeCompleteConsumer.h"
|
||||||
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
|
#include "llvm/ADT/SmallString.h"
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
#include "llvm/ADT/StringExtras.h"
|
||||||
|
#include "llvm/ADT/StringRef.h"
|
||||||
#include "llvm/Support/Casting.h"
|
#include "llvm/Support/Casting.h"
|
||||||
#include "llvm/Support/FormatVariadic.h"
|
#include "llvm/Support/FormatVariadic.h"
|
||||||
#include "llvm/Support/MathExtras.h"
|
#include "llvm/Support/MathExtras.h"
|
||||||
#include "llvm/Support/raw_ostream.h"
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
namespace clang {
|
namespace clang {
|
||||||
|
@ -268,6 +274,7 @@ void SymbolRelevanceSignals::merge(const Symbol &IndexResult) {
|
||||||
// relevant to non-completion requests, we should recognize class members etc.
|
// relevant to non-completion requests, we should recognize class members etc.
|
||||||
|
|
||||||
SymbolURI = IndexResult.CanonicalDeclaration.FileURI;
|
SymbolURI = IndexResult.CanonicalDeclaration.FileURI;
|
||||||
|
SymbolScope = IndexResult.Scope;
|
||||||
IsInstanceMember |= isInstanceMember(IndexResult.SymInfo);
|
IsInstanceMember |= isInstanceMember(IndexResult.SymInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,6 +284,7 @@ void SymbolRelevanceSignals::merge(const CodeCompletionResult &SemaCCResult) {
|
||||||
Forbidden = true;
|
Forbidden = true;
|
||||||
|
|
||||||
if (SemaCCResult.Declaration) {
|
if (SemaCCResult.Declaration) {
|
||||||
|
SemaSaysInScope = true;
|
||||||
// We boost things that have decls in the main file. We give a fixed score
|
// We boost things that have decls in the main file. We give a fixed score
|
||||||
// for all other declarations in sema as they are already included in the
|
// for all other declarations in sema as they are already included in the
|
||||||
// translation unit.
|
// translation unit.
|
||||||
|
@ -284,7 +292,7 @@ void SymbolRelevanceSignals::merge(const CodeCompletionResult &SemaCCResult) {
|
||||||
hasUsingDeclInMainFile(SemaCCResult))
|
hasUsingDeclInMainFile(SemaCCResult))
|
||||||
? 1.0
|
? 1.0
|
||||||
: 0.6;
|
: 0.6;
|
||||||
SemaProximityScore = std::max(DeclProximity, SemaProximityScore);
|
SemaFileProximityScore = std::max(DeclProximity, SemaFileProximityScore);
|
||||||
IsInstanceMember |= isInstanceMember(SemaCCResult.Declaration);
|
IsInstanceMember |= isInstanceMember(SemaCCResult.Declaration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,8 +303,8 @@ void SymbolRelevanceSignals::merge(const CodeCompletionResult &SemaCCResult) {
|
||||||
NeedsFixIts = !SemaCCResult.FixIts.empty();
|
NeedsFixIts = !SemaCCResult.FixIts.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::pair<float, unsigned> proximityScore(llvm::StringRef SymbolURI,
|
static std::pair<float, unsigned> uriProximity(llvm::StringRef SymbolURI,
|
||||||
URIDistance *D) {
|
URIDistance *D) {
|
||||||
if (!D || SymbolURI.empty())
|
if (!D || SymbolURI.empty())
|
||||||
return {0.f, 0u};
|
return {0.f, 0u};
|
||||||
unsigned Distance = D->distance(SymbolURI);
|
unsigned Distance = D->distance(SymbolURI);
|
||||||
|
@ -304,6 +312,16 @@ static std::pair<float, unsigned> proximityScore(llvm::StringRef SymbolURI,
|
||||||
return {std::exp(Distance * -0.4f / FileDistanceOptions().UpCost), Distance};
|
return {std::exp(Distance * -0.4f / FileDistanceOptions().UpCost), Distance};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static float scopeBoost(ScopeDistance &Distance,
|
||||||
|
llvm::Optional<llvm::StringRef> SymbolScope) {
|
||||||
|
if (!SymbolScope)
|
||||||
|
return 1;
|
||||||
|
auto D = Distance.distance(*SymbolScope);
|
||||||
|
if (D == FileDistance::Unreachable)
|
||||||
|
return 0.4;
|
||||||
|
return std::max(0.5, 2.0 * std::pow(0.6, D / 2.0));
|
||||||
|
}
|
||||||
|
|
||||||
float SymbolRelevanceSignals::evaluate() const {
|
float SymbolRelevanceSignals::evaluate() const {
|
||||||
float Score = 1;
|
float Score = 1;
|
||||||
|
|
||||||
|
@ -312,10 +330,18 @@ float SymbolRelevanceSignals::evaluate() const {
|
||||||
|
|
||||||
Score *= NameMatch;
|
Score *= NameMatch;
|
||||||
|
|
||||||
// Proximity scores are [0,1] and we translate them into a multiplier in the
|
// File proximity scores are [0,1] and we translate them into a multiplier in
|
||||||
// range from 1 to 3.
|
// the range from 1 to 3.
|
||||||
Score *= 1 + 2 * std::max(proximityScore(SymbolURI, FileProximityMatch).first,
|
Score *= 1 + 2 * std::max(uriProximity(SymbolURI, FileProximityMatch).first,
|
||||||
SemaProximityScore);
|
SemaFileProximityScore);
|
||||||
|
|
||||||
|
if (ScopeProximityMatch)
|
||||||
|
// Use a constant scope boost for sema results, as scopes of sema results
|
||||||
|
// can be tricky (e.g. class/function scope). Set to the max boost as we
|
||||||
|
// don't load top-level symbols from the preamble and sema results are
|
||||||
|
// always in the accessible scope.
|
||||||
|
Score *=
|
||||||
|
SemaSaysInScope ? 2.0 : scopeBoost(*ScopeProximityMatch, SymbolScope);
|
||||||
|
|
||||||
// Symbols like local variables may only be referenced within their scope.
|
// Symbols like local variables may only be referenced within their scope.
|
||||||
// Conversely if we're in that scope, it's likely we'll reference them.
|
// Conversely if we're in that scope, it's likely we'll reference them.
|
||||||
|
@ -358,15 +384,25 @@ raw_ostream &operator<<(raw_ostream &OS, const SymbolRelevanceSignals &S) {
|
||||||
OS << formatv("\tNeedsFixIts: {0}\n", S.NeedsFixIts);
|
OS << formatv("\tNeedsFixIts: {0}\n", S.NeedsFixIts);
|
||||||
OS << formatv("\tIsInstanceMember: {0}\n", S.IsInstanceMember);
|
OS << formatv("\tIsInstanceMember: {0}\n", S.IsInstanceMember);
|
||||||
OS << formatv("\tContext: {0}\n", getCompletionKindString(S.Context));
|
OS << formatv("\tContext: {0}\n", getCompletionKindString(S.Context));
|
||||||
OS << formatv("\tSymbol URI: {0}\n", S.SymbolURI);
|
|
||||||
if (S.FileProximityMatch) {
|
|
||||||
auto Score = proximityScore(S.SymbolURI, S.FileProximityMatch);
|
|
||||||
OS << formatv("\tIndex proximity: {0} (distance={1})\n", Score.first,
|
|
||||||
Score.second);
|
|
||||||
}
|
|
||||||
OS << formatv("\tSema proximity: {0}\n", S.SemaProximityScore);
|
|
||||||
OS << formatv("\tQuery type: {0}\n", static_cast<int>(S.Query));
|
OS << formatv("\tQuery type: {0}\n", static_cast<int>(S.Query));
|
||||||
OS << formatv("\tScope: {0}\n", static_cast<int>(S.Scope));
|
OS << formatv("\tScope: {0}\n", static_cast<int>(S.Scope));
|
||||||
|
|
||||||
|
OS << formatv("\tSymbol URI: {0}\n", S.SymbolURI);
|
||||||
|
OS << formatv("\tSymbol scope: {0}\n",
|
||||||
|
S.SymbolScope ? *S.SymbolScope : "<None>");
|
||||||
|
|
||||||
|
if (S.FileProximityMatch) {
|
||||||
|
auto Score = uriProximity(S.SymbolURI, S.FileProximityMatch);
|
||||||
|
OS << formatv("\tIndex URI proximity: {0} (distance={1})\n", Score.first,
|
||||||
|
Score.second);
|
||||||
|
}
|
||||||
|
OS << formatv("\tSema file proximity: {0}\n", S.SemaFileProximityScore);
|
||||||
|
|
||||||
|
OS << formatv("\tSema says in scope: {0}\n", S.SemaSaysInScope);
|
||||||
|
if (S.ScopeProximityMatch)
|
||||||
|
OS << formatv("\tIndex scope boost: {0}\n",
|
||||||
|
scopeBoost(*S.ScopeProximityMatch, S.SymbolScope));
|
||||||
|
|
||||||
return OS;
|
return OS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_QUALITY_H
|
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_QUALITY_H
|
||||||
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_QUALITY_H
|
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_QUALITY_H
|
||||||
|
|
||||||
|
#include "FileDistance.h"
|
||||||
#include "clang/Sema/CodeCompleteConsumer.h"
|
#include "clang/Sema/CodeCompleteConsumer.h"
|
||||||
#include "llvm/ADT/ArrayRef.h"
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
#include "llvm/ADT/StringRef.h"
|
#include "llvm/ADT/StringRef.h"
|
||||||
|
@ -87,13 +88,19 @@ struct SymbolRelevanceSignals {
|
||||||
bool NeedsFixIts = false;
|
bool NeedsFixIts = false;
|
||||||
|
|
||||||
URIDistance *FileProximityMatch = nullptr;
|
URIDistance *FileProximityMatch = nullptr;
|
||||||
/// This is used to calculate proximity between the index symbol and the
|
/// These are used to calculate proximity between the index symbol and the
|
||||||
/// query.
|
/// query.
|
||||||
llvm::StringRef SymbolURI;
|
llvm::StringRef SymbolURI;
|
||||||
/// Proximity between best declaration and the query. [0-1], 1 is closest.
|
|
||||||
/// FIXME: unify with index proximity score - signals should be
|
/// FIXME: unify with index proximity score - signals should be
|
||||||
/// source-independent.
|
/// source-independent.
|
||||||
float SemaProximityScore = 0;
|
/// Proximity between best declaration and the query. [0-1], 1 is closest.
|
||||||
|
float SemaFileProximityScore = 0;
|
||||||
|
|
||||||
|
// Scope proximity is only considered (both index and sema) when this is set.
|
||||||
|
ScopeDistance *ScopeProximityMatch = nullptr;
|
||||||
|
llvm::Optional<llvm::StringRef> SymbolScope;
|
||||||
|
// A symbol from sema should be accessible from the current scope.
|
||||||
|
bool SemaSaysInScope = false;
|
||||||
|
|
||||||
// An approximate measure of where we expect the symbol to be used.
|
// An approximate measure of where we expect the symbol to be used.
|
||||||
enum AccessibleScope {
|
enum AccessibleScope {
|
||||||
|
|
|
@ -1040,6 +1040,28 @@ TEST(CompletionTest, UnqualifiedIdQuery) {
|
||||||
UnorderedElementsAre("", "ns::", "std::"))));
|
UnorderedElementsAre("", "ns::", "std::"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(CompletionTest, EnclosingScopeComesFirst) {
|
||||||
|
auto Requests = captureIndexRequests(R"cpp(
|
||||||
|
namespace std {}
|
||||||
|
using namespace std;
|
||||||
|
namespace nx {
|
||||||
|
namespace ns {
|
||||||
|
namespace {
|
||||||
|
void f() {
|
||||||
|
vec^
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)cpp");
|
||||||
|
|
||||||
|
EXPECT_THAT(Requests,
|
||||||
|
ElementsAre(Field(
|
||||||
|
&FuzzyFindRequest::Scopes,
|
||||||
|
UnorderedElementsAre("", "std::", "nx::ns::", "nx::"))));
|
||||||
|
EXPECT_EQ(Requests[0].Scopes[0], "nx::ns::");
|
||||||
|
}
|
||||||
|
|
||||||
TEST(CompletionTest, ResolvedQualifiedIdQuery) {
|
TEST(CompletionTest, ResolvedQualifiedIdQuery) {
|
||||||
auto Requests = captureIndexRequests(R"cpp(
|
auto Requests = captureIndexRequests(R"cpp(
|
||||||
namespace ns1 {}
|
namespace ns1 {}
|
||||||
|
|
|
@ -109,6 +109,16 @@ TEST(FileDistance, DisallowDownTraversalsFromRoot) {
|
||||||
EXPECT_EQ(D.distance("/x"), FileDistance::Unreachable);
|
EXPECT_EQ(D.distance("/x"), FileDistance::Unreachable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(ScopeDistance, Smoke) {
|
||||||
|
ScopeDistance D({"x::y::z", "x::", "", "a::"});
|
||||||
|
EXPECT_EQ(D.distance("x::y::z::"), 0u);
|
||||||
|
EXPECT_GT(D.distance("x::y::"), D.distance("x::y::z::"));
|
||||||
|
EXPECT_GT(D.distance("x::"), D.distance("x::y::"));
|
||||||
|
EXPECT_GT(D.distance("x::y::z::down::"), D.distance("x::y::"));
|
||||||
|
EXPECT_GT(D.distance(""), D.distance("a::"));
|
||||||
|
EXPECT_GT(D.distance("x::"), D.distance("a::"));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace clangd
|
} // namespace clangd
|
||||||
} // namespace clang
|
} // namespace clang
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include "llvm/Support/Casting.h"
|
#include "llvm/Support/Casting.h"
|
||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace clang {
|
namespace clang {
|
||||||
namespace clangd {
|
namespace clangd {
|
||||||
|
@ -117,13 +118,14 @@ TEST(QualityTests, SymbolRelevanceSignalExtraction) {
|
||||||
|
|
||||||
Relevance = {};
|
Relevance = {};
|
||||||
Relevance.merge(CodeCompletionResult(&findDecl(AST, "main"), 42));
|
Relevance.merge(CodeCompletionResult(&findDecl(AST, "main"), 42));
|
||||||
EXPECT_FLOAT_EQ(Relevance.SemaProximityScore, 1.0f) << "Decl in current file";
|
EXPECT_FLOAT_EQ(Relevance.SemaFileProximityScore, 1.0f)
|
||||||
|
<< "Decl in current file";
|
||||||
Relevance = {};
|
Relevance = {};
|
||||||
Relevance.merge(CodeCompletionResult(&findDecl(AST, "header"), 42));
|
Relevance.merge(CodeCompletionResult(&findDecl(AST, "header"), 42));
|
||||||
EXPECT_FLOAT_EQ(Relevance.SemaProximityScore, 0.6f) << "Decl from header";
|
EXPECT_FLOAT_EQ(Relevance.SemaFileProximityScore, 0.6f) << "Decl from header";
|
||||||
Relevance = {};
|
Relevance = {};
|
||||||
Relevance.merge(CodeCompletionResult(&findDecl(AST, "header_main"), 42));
|
Relevance.merge(CodeCompletionResult(&findDecl(AST, "header_main"), 42));
|
||||||
EXPECT_FLOAT_EQ(Relevance.SemaProximityScore, 1.0f)
|
EXPECT_FLOAT_EQ(Relevance.SemaFileProximityScore, 1.0f)
|
||||||
<< "Current file and header";
|
<< "Current file and header";
|
||||||
|
|
||||||
auto constructShadowDeclCompletionResult = [&](const std::string DeclName) {
|
auto constructShadowDeclCompletionResult = [&](const std::string DeclName) {
|
||||||
|
@ -146,10 +148,10 @@ TEST(QualityTests, SymbolRelevanceSignalExtraction) {
|
||||||
|
|
||||||
Relevance = {};
|
Relevance = {};
|
||||||
Relevance.merge(constructShadowDeclCompletionResult("Bar"));
|
Relevance.merge(constructShadowDeclCompletionResult("Bar"));
|
||||||
EXPECT_FLOAT_EQ(Relevance.SemaProximityScore, 1.0f)
|
EXPECT_FLOAT_EQ(Relevance.SemaFileProximityScore, 1.0f)
|
||||||
<< "Using declaration in main file";
|
<< "Using declaration in main file";
|
||||||
Relevance.merge(constructShadowDeclCompletionResult("FLAGS_FOO"));
|
Relevance.merge(constructShadowDeclCompletionResult("FLAGS_FOO"));
|
||||||
EXPECT_FLOAT_EQ(Relevance.SemaProximityScore, 1.0f)
|
EXPECT_FLOAT_EQ(Relevance.SemaFileProximityScore, 1.0f)
|
||||||
<< "Using declaration in main file";
|
<< "Using declaration in main file";
|
||||||
|
|
||||||
Relevance = {};
|
Relevance = {};
|
||||||
|
@ -210,9 +212,21 @@ TEST(QualityTests, SymbolRelevanceSignalsSanity) {
|
||||||
PoorNameMatch.NameMatch = 0.2f;
|
PoorNameMatch.NameMatch = 0.2f;
|
||||||
EXPECT_LT(PoorNameMatch.evaluate(), Default.evaluate());
|
EXPECT_LT(PoorNameMatch.evaluate(), Default.evaluate());
|
||||||
|
|
||||||
SymbolRelevanceSignals WithSemaProximity;
|
SymbolRelevanceSignals WithSemaFileProximity;
|
||||||
WithSemaProximity.SemaProximityScore = 0.2f;
|
WithSemaFileProximity.SemaFileProximityScore = 0.2f;
|
||||||
EXPECT_GT(WithSemaProximity.evaluate(), Default.evaluate());
|
EXPECT_GT(WithSemaFileProximity.evaluate(), Default.evaluate());
|
||||||
|
|
||||||
|
ScopeDistance ScopeProximity({"x::y::"});
|
||||||
|
|
||||||
|
SymbolRelevanceSignals WithSemaScopeProximity;
|
||||||
|
WithSemaScopeProximity.ScopeProximityMatch = &ScopeProximity;
|
||||||
|
WithSemaScopeProximity.SemaSaysInScope = true;
|
||||||
|
EXPECT_GT(WithSemaScopeProximity.evaluate(), Default.evaluate());
|
||||||
|
|
||||||
|
SymbolRelevanceSignals WithIndexScopeProximity;
|
||||||
|
WithIndexScopeProximity.ScopeProximityMatch = &ScopeProximity;
|
||||||
|
WithIndexScopeProximity.SymbolScope = "x::";
|
||||||
|
EXPECT_GT(WithSemaScopeProximity.evaluate(), Default.evaluate());
|
||||||
|
|
||||||
SymbolRelevanceSignals IndexProximate;
|
SymbolRelevanceSignals IndexProximate;
|
||||||
IndexProximate.SymbolURI = "unittest:/foo/bar.h";
|
IndexProximate.SymbolURI = "unittest:/foo/bar.h";
|
||||||
|
@ -242,6 +256,35 @@ TEST(QualityTests, SymbolRelevanceSignalsSanity) {
|
||||||
EXPECT_EQ(Instance.evaluate(), Default.evaluate());
|
EXPECT_EQ(Instance.evaluate(), Default.evaluate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(QualityTests, ScopeProximity) {
|
||||||
|
SymbolRelevanceSignals Relevance;
|
||||||
|
ScopeDistance ScopeProximity({"x::y::z::", "x::", "llvm::", ""});
|
||||||
|
Relevance.ScopeProximityMatch = &ScopeProximity;
|
||||||
|
|
||||||
|
Relevance.SymbolScope = "other::";
|
||||||
|
float NotMatched = Relevance.evaluate();
|
||||||
|
|
||||||
|
Relevance.SymbolScope = "";
|
||||||
|
float Global = Relevance.evaluate();
|
||||||
|
EXPECT_GT(Global, NotMatched);
|
||||||
|
|
||||||
|
Relevance.SymbolScope = "llvm::";
|
||||||
|
float NonParent = Relevance.evaluate();
|
||||||
|
EXPECT_GT(NonParent, Global);
|
||||||
|
|
||||||
|
Relevance.SymbolScope = "x::";
|
||||||
|
float GrandParent = Relevance.evaluate();
|
||||||
|
EXPECT_GT(GrandParent, Global);
|
||||||
|
|
||||||
|
Relevance.SymbolScope = "x::y::";
|
||||||
|
float Parent = Relevance.evaluate();
|
||||||
|
EXPECT_GT(Parent, GrandParent);
|
||||||
|
|
||||||
|
Relevance.SymbolScope = "x::y::z::";
|
||||||
|
float Enclosing = Relevance.evaluate();
|
||||||
|
EXPECT_GT(Enclosing, Parent);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(QualityTests, SortText) {
|
TEST(QualityTests, SortText) {
|
||||||
EXPECT_LT(sortText(std::numeric_limits<float>::infinity()),
|
EXPECT_LT(sortText(std::numeric_limits<float>::infinity()),
|
||||||
sortText(1000.2f));
|
sortText(1000.2f));
|
||||||
|
|
Loading…
Reference in New Issue