Skip to content

Commit 0e00611

Browse files
committed
[clangd] Add heuristic for dropping snippet when completing member function pointer
This implements the 1st heuristic mentioned in clangd/clangd#968 (comment): When completing a function that names a non-static member of a class, and we are not inside that class's scope, assume the reference will not be a call (and thus don't add the snippetSuffix) Reviewed By: nridge Differential Revision: https://reviews.llvm.org/D137040
1 parent 9e52db1 commit 0e00611

File tree

5 files changed

+213
-10
lines changed

5 files changed

+213
-10
lines changed

clang-tools-extra/clangd/CodeComplete.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,8 @@ struct CodeCompletionBuilder {
411411
bool IsPattern = C.SemaResult->Kind == CodeCompletionResult::RK_Pattern;
412412
getSignature(*SemaCCS, &S.Signature, &S.SnippetSuffix,
413413
&Completion.RequiredQualifier, IsPattern);
414+
if (!C.SemaResult->FunctionCanBeCall)
415+
S.SnippetSuffix.clear();
414416
S.ReturnType = getReturnType(*SemaCCS);
415417
} else if (C.IndexResult) {
416418
S.Signature = std::string(C.IndexResult->Signature);

clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,63 @@ TEST(CompletionTest, Snippets) {
505505
snippetSuffix("(${1:int i}, ${2:const float f})")));
506506
}
507507

508+
TEST(CompletionTest, HeuristicsForMemberFunctionCompletion) {
509+
clangd::CodeCompleteOptions Opts;
510+
Opts.EnableSnippets = true;
511+
512+
Annotations Code(R"cpp(
513+
struct Foo {
514+
static int staticMethod();
515+
int method() const;
516+
Foo() {
517+
this->$keepSnippet^
518+
$keepSnippet^
519+
Foo::$keepSnippet^
520+
}
521+
};
522+
523+
struct Derived : Foo {
524+
Derived() {
525+
Foo::$keepSnippet^
526+
}
527+
};
528+
529+
struct OtherClass {
530+
OtherClass() {
531+
Foo f;
532+
f.$keepSnippet^
533+
&Foo::$noSnippet^
534+
}
535+
};
536+
537+
int main() {
538+
Foo f;
539+
f.$keepSnippet^
540+
&Foo::$noSnippet^
541+
}
542+
)cpp");
543+
auto TU = TestTU::withCode(Code.code());
544+
545+
for (const auto &P : Code.points("noSnippet")) {
546+
auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts);
547+
EXPECT_THAT(Results.Completions,
548+
Contains(AllOf(named("method"), snippetSuffix(""))));
549+
}
550+
551+
for (const auto &P : Code.points("keepSnippet")) {
552+
auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts);
553+
EXPECT_THAT(Results.Completions,
554+
Contains(AllOf(named("method"), snippetSuffix("()"))));
555+
}
556+
557+
// static method will always keep the snippet
558+
for (const auto &P : Code.points()) {
559+
auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts);
560+
EXPECT_THAT(Results.Completions,
561+
Contains(AllOf(named("staticMethod"), snippetSuffix("()"))));
562+
}
563+
}
564+
508565
TEST(CompletionTest, NoSnippetsInUsings) {
509566
clangd::CodeCompleteOptions Opts;
510567
Opts.EnableSnippets = true;

clang/include/clang/Sema/CodeCompleteConsumer.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,12 @@ class CodeCompletionResult {
850850
/// rather than a use of that entity.
851851
bool DeclaringEntity : 1;
852852

853+
/// When completing a function, whether it can be a call. This will usually be
854+
/// true, but we have some heuristics, e.g. when a pointer to a non-static
855+
/// member function is completed outside of that class' scope, it can never
856+
/// be a call.
857+
bool FunctionCanBeCall : 1;
858+
853859
/// If the result should have a nested-name-specifier, this is it.
854860
/// When \c QualifierIsInformative, the nested-name-specifier is
855861
/// informative rather than required.
@@ -876,7 +882,7 @@ class CodeCompletionResult {
876882
FixIts(std::move(FixIts)), Hidden(false), InBaseClass(false),
877883
QualifierIsInformative(QualifierIsInformative),
878884
StartsNestedNameSpecifier(false), AllParametersAreInformative(false),
879-
DeclaringEntity(false), Qualifier(Qualifier) {
885+
DeclaringEntity(false), FunctionCanBeCall(true), Qualifier(Qualifier) {
880886
// FIXME: Add assert to check FixIts range requirements.
881887
computeCursorKindAndAvailability(Accessible);
882888
}
@@ -886,7 +892,8 @@ class CodeCompletionResult {
886892
: Keyword(Keyword), Priority(Priority), Kind(RK_Keyword),
887893
CursorKind(CXCursor_NotImplemented), Hidden(false), InBaseClass(false),
888894
QualifierIsInformative(false), StartsNestedNameSpecifier(false),
889-
AllParametersAreInformative(false), DeclaringEntity(false) {}
895+
AllParametersAreInformative(false), DeclaringEntity(false),
896+
FunctionCanBeCall(true) {}
890897

891898
/// Build a result that refers to a macro.
892899
CodeCompletionResult(const IdentifierInfo *Macro,
@@ -896,7 +903,7 @@ class CodeCompletionResult {
896903
CursorKind(CXCursor_MacroDefinition), Hidden(false), InBaseClass(false),
897904
QualifierIsInformative(false), StartsNestedNameSpecifier(false),
898905
AllParametersAreInformative(false), DeclaringEntity(false),
899-
MacroDefInfo(MI) {}
906+
FunctionCanBeCall(true), MacroDefInfo(MI) {}
900907

901908
/// Build a result that refers to a pattern.
902909
CodeCompletionResult(
@@ -908,7 +915,7 @@ class CodeCompletionResult {
908915
CursorKind(CursorKind), Availability(Availability), Hidden(false),
909916
InBaseClass(false), QualifierIsInformative(false),
910917
StartsNestedNameSpecifier(false), AllParametersAreInformative(false),
911-
DeclaringEntity(false) {}
918+
DeclaringEntity(false), FunctionCanBeCall(true) {}
912919

913920
/// Build a result that refers to a pattern with an associated
914921
/// declaration.
@@ -917,7 +924,7 @@ class CodeCompletionResult {
917924
: Declaration(D), Pattern(Pattern), Priority(Priority), Kind(RK_Pattern),
918925
Hidden(false), InBaseClass(false), QualifierIsInformative(false),
919926
StartsNestedNameSpecifier(false), AllParametersAreInformative(false),
920-
DeclaringEntity(false) {
927+
DeclaringEntity(false), FunctionCanBeCall(true) {
921928
computeCursorKindAndAvailability();
922929
}
923930

clang/lib/Sema/SemaCodeComplete.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,6 +1379,33 @@ void ResultBuilder::AddResult(Result R, DeclContext *CurContext,
13791379
OverloadSet.Add(Method, Results.size());
13801380
}
13811381

1382+
// When completing a non-static member function (and not via
1383+
// dot/arrow member access) and we're not inside that class' scope,
1384+
// it can't be a call.
1385+
if (CompletionContext.getKind() == clang::CodeCompletionContext::CCC_Symbol) {
1386+
const auto *Method = dyn_cast<CXXMethodDecl>(R.getDeclaration());
1387+
if (Method && !Method->isStatic()) {
1388+
// Find the class scope that we're currently in.
1389+
// We could e.g. be inside a lambda, so walk up the DeclContext until we
1390+
// find a CXXMethodDecl.
1391+
const auto *CurrentClassScope = [&]() -> const CXXRecordDecl * {
1392+
for (DeclContext *Ctx = SemaRef.CurContext; Ctx;
1393+
Ctx = Ctx->getParent()) {
1394+
const auto *CtxMethod = llvm::dyn_cast<CXXMethodDecl>(Ctx);
1395+
if (CtxMethod && !CtxMethod->getParent()->isLambda()) {
1396+
return CtxMethod->getParent();
1397+
}
1398+
}
1399+
return nullptr;
1400+
}();
1401+
1402+
R.FunctionCanBeCall =
1403+
CurrentClassScope &&
1404+
(CurrentClassScope == Method->getParent() ||
1405+
CurrentClassScope->isDerivedFrom(Method->getParent()));
1406+
}
1407+
}
1408+
13821409
// Insert this result into the set of results.
13831410
Results.push_back(R);
13841411

clang/unittests/Sema/CodeCompleteTest.cpp

Lines changed: 115 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ namespace {
2323

2424
using namespace clang;
2525
using namespace clang::tooling;
26+
using ::testing::AllOf;
27+
using ::testing::Contains;
2628
using ::testing::Each;
2729
using ::testing::UnorderedElementsAre;
2830

@@ -36,6 +38,51 @@ struct CompletionContext {
3638
std::string PtrDiffType;
3739
};
3840

41+
struct CompletedFunctionDecl {
42+
std::string Name;
43+
bool IsStatic;
44+
bool CanBeCall;
45+
};
46+
MATCHER_P(named, name, "") { return arg.Name == name; }
47+
MATCHER_P(isStatic, value, "") { return arg.IsStatic == value; }
48+
MATCHER_P(canBeCall, value, "") { return arg.CanBeCall == value; }
49+
50+
class SaveCompletedFunctions : public CodeCompleteConsumer {
51+
public:
52+
SaveCompletedFunctions(std::vector<CompletedFunctionDecl> &CompletedFuncDecls)
53+
: CodeCompleteConsumer(/*CodeCompleteOpts=*/{}),
54+
CompletedFuncDecls(CompletedFuncDecls),
55+
CCTUInfo(std::make_shared<GlobalCodeCompletionAllocator>()) {}
56+
57+
void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
58+
CodeCompletionResult *Results,
59+
unsigned NumResults) override {
60+
for (unsigned I = 0; I < NumResults; ++I) {
61+
auto R = Results[I];
62+
if (R.Kind == CodeCompletionResult::RK_Declaration) {
63+
if (const auto *FD = llvm::dyn_cast<FunctionDecl>(R.getDeclaration())) {
64+
CompletedFunctionDecl D;
65+
D.Name = FD->getNameAsString();
66+
D.CanBeCall = R.FunctionCanBeCall;
67+
D.IsStatic = FD->isStatic();
68+
CompletedFuncDecls.emplace_back(std::move(D));
69+
}
70+
}
71+
}
72+
}
73+
74+
private:
75+
CodeCompletionAllocator &getAllocator() override {
76+
return CCTUInfo.getAllocator();
77+
}
78+
79+
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
80+
81+
std::vector<CompletedFunctionDecl> &CompletedFuncDecls;
82+
83+
CodeCompletionTUInfo CCTUInfo;
84+
};
85+
3986
class VisitedContextFinder : public CodeCompleteConsumer {
4087
public:
4188
VisitedContextFinder(CompletionContext &ResultCtx)
@@ -74,19 +121,19 @@ class VisitedContextFinder : public CodeCompleteConsumer {
74121

75122
class CodeCompleteAction : public SyntaxOnlyAction {
76123
public:
77-
CodeCompleteAction(ParsedSourceLocation P, CompletionContext &ResultCtx)
78-
: CompletePosition(std::move(P)), ResultCtx(ResultCtx) {}
124+
CodeCompleteAction(ParsedSourceLocation P, CodeCompleteConsumer *Consumer)
125+
: CompletePosition(std::move(P)), Consumer(Consumer) {}
79126

80127
bool BeginInvocation(CompilerInstance &CI) override {
81128
CI.getFrontendOpts().CodeCompletionAt = CompletePosition;
82-
CI.setCodeCompletionConsumer(new VisitedContextFinder(ResultCtx));
129+
CI.setCodeCompletionConsumer(Consumer);
83130
return true;
84131
}
85132

86133
private:
87134
// 1-based code complete position <Line, Col>;
88135
ParsedSourceLocation CompletePosition;
89-
CompletionContext &ResultCtx;
136+
CodeCompleteConsumer *Consumer;
90137
};
91138

92139
ParsedSourceLocation offsetToPosition(llvm::StringRef Code, size_t Offset) {
@@ -103,7 +150,7 @@ CompletionContext runCompletion(StringRef Code, size_t Offset) {
103150
CompletionContext ResultCtx;
104151
clang::tooling::runToolOnCodeWithArgs(
105152
std::make_unique<CodeCompleteAction>(offsetToPosition(Code, Offset),
106-
ResultCtx),
153+
new VisitedContextFinder(ResultCtx)),
107154
Code, {"-std=c++11"}, TestCCName);
108155
return ResultCtx;
109156
}
@@ -129,6 +176,69 @@ collectPreferredTypes(StringRef AnnotatedCode,
129176
return Types;
130177
}
131178

179+
std::vector<CompletedFunctionDecl>
180+
CollectCompletedFunctions(StringRef Code, std::size_t Point) {
181+
std::vector<CompletedFunctionDecl> Result;
182+
clang::tooling::runToolOnCodeWithArgs(
183+
std::make_unique<CodeCompleteAction>(offsetToPosition(Code, Point),
184+
new SaveCompletedFunctions(Result)),
185+
Code, {"-std=c++11"}, TestCCName);
186+
return Result;
187+
}
188+
189+
TEST(SemaCodeCompleteTest, FunctionCanBeCall) {
190+
llvm::Annotations Code(R"cpp(
191+
struct Foo {
192+
static int staticMethod();
193+
int method() const;
194+
Foo() {
195+
this->$canBeCall^
196+
$canBeCall^
197+
Foo::$canBeCall^
198+
}
199+
};
200+
201+
struct Derived : Foo {
202+
Derived() {
203+
Foo::$canBeCall^
204+
}
205+
};
206+
207+
struct OtherClass {
208+
OtherClass() {
209+
Foo f;
210+
f.$canBeCall^
211+
&Foo::$cannotBeCall^
212+
}
213+
};
214+
215+
int main() {
216+
Foo f;
217+
f.$canBeCall^
218+
&Foo::$cannotBeCall^
219+
}
220+
)cpp");
221+
222+
for (const auto &P : Code.points("canBeCall")) {
223+
auto Results = CollectCompletedFunctions(Code.code(), P);
224+
EXPECT_THAT(Results, Contains(AllOf(named("method"), isStatic(false),
225+
canBeCall(true))));
226+
}
227+
228+
for (const auto &P : Code.points("cannotBeCall")) {
229+
auto Results = CollectCompletedFunctions(Code.code(), P);
230+
EXPECT_THAT(Results, Contains(AllOf(named("method"), isStatic(false),
231+
canBeCall(false))));
232+
}
233+
234+
// static method can always be a call
235+
for (const auto &P : Code.points()) {
236+
auto Results = CollectCompletedFunctions(Code.code(), P);
237+
EXPECT_THAT(Results, Contains(AllOf(named("staticMethod"), isStatic(true),
238+
canBeCall(true))));
239+
}
240+
}
241+
132242
TEST(SemaCodeCompleteTest, VisitedNSForValidQualifiedId) {
133243
auto VisitedNS = runCodeCompleteOnCode(R"cpp(
134244
namespace ns1 {}

0 commit comments

Comments
 (0)