Skip to content

Commit 23ef8bf

Browse files
committed
[clangd][CodeComplete] Improve FunctionCanBeCall
From two aspects: - For function templates, emit additional template argument placeholders in the context where it can't be a call in order to specify an instantiation explicitly. - Consider expressions with base type specifier such as 'Derived().Base::foo^' a function call. Reviewed By: nridge Differential Revision: https://reviews.llvm.org/D156605
1 parent 1b8fb1a commit 23ef8bf

File tree

8 files changed

+245
-55
lines changed

8 files changed

+245
-55
lines changed

clang-tools-extra/clangd/CodeComplete.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -460,9 +460,9 @@ struct CodeCompletionBuilder {
460460
bool IsConcept = false;
461461
if (C.SemaResult) {
462462
getSignature(*SemaCCS, &S.Signature, &S.SnippetSuffix, C.SemaResult->Kind,
463-
C.SemaResult->CursorKind, &Completion.RequiredQualifier);
464-
if (!C.SemaResult->FunctionCanBeCall)
465-
S.SnippetSuffix.clear();
463+
C.SemaResult->CursorKind,
464+
/*IncludeFunctionArguments=*/C.SemaResult->FunctionCanBeCall,
465+
/*RequiredQualifiers=*/&Completion.RequiredQualifier);
466466
S.ReturnType = getReturnType(*SemaCCS);
467467
if (C.SemaResult->Kind == CodeCompletionResult::RK_Declaration)
468468
if (const auto *D = C.SemaResult->getDeclaration())

clang-tools-extra/clangd/CodeCompletionStrings.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "clang/AST/RawCommentList.h"
1313
#include "clang/Basic/SourceManager.h"
1414
#include "clang/Sema/CodeCompleteConsumer.h"
15+
#include "llvm/Support/Compiler.h"
1516
#include "llvm/Support/JSON.h"
1617
#include <limits>
1718
#include <utility>
@@ -118,7 +119,8 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) {
118119
void getSignature(const CodeCompletionString &CCS, std::string *Signature,
119120
std::string *Snippet,
120121
CodeCompletionResult::ResultKind ResultKind,
121-
CXCursorKind CursorKind, std::string *RequiredQualifiers) {
122+
CXCursorKind CursorKind, bool IncludeFunctionArguments,
123+
std::string *RequiredQualifiers) {
122124
// Placeholder with this index will be $0 to mark final cursor position.
123125
// Usually we do not add $0, so the cursor is placed at end of completed text.
124126
unsigned CursorSnippetArg = std::numeric_limits<unsigned>::max();
@@ -138,6 +140,8 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature,
138140
unsigned SnippetArg = 0;
139141
bool HadObjCArguments = false;
140142
bool HadInformativeChunks = false;
143+
144+
std::optional<unsigned> TruncateSnippetAt;
141145
for (const auto &Chunk : CCS) {
142146
// Informative qualifier chunks only clutter completion results, skip
143147
// them.
@@ -243,6 +247,13 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature,
243247
"CompletionItems");
244248
break;
245249
case CodeCompletionString::CK_LeftParen:
250+
// We're assuming that a LeftParen in a declaration starts a function
251+
// call, and arguments following the parenthesis could be discarded if
252+
// IncludeFunctionArguments is false.
253+
if (!IncludeFunctionArguments &&
254+
ResultKind == CodeCompletionResult::RK_Declaration)
255+
TruncateSnippetAt.emplace(Snippet->size());
256+
LLVM_FALLTHROUGH;
246257
case CodeCompletionString::CK_RightParen:
247258
case CodeCompletionString::CK_LeftBracket:
248259
case CodeCompletionString::CK_RightBracket:
@@ -264,6 +275,8 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature,
264275
break;
265276
}
266277
}
278+
if (TruncateSnippetAt)
279+
*Snippet = Snippet->substr(0, *TruncateSnippetAt);
267280
}
268281

269282
std::string formatDocumentation(const CodeCompletionString &CCS,

clang-tools-extra/clangd/CodeCompletionStrings.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,17 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &D);
4242
/// If set, RequiredQualifiers is the text that must be typed before the name.
4343
/// e.g "Base::" when calling a base class member function that's hidden.
4444
///
45+
/// If \p IncludeFunctionArguments is disabled, the \p Snippet will only
46+
/// contain function name and template arguments, if any.
47+
///
4548
/// When \p ResultKind is RK_Pattern, the last placeholder will be $0,
4649
/// indicating the cursor should stay there.
4750
/// Note that for certain \p CursorKind like \p CXCursor_Constructor, $0 won't
4851
/// be emitted in order to avoid overlapping normal parameters.
4952
void getSignature(const CodeCompletionString &CCS, std::string *Signature,
5053
std::string *Snippet,
5154
CodeCompletionResult::ResultKind ResultKind,
52-
CXCursorKind CursorKind,
55+
CXCursorKind CursorKind, bool IncludeFunctionArguments = true,
5356
std::string *RequiredQualifiers = nullptr);
5457

5558
/// Assembles formatted documentation for a completion result. This includes

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

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -530,54 +530,92 @@ TEST(CompletionTest, HeuristicsForMemberFunctionCompletion) {
530530

531531
Annotations Code(R"cpp(
532532
struct Foo {
533-
static int staticMethod();
534-
int method() const;
533+
static int staticMethod(int);
534+
int method(int) const;
535+
template <typename T, typename U, typename V = int>
536+
T generic(U, V);
537+
template <typename T, int U>
538+
static T staticGeneric();
535539
Foo() {
536-
this->$keepSnippet^
537-
$keepSnippet^
538-
Foo::$keepSnippet^
540+
this->$canBeCall^
541+
$canBeCall^
542+
Foo::$canBeCall^
539543
}
540544
};
541545
542546
struct Derived : Foo {
547+
using Foo::method;
548+
using Foo::generic;
543549
Derived() {
544-
Foo::$keepSnippet^
550+
Foo::$canBeCall^
545551
}
546552
};
547553
548554
struct OtherClass {
549555
OtherClass() {
550556
Foo f;
551-
f.$keepSnippet^
552-
&Foo::$noSnippet^
557+
Derived d;
558+
f.$canBeCall^
559+
; // Prevent parsing as 'f.f'
560+
f.Foo::$canBeCall^
561+
&Foo::$canNotBeCall^
562+
;
563+
d.Foo::$canBeCall^
564+
;
565+
d.Derived::$canBeCall^
553566
}
554567
};
555568
556569
int main() {
557570
Foo f;
558-
f.$keepSnippet^
559-
&Foo::$noSnippet^
571+
Derived d;
572+
f.$canBeCall^
573+
; // Prevent parsing as 'f.f'
574+
f.Foo::$canBeCall^
575+
&Foo::$canNotBeCall^
576+
;
577+
d.Foo::$canBeCall^
578+
;
579+
d.Derived::$canBeCall^
560580
}
561581
)cpp");
562582
auto TU = TestTU::withCode(Code.code());
563583

564-
for (const auto &P : Code.points("noSnippet")) {
584+
for (const auto &P : Code.points("canNotBeCall")) {
565585
auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts);
566586
EXPECT_THAT(Results.Completions,
567-
Contains(AllOf(named("method"), snippetSuffix(""))));
587+
Contains(AllOf(named("method"), signature("(int) const"),
588+
snippetSuffix(""))));
589+
// We don't have any arguments to deduce against if this isn't a call.
590+
// Thus, we should emit these deducible template arguments explicitly.
591+
EXPECT_THAT(
592+
Results.Completions,
593+
Contains(AllOf(named("generic"),
594+
signature("<typename T, typename U>(U, V)"),
595+
snippetSuffix("<${1:typename T}, ${2:typename U}>"))));
568596
}
569597

570-
for (const auto &P : Code.points("keepSnippet")) {
598+
for (const auto &P : Code.points("canBeCall")) {
571599
auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts);
572600
EXPECT_THAT(Results.Completions,
573-
Contains(AllOf(named("method"), snippetSuffix("()"))));
601+
Contains(AllOf(named("method"), signature("(int) const"),
602+
snippetSuffix("(${1:int})"))));
603+
EXPECT_THAT(
604+
Results.Completions,
605+
Contains(AllOf(named("generic"), signature("<typename T>(U, V)"),
606+
snippetSuffix("<${1:typename T}>(${2:U}, ${3:V})"))));
574607
}
575608

576609
// static method will always keep the snippet
577610
for (const auto &P : Code.points()) {
578611
auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts);
579612
EXPECT_THAT(Results.Completions,
580-
Contains(AllOf(named("staticMethod"), snippetSuffix("()"))));
613+
Contains(AllOf(named("staticMethod"), signature("(int)"),
614+
snippetSuffix("(${1:int})"))));
615+
EXPECT_THAT(Results.Completions,
616+
Contains(AllOf(
617+
named("staticGeneric"), signature("<typename T, int U>()"),
618+
snippetSuffix("<${1:typename T}, ${2:int U}>()"))));
581619
}
582620
}
583621

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ class CompletionStringTest : public ::testing::Test {
2525
protected:
2626
void computeSignature(const CodeCompletionString &CCS,
2727
CodeCompletionResult::ResultKind ResultKind =
28-
CodeCompletionResult::ResultKind::RK_Declaration) {
28+
CodeCompletionResult::ResultKind::RK_Declaration,
29+
bool IncludeFunctionArguments = true) {
2930
Signature.clear();
3031
Snippet.clear();
3132
getSignature(CCS, &Signature, &Snippet, ResultKind,
3233
/*CursorKind=*/CXCursorKind::CXCursor_NotImplemented,
34+
/*IncludeFunctionArguments=*/IncludeFunctionArguments,
3335
/*RequiredQualifiers=*/nullptr);
3436
}
3537

@@ -156,6 +158,28 @@ TEST_F(CompletionStringTest, SnippetsInPatterns) {
156158
EXPECT_EQ(Snippet, " ${1:name} = $0;");
157159
}
158160

161+
TEST_F(CompletionStringTest, DropFunctionArguments) {
162+
Builder.AddTypedTextChunk("foo");
163+
Builder.AddChunk(CodeCompletionString::CK_LeftAngle);
164+
Builder.AddPlaceholderChunk("typename T");
165+
Builder.AddChunk(CodeCompletionString::CK_Comma);
166+
Builder.AddPlaceholderChunk("int U");
167+
Builder.AddChunk(CodeCompletionString::CK_RightAngle);
168+
Builder.AddChunk(CodeCompletionString::CK_LeftParen);
169+
Builder.AddPlaceholderChunk("arg1");
170+
Builder.AddChunk(CodeCompletionString::CK_Comma);
171+
Builder.AddPlaceholderChunk("arg2");
172+
Builder.AddChunk(CodeCompletionString::CK_RightParen);
173+
174+
computeSignature(
175+
*Builder.TakeString(),
176+
/*ResultKind=*/CodeCompletionResult::ResultKind::RK_Declaration,
177+
/*IncludeFunctionArguments=*/false);
178+
// Arguments dropped from snippet, kept in signature.
179+
EXPECT_EQ(Signature, "<typename T, int U>(arg1, arg2)");
180+
EXPECT_EQ(Snippet, "<${1:typename T}, ${2:int U}>");
181+
}
182+
159183
TEST_F(CompletionStringTest, IgnoreInformativeQualifier) {
160184
Builder.AddTypedTextChunk("X");
161185
Builder.AddInformativeChunk("info ok");

0 commit comments

Comments
 (0)