Skip to content

Commit

Permalink
[clangd] Penalize non-instance members when accessed via class instan…
Browse files Browse the repository at this point in the history
…ces.

Summary:
The following are metrics for explicit member access completions. There is no
noticeable impact on other completion types.

Before:
EXPLICIT_MEMBER_ACCESS
  Total measurements: 24382
  All measurements: MRR: 62.27	Top10: 80.21%	Top-100: 94.48%
  Full identifiers: MRR: 98.81	Top10: 99.89%	Top-100: 99.95%
  0-5 filter len:
	MRR:  13.25	46.31	62.47	67.77	70.40	81.91
	Top-10:  29%	74%	84%	91%	91%	97%
	Top-100:  67%	99%	99%	99%	99%	100%

After:
EXPLICIT_MEMBER_ACCESS
  Total measurements: 24382
  All measurements: MRR: 63.18	Top10: 80.58%	Top-100: 95.07%
  Full identifiers: MRR: 98.79	Top10: 99.89%	Top-100: 99.95%
  0-5 filter len:
	MRR:  13.84	48.39	63.55	68.83	71.28	82.64
	Top-10:  30%	75%	84%	91%	91%	97%
	Top-100:  70%	99%	99%	99%	99%	100%

* Top-N: wanted result is found in the first N completion results.
* MRR: Mean reciprocal rank.

Remark: the change seems to have minor positive impact. Although the improvement
is relatively small, down-ranking non-instance members in instance member access
should reduce noise in the completion results.

Reviewers: sammccall

Reviewed By: sammccall

Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, cfe-commits

Differential Revision: https://reviews.llvm.org/D49543

llvm-svn: 337681
  • Loading branch information
Eric Liu committed Jul 23, 2018
1 parent bf6009c commit 5d2a807
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 0 deletions.
3 changes: 3 additions & 0 deletions clang-tools-extra/clangd/CodeComplete.cpp
Expand Up @@ -1062,6 +1062,7 @@ class CodeCompleteFlow {
Output.Completions.back().Score = C.second;
}
Output.HasMore = Incomplete;
Output.Context = Recorder->CCContext.getKind();
return Output;
}

Expand Down Expand Up @@ -1156,6 +1157,7 @@ class CodeCompleteFlow {
CompletionCandidate::Bundle Bundle) {
SymbolQualitySignals Quality;
SymbolRelevanceSignals Relevance;
Relevance.Context = Recorder->CCContext.getKind();
Relevance.Query = SymbolRelevanceSignals::CodeComplete;
Relevance.FileProximityMatch = FileProximity.getPointer();
auto &First = Bundle.front();
Expand Down Expand Up @@ -1290,6 +1292,7 @@ raw_ostream &operator<<(raw_ostream &OS, const CodeCompletion &C) {

raw_ostream &operator<<(raw_ostream &OS, const CodeCompleteResult &R) {
OS << "CodeCompleteResult: " << R.Completions.size() << (R.HasMore ? "+" : "")
<< " (" << getCompletionKindString(R.Context) << ")"
<< " items:\n";
for (const auto &C : R.Completions)
OS << C << "\n";
Expand Down
2 changes: 2 additions & 0 deletions clang-tools-extra/clangd/CodeComplete.h
Expand Up @@ -21,6 +21,7 @@
#include "Protocol.h"
#include "index/Index.h"
#include "clang/Frontend/PrecompiledPreamble.h"
#include "clang/Sema/CodeCompleteConsumer.h"
#include "clang/Sema/CodeCompleteOptions.h"
#include "clang/Tooling/CompilationDatabase.h"

Expand Down Expand Up @@ -144,6 +145,7 @@ raw_ostream &operator<<(raw_ostream &, const CodeCompletion &);
struct CodeCompleteResult {
std::vector<CodeCompletion> Completions;
bool HasMore = false;
CodeCompletionContext::Kind Context = CodeCompletionContext::CCC_Other;
};
raw_ostream &operator<<(raw_ostream &, const CodeCompleteResult &);

Expand Down
34 changes: 34 additions & 0 deletions clang-tools-extra/clangd/Quality.cpp
Expand Up @@ -11,7 +11,9 @@
#include "URI.h"
#include "index/Index.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/DeclVisitor.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/SourceManager.h"
Expand Down Expand Up @@ -139,6 +141,27 @@ categorize(const index::SymbolInfo &D) {
llvm_unreachable("Unknown index::SymbolKind");
}

static bool isInstanceMember(const NamedDecl *ND) {
if (!ND)
return false;
if (const auto *TP = dyn_cast<FunctionTemplateDecl>(ND))
ND = TP->TemplateDecl::getTemplatedDecl();
if (const auto *CM = dyn_cast<CXXMethodDecl>(ND))
return !CM->isStatic();
return isa<FieldDecl>(ND); // Note that static fields are VarDecl.
}

static bool isInstanceMember(const index::SymbolInfo &D) {
switch (D.Kind) {
case index::SymbolKind::InstanceMethod:
case index::SymbolKind::InstanceProperty:
case index::SymbolKind::Field:
return true;
default:
return false;
}
}

void SymbolQualitySignals::merge(const CodeCompletionResult &SemaCCResult) {
if (SemaCCResult.Availability == CXAvailability_Deprecated)
Deprecated = true;
Expand Down Expand Up @@ -231,6 +254,7 @@ void SymbolRelevanceSignals::merge(const Symbol &IndexResult) {
// relevant to non-completion requests, we should recognize class members etc.

SymbolURI = IndexResult.CanonicalDeclaration.FileURI;
IsInstanceMember |= isInstanceMember(IndexResult.SymInfo);
}

void SymbolRelevanceSignals::merge(const CodeCompletionResult &SemaCCResult) {
Expand All @@ -247,6 +271,7 @@ void SymbolRelevanceSignals::merge(const CodeCompletionResult &SemaCCResult) {
? 1.0
: 0.6;
SemaProximityScore = std::max(DeclProximity, SemaProximityScore);
IsInstanceMember |= isInstanceMember(SemaCCResult.Declaration);
}

// Declarations are scoped, others (like macros) are assumed global.
Expand Down Expand Up @@ -296,13 +321,22 @@ float SymbolRelevanceSignals::evaluate() const {
}
}

// Penalize non-instance members when they are accessed via a class instance.
if (!IsInstanceMember &&
(Context == CodeCompletionContext::CCC_DotMemberAccess ||
Context == CodeCompletionContext::CCC_ArrowMemberAccess)) {
Score *= 0.5;
}

return Score;
}

raw_ostream &operator<<(raw_ostream &OS, const SymbolRelevanceSignals &S) {
OS << formatv("=== Symbol relevance: {0}\n", S.evaluate());
OS << formatv("\tName match: {0}\n", S.NameMatch);
OS << formatv("\tForbidden: {0}\n", S.Forbidden);
OS << formatv("\tIsInstanceMember: {0}\n", S.IsInstanceMember);
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);
Expand Down
6 changes: 6 additions & 0 deletions clang-tools-extra/clangd/Quality.h
Expand Up @@ -26,6 +26,7 @@
//===---------------------------------------------------------------------===//
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_QUALITY_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_QUALITY_H
#include "clang/Sema/CodeCompleteConsumer.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
#include <algorithm>
Expand Down Expand Up @@ -98,6 +99,11 @@ struct SymbolRelevanceSignals {
Generic,
} Query = Generic;

CodeCompletionContext::Kind Context = CodeCompletionContext::CCC_Other;

// Whether symbol is an instance member of a class.
bool IsInstanceMember = false;

void merge(const CodeCompletionResult &SemaResult);
void merge(const Symbol &IndexResult);

Expand Down
16 changes: 16 additions & 0 deletions clang-tools-extra/unittests/clangd/CodeCompleteTests.cpp
Expand Up @@ -18,6 +18,7 @@
#include "SyncAPI.h"
#include "TestFS.h"
#include "index/MemIndex.h"
#include "clang/Sema/CodeCompleteConsumer.h"
#include "llvm/Support/Error.h"
#include "llvm/Testing/Support/Error.h"
#include "gmock/gmock.h"
Expand Down Expand Up @@ -1321,6 +1322,21 @@ TEST(CompletionTest, ScopeOfClassFieldInConstructorInitializer) {
UnorderedElementsAre(AllOf(Scope("ns::X::"), Named("x_"))));
}

TEST(CompletionTest, CodeCompletionContext) {
auto Results = completions(
R"cpp(
namespace ns {
class X { public: X(); int x_; };
void f() {
X x;
x.^;
}
}
)cpp");

EXPECT_THAT(Results.Context, CodeCompletionContext::CCC_DotMemberAccess);
}

} // namespace
} // namespace clangd
} // namespace clang
50 changes: 50 additions & 0 deletions clang-tools-extra/unittests/clangd/QualityTests.cpp
Expand Up @@ -23,6 +23,7 @@
#include "TestTU.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/Sema/CodeCompleteConsumer.h"
#include "llvm/Support/Casting.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
Expand Down Expand Up @@ -227,6 +228,14 @@ TEST(QualityTests, SymbolRelevanceSignalsSanity) {
EXPECT_EQ(Scoped.evaluate(), Default.evaluate());
Scoped.Query = SymbolRelevanceSignals::CodeComplete;
EXPECT_GT(Scoped.evaluate(), Default.evaluate());

SymbolRelevanceSignals Instance;
Instance.IsInstanceMember = false;
EXPECT_EQ(Instance.evaluate(), Default.evaluate());
Instance.Context = CodeCompletionContext::CCC_DotMemberAccess;
EXPECT_LT(Instance.evaluate(), Default.evaluate());
Instance.IsInstanceMember = true;
EXPECT_EQ(Instance.evaluate(), Default.evaluate());
}

TEST(QualityTests, SortText) {
Expand Down Expand Up @@ -267,6 +276,47 @@ TEST(QualityTests, NoBoostForClassConstructor) {
EXPECT_EQ(Ctor.Scope, SymbolRelevanceSignals::GlobalScope);
}

TEST(QualityTests, IsInstanceMember) {
auto Header = TestTU::withHeaderCode(R"cpp(
class Foo {
public:
static void foo() {}
template <typename T> void tpl(T *t) {}
void bar() {}
};
)cpp");
auto Symbols = Header.headerSymbols();

SymbolRelevanceSignals Rel;
const Symbol &FooSym = findSymbol(Symbols, "Foo::foo");
Rel.merge(FooSym);
EXPECT_FALSE(Rel.IsInstanceMember);
const Symbol &BarSym = findSymbol(Symbols, "Foo::bar");
Rel.merge(BarSym);
EXPECT_TRUE(Rel.IsInstanceMember);

Rel.IsInstanceMember =false;
const Symbol &TplSym = findSymbol(Symbols, "Foo::tpl");
Rel.merge(TplSym);
EXPECT_TRUE(Rel.IsInstanceMember);

auto AST = Header.build();
const NamedDecl *Foo = &findDecl(AST, "Foo::foo");
const NamedDecl *Bar = &findDecl(AST, "Foo::bar");
const NamedDecl *Tpl = &findDecl(AST, "Foo::tpl");

Rel.IsInstanceMember = false;
Rel.merge(CodeCompletionResult(Foo, /*Priority=*/0));
EXPECT_FALSE(Rel.IsInstanceMember);
Rel.merge(CodeCompletionResult(Bar, /*Priority=*/0));
EXPECT_TRUE(Rel.IsInstanceMember);
Rel.IsInstanceMember = false;
Rel.merge(CodeCompletionResult(Tpl, /*Priority=*/0));
EXPECT_TRUE(Rel.IsInstanceMember);
}

} // namespace
} // namespace clangd
} // namespace clang

0 comments on commit 5d2a807

Please sign in to comment.