[clang] [clang-tools-extra] [clangd] Autocomplete fixes for methods (PR #165916)
Hippolyte Melica via cfe-commits
cfe-commits at lists.llvm.org
Wed Feb 18 03:07:19 PST 2026
https://github.com/etyloppihacilem updated https://github.com/llvm/llvm-project/pull/165916
>From 2503e81717f504fc5a3db4bc6fd6b3e55704a491 Mon Sep 17 00:00:00 2001
From: Hippolyte Melica <hippolytemelica at gmail.com>
Date: Fri, 31 Oct 2025 12:08:07 +0100
Subject: [PATCH] [clangd] Autocomplete fixes for methods and arguments
---
clang-tools-extra/clangd/CodeComplete.cpp | 1 +
.../clangd/CodeCompletionStrings.cpp | 84 ++++++++++-----
.../clangd/CodeCompletionStrings.h | 1 +
.../clangd/unittests/CodeCompleteTests.cpp | 100 ++++++++++++++----
.../unittests/CodeCompletionStringsTests.cpp | 28 ++++-
clang/include/clang/Parse/Parser.h | 26 ++++-
.../include/clang/Sema/CodeCompleteConsumer.h | 14 ++-
clang/include/clang/Sema/SemaCodeCompletion.h | 10 +-
clang/lib/Parse/ParseExpr.cpp | 5 +-
clang/lib/Parse/ParseExprCXX.cpp | 5 +-
clang/lib/Parse/Parser.cpp | 10 +-
clang/lib/Sema/CodeCompleteConsumer.cpp | 13 +++
clang/lib/Sema/SemaCodeComplete.cpp | 84 +++++++++++----
clang/tools/libclang/CIndexCodeCompletion.cpp | 4 +
14 files changed, 301 insertions(+), 84 deletions(-)
diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp
index 7c390f9c8219d..1556820da1766 100644
--- a/clang-tools-extra/clangd/CodeComplete.cpp
+++ b/clang-tools-extra/clangd/CodeComplete.cpp
@@ -480,6 +480,7 @@ struct CodeCompletionBuilder {
getSignature(*SemaCCS, &S.Signature, &S.SnippetSuffix, C.SemaResult->Kind,
C.SemaResult->CursorKind,
/*IncludeFunctionArguments=*/C.SemaResult->FunctionCanBeCall,
+ /*IsDefinition=*/C.SemaResult->DeclaringEntity,
/*RequiredQualifiers=*/&Completion.RequiredQualifier);
S.ReturnType = getReturnType(*SemaCCS);
if (C.SemaResult->Kind == CodeCompletionResult::RK_Declaration)
diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp
index 9c4241b54057a..d863a5ab973fd 100644
--- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp
+++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp
@@ -39,16 +39,29 @@ void appendEscapeSnippet(const llvm::StringRef Text, std::string *Out) {
}
}
-void appendOptionalChunk(const CodeCompletionString &CCS, std::string *Out) {
+/// Removes the value for defaults arguments.
+static void addWithoutValue(std::string *Out, const std::string &ToAdd) {
+ size_t Val = ToAdd.find('=');
+ if (Val != ToAdd.npos)
+ *Out += ToAdd.substr(0, Val - 1); // removing value in definition
+ else
+ *Out += ToAdd;
+}
+
+void appendOptionalChunk(const CodeCompletionString &CCS, std::string *Out,
+ bool RemoveValues = false) {
for (const CodeCompletionString::Chunk &C : CCS) {
switch (C.Kind) {
case CodeCompletionString::CK_Optional:
assert(C.Optional &&
"Expected the optional code completion string to be non-null.");
- appendOptionalChunk(*C.Optional, Out);
+ appendOptionalChunk(*C.Optional, Out, RemoveValues);
break;
default:
- *Out += C.Text;
+ if (RemoveValues)
+ addWithoutValue(Out, C.Text);
+ else
+ *Out += C.Text;
break;
}
}
@@ -164,7 +177,7 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature,
std::string *Snippet,
CodeCompletionResult::ResultKind ResultKind,
CXCursorKind CursorKind, bool IncludeFunctionArguments,
- std::string *RequiredQualifiers) {
+ bool IsDefinition, std::string *RequiredQualifiers) {
// Placeholder with this index will be $0 to mark final cursor position.
// Usually we do not add $0, so the cursor is placed at end of completed text.
unsigned CursorSnippetArg = std::numeric_limits<unsigned>::max();
@@ -184,8 +197,8 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature,
unsigned SnippetArg = 0;
bool HadObjCArguments = false;
bool HadInformativeChunks = false;
+ int IsTemplateArgument = 0;
- std::optional<unsigned> TruncateSnippetAt;
for (const auto &Chunk : CCS) {
// Informative qualifier chunks only clutter completion results, skip
// them.
@@ -252,26 +265,39 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature,
}
}
break;
- case CodeCompletionString::CK_Text:
+ case CodeCompletionString::CK_FunctionQualifier:
+ if (IsDefinition) // Only for definition
+ *Snippet += Chunk.Text;
*Signature += Chunk.Text;
+ break;
+ case CodeCompletionString::CK_Text:
*Snippet += Chunk.Text;
+ *Signature += Chunk.Text;
break;
case CodeCompletionString::CK_Optional:
assert(Chunk.Optional);
// No need to create placeholders for default arguments in Snippet.
appendOptionalChunk(*Chunk.Optional, Signature);
+ // complete args without default value in definition
+ if (IsDefinition)
+ appendOptionalChunk(*Chunk.Optional, Snippet, /*RemoveValues=*/true);
break;
case CodeCompletionString::CK_Placeholder:
*Signature += Chunk.Text;
- ++SnippetArg;
- if (SnippetArg == CursorSnippetArg) {
- // We'd like to make $0 a placeholder too, but vscode does not support
- // this (https://github.com/microsoft/vscode/issues/152837).
- *Snippet += "$0";
- } else {
- *Snippet += "${" + std::to_string(SnippetArg) + ':';
- appendEscapeSnippet(Chunk.Text, Snippet);
- *Snippet += '}';
+ if (IncludeFunctionArguments || (IsTemplateArgument && !IsDefinition)) {
+ ++SnippetArg;
+ if (SnippetArg == CursorSnippetArg) {
+ // We'd like to make $0 a placeholder too, but vscode does not support
+ // this (https://github.com/microsoft/vscode/issues/152837).
+ *Snippet += "$0";
+ } else {
+ *Snippet += "${" + std::to_string(SnippetArg) + ':';
+ appendEscapeSnippet(Chunk.Text, Snippet);
+ *Snippet += '}';
+ }
+ } else if (IsDefinition && // completion without snippets
+ !IsTemplateArgument) { // no template arguments in definition
+ *Snippet += Chunk.Text;
}
break;
case CodeCompletionString::CK_Informative:
@@ -290,28 +316,34 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature,
llvm_unreachable("Unexpected CK_CurrentParameter while collecting "
"CompletionItems");
break;
+ case CodeCompletionString::CK_LeftAngle:
+ // Do not add template arguments in address of a function
+ IsTemplateArgument++;
+ if (!IsDefinition)
+ *Snippet += Chunk.Text;
+ *Signature += Chunk.Text;
+ break;
+ case CodeCompletionString::CK_RightAngle:
+ IsTemplateArgument--;
+ if (!IsDefinition)
+ *Snippet += Chunk.Text;
+ *Signature += Chunk.Text;
+ break;
case CodeCompletionString::CK_LeftParen:
- // We're assuming that a LeftParen in a declaration starts a function
- // call, and arguments following the parenthesis could be discarded if
- // IncludeFunctionArguments is false.
- if (!IncludeFunctionArguments &&
- ResultKind == CodeCompletionResult::RK_Declaration)
- TruncateSnippetAt.emplace(Snippet->size());
- [[fallthrough]];
case CodeCompletionString::CK_RightParen:
case CodeCompletionString::CK_LeftBracket:
case CodeCompletionString::CK_RightBracket:
case CodeCompletionString::CK_LeftBrace:
case CodeCompletionString::CK_RightBrace:
- case CodeCompletionString::CK_LeftAngle:
- case CodeCompletionString::CK_RightAngle:
case CodeCompletionString::CK_Comma:
case CodeCompletionString::CK_Colon:
case CodeCompletionString::CK_SemiColon:
case CodeCompletionString::CK_Equal:
case CodeCompletionString::CK_HorizontalSpace:
*Signature += Chunk.Text;
- *Snippet += Chunk.Text;
+ if (IncludeFunctionArguments ||
+ (IsDefinition != static_cast<bool>(IsTemplateArgument)))
+ *Snippet += Chunk.Text;
break;
case CodeCompletionString::CK_VerticalSpace:
*Snippet += Chunk.Text;
@@ -319,8 +351,6 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature,
break;
}
}
- if (TruncateSnippetAt)
- *Snippet = Snippet->substr(0, *TruncateSnippetAt);
}
std::string formatDocumentation(const CodeCompletionString &CCS,
diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.h b/clang-tools-extra/clangd/CodeCompletionStrings.h
index fa81ad64d406c..baad1346c4c20 100644
--- a/clang-tools-extra/clangd/CodeCompletionStrings.h
+++ b/clang-tools-extra/clangd/CodeCompletionStrings.h
@@ -53,6 +53,7 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature,
std::string *Snippet,
CodeCompletionResult::ResultKind ResultKind,
CXCursorKind CursorKind, bool IncludeFunctionArguments = true,
+ bool IsDefinition = false,
std::string *RequiredQualifiers = nullptr);
/// Assembles formatted documentation for a completion result. This includes
diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
index 31f2d8bd68703..e3763b53b1d07 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -531,19 +531,23 @@ TEST(CompletionTest, HeuristicsForMemberFunctionCompletion) {
Annotations Code(R"cpp(
struct Foo {
- static int staticMethod(int);
- int method(int) const;
+ static int staticMethod(int name);
+ int method(int name) const;
template <typename T, typename U, typename V = int>
- T generic(U, V);
+ T generic(U nameU, V nameV);
template <typename T, int U>
static T staticGeneric();
Foo() {
- this->$canBeCall^
+ this->$canBeCallNoStatic^
$canBeCall^
Foo::$canBeCall^
}
};
+ int Foo::$isDefinition^ {
+ }
+ ;
+
struct Derived : Foo {
using Foo::method;
using Foo::generic;
@@ -556,9 +560,10 @@ TEST(CompletionTest, HeuristicsForMemberFunctionCompletion) {
OtherClass() {
Foo f;
Derived d;
- f.$canBeCall^
+ f.$canBeCallNoStatic^
; // Prevent parsing as 'f.f'
f.Foo::$canBeCall^
+ ;
&Foo::$canNotBeCall^
;
d.Foo::$canBeCall^
@@ -573,6 +578,7 @@ TEST(CompletionTest, HeuristicsForMemberFunctionCompletion) {
f.$canBeCall^
; // Prevent parsing as 'f.f'
f.Foo::$canBeCall^
+ ;
&Foo::$canNotBeCall^
;
d.Foo::$canBeCall^
@@ -585,39 +591,93 @@ TEST(CompletionTest, HeuristicsForMemberFunctionCompletion) {
for (const auto &P : Code.points("canNotBeCall")) {
auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts);
EXPECT_THAT(Results.Completions,
- Contains(AllOf(named("method"), signature("(int) const"),
+ Contains(AllOf(named("method"), signature("(int name) const"),
snippetSuffix(""))));
- // We don't have any arguments to deduce against if this isn't a call.
- // Thus, we should emit these deducible template arguments explicitly.
EXPECT_THAT(
Results.Completions,
Contains(AllOf(named("generic"),
- signature("<typename T, typename U>(U, V)"),
+ signature("<typename T, typename U>(U nameU, V nameV)"),
snippetSuffix("<${1:typename T}, ${2:typename U}>"))));
+ EXPECT_THAT(Results.Completions,
+ Contains(AllOf(named("staticMethod"), signature("(int name)"),
+ snippetSuffix(""))));
+ EXPECT_THAT(Results.Completions,
+ Contains(AllOf(
+ named("staticGeneric"), signature("<typename T, int U>()"),
+ snippetSuffix("<${1:typename T}, ${2:int U}>"))));
}
for (const auto &P : Code.points("canBeCall")) {
auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts);
EXPECT_THAT(Results.Completions,
- Contains(AllOf(named("method"), signature("(int) const"),
- snippetSuffix("(${1:int})"))));
+ Contains(AllOf(named("method"), signature("(int name) const"),
+ snippetSuffix("(${1:int name})"))));
EXPECT_THAT(
Results.Completions,
- Contains(AllOf(named("generic"), signature("<typename T>(U, V)"),
- snippetSuffix("<${1:typename T}>(${2:U}, ${3:V})"))));
- }
-
- // static method will always keep the snippet
- for (const auto &P : Code.points()) {
- auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts);
+ Contains(AllOf(
+ named("generic"), signature("<typename T>(U nameU, V nameV)"),
+ snippetSuffix("<${1:typename T}>(${2:U nameU}, ${3:V nameV})"))));
EXPECT_THAT(Results.Completions,
- Contains(AllOf(named("staticMethod"), signature("(int)"),
- snippetSuffix("(${1:int})"))));
+ Contains(AllOf(named("staticMethod"), signature("(int name)"),
+ snippetSuffix("(${1:int name})"))));
EXPECT_THAT(Results.Completions,
Contains(AllOf(
named("staticGeneric"), signature("<typename T, int U>()"),
snippetSuffix("<${1:typename T}, ${2:int U}>()"))));
}
+
+ for (const auto &P : Code.points("canBeCallNoStatic")) {
+ auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts);
+ EXPECT_THAT(Results.Completions,
+ Contains(AllOf(named("method"), signature("(int name) const"),
+ snippetSuffix("(${1:int name})"))));
+ EXPECT_THAT(
+ Results.Completions,
+ Contains(AllOf(
+ named("generic"), signature("<typename T>(U nameU, V nameV)"),
+ snippetSuffix("<${1:typename T}>(${2:U nameU}, ${3:V nameV})"))));
+ }
+
+ for (const auto &P : Code.points("isDefinition")) {
+ auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts);
+
+ EXPECT_THAT(Results.Completions,
+ Contains(AllOf(named("method"), signature("(int name) const"),
+ snippetSuffix("(int name) const"))));
+ EXPECT_THAT(
+ Results.Completions,
+ Contains(AllOf(named("generic"),
+ signature("<typename T, typename U>(U nameU, V nameV)"),
+ snippetSuffix("(U nameU, V nameV)"))));
+ EXPECT_THAT(Results.Completions,
+ Contains(AllOf(named("staticMethod"), signature("(int name)"),
+ snippetSuffix("(int name)"))));
+ EXPECT_THAT(Results.Completions,
+ Contains(AllOf(named("staticGeneric"),
+ signature("<typename T, int U>()"),
+ snippetSuffix("()"))));
+ }
+}
+
+TEST(CompletionTest, DefaultArgsWithValues) {
+ clangd::CodeCompleteOptions Opts;
+ Opts.EnableSnippets = true;
+ auto Results = completions(
+ R"cpp(
+ struct Arg {
+ Arg(int a, int b);
+ };
+ struct Foo {
+ void foo(int x = 42, int y = 0, Arg arg = Arg(42, 0));
+ };
+ void Foo::foo^
+ )cpp",
+ /*IndexSymbols=*/{}, Opts);
+ EXPECT_THAT(Results.Completions,
+ Contains(AllOf(
+ named("foo"),
+ signature("(int x = 42, int y = 0, Arg arg = Arg(42, 0))"),
+ snippetSuffix("(int x, int y, Arg arg)"))));
}
TEST(CompletionTest, NoSnippetsInUsings) {
diff --git a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
index de5f533d31645..23ab3226d7b71 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp
@@ -26,12 +26,14 @@ class CompletionStringTest : public ::testing::Test {
void computeSignature(const CodeCompletionString &CCS,
CodeCompletionResult::ResultKind ResultKind =
CodeCompletionResult::ResultKind::RK_Declaration,
- bool IncludeFunctionArguments = true) {
+ bool IncludeFunctionArguments = true,
+ bool IsDefinition = false) {
Signature.clear();
Snippet.clear();
getSignature(CCS, &Signature, &Snippet, ResultKind,
/*CursorKind=*/CXCursorKind::CXCursor_NotImplemented,
/*IncludeFunctionArguments=*/IncludeFunctionArguments,
+ /*IsDefinition=*/IsDefinition,
/*RequiredQualifiers=*/nullptr);
}
@@ -158,6 +160,28 @@ TEST_F(CompletionStringTest, SnippetsInPatterns) {
EXPECT_EQ(Snippet, " ${1:name} = $0;");
}
+TEST_F(CompletionStringTest, DropFunctionPlaceholders) {
+ Builder.AddTypedTextChunk("foo");
+ Builder.AddChunk(CodeCompletionString::CK_LeftAngle);
+ Builder.AddPlaceholderChunk("typename T");
+ Builder.AddChunk(CodeCompletionString::CK_Comma);
+ Builder.AddPlaceholderChunk("int U");
+ Builder.AddChunk(CodeCompletionString::CK_RightAngle);
+ Builder.AddChunk(CodeCompletionString::CK_LeftParen);
+ Builder.AddPlaceholderChunk("arg1");
+ Builder.AddChunk(CodeCompletionString::CK_Comma);
+ Builder.AddPlaceholderChunk("arg2");
+ Builder.AddChunk(CodeCompletionString::CK_RightParen);
+
+ computeSignature(
+ *Builder.TakeString(),
+ /*ResultKind=*/CodeCompletionResult::ResultKind::RK_Declaration,
+ /*IncludeFunctionArguments=*/false, /*IsDefinition=*/true);
+ // Arguments placeholders dropped from snippet, kept in signature.
+ EXPECT_EQ(Signature, "<typename T, int U>(arg1, arg2)");
+ EXPECT_EQ(Snippet, "(arg1, arg2)");
+}
+
TEST_F(CompletionStringTest, DropFunctionArguments) {
Builder.AddTypedTextChunk("foo");
Builder.AddChunk(CodeCompletionString::CK_LeftAngle);
@@ -174,7 +198,7 @@ TEST_F(CompletionStringTest, DropFunctionArguments) {
computeSignature(
*Builder.TakeString(),
/*ResultKind=*/CodeCompletionResult::ResultKind::RK_Declaration,
- /*IncludeFunctionArguments=*/false);
+ /*IncludeFunctionArguments=*/false, /*IsDefinition=*/false);
// Arguments dropped from snippet, kept in signature.
EXPECT_EQ(Signature, "<typename T, int U>(arg1, arg2)");
EXPECT_EQ(Snippet, "<${1:typename T}, ${2:int U}>");
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 5ae02e2b4e8ad..c34e3d01785b1 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -356,7 +356,14 @@ class Parser : public CodeCompletionHandler {
/// are invalid.
bool
TryAnnotateTypeOrScopeToken(ImplicitTypenameContext AllowImplicitTypename =
- ImplicitTypenameContext::No);
+ ImplicitTypenameContext::No,
+ bool isAddressOfOperand = false);
+
+ bool TryAnnotateTypeOrScopeToken(bool isAddressOfOperand) {
+ return TryAnnotateTypeOrScopeToken(
+ /*AllowImplicitTypename=*/ImplicitTypenameContext::No,
+ /*isAddressOfOperand=*/isAddressOfOperand);
+ }
/// Try to annotate a type or scope token, having already parsed an
/// optional scope specifier. \p IsNewScope should be \c true unless the scope
@@ -4568,7 +4575,22 @@ class Parser : public CodeCompletionHandler {
bool EnteringContext, bool *MayBePseudoDestructor = nullptr,
bool IsTypename = false, const IdentifierInfo **LastII = nullptr,
bool OnlyNamespace = false, bool InUsingDeclaration = false,
- bool Disambiguation = false);
+ bool Disambiguation = false, bool isAddressOfOperand = false);
+
+ bool ParseOptionalCXXScopeSpecifier(CXXScopeSpec &SS, ParsedType ObjectType,
+ bool ObjectHasErrors,
+ bool EnteringContext,
+ bool isAddressOfOperand) {
+ return ParseOptionalCXXScopeSpecifier(
+ SS, ObjectType, ObjectHasErrors, EnteringContext,
+ /*MayBePseudoDestructor=*/nullptr,
+ /*IsTypename=*/false,
+ /*LastII=*/nullptr,
+ /*OnlyNamespace=*/false,
+ /*InUsingDeclaration=*/false,
+ /*Disambiguation=*/false,
+ /*isAddressOfOperand=*/isAddressOfOperand);
+ }
//===--------------------------------------------------------------------===//
// C++11 5.1.2: Lambda expressions
diff --git a/clang/include/clang/Sema/CodeCompleteConsumer.h b/clang/include/clang/Sema/CodeCompleteConsumer.h
index c26f4e33d289c..37821bfb51cda 100644
--- a/clang/include/clang/Sema/CodeCompleteConsumer.h
+++ b/clang/include/clang/Sema/CodeCompleteConsumer.h
@@ -473,6 +473,12 @@ class CodeCompletionString {
/// A piece of text that describes something about the result but
/// should not be inserted into the buffer.
CK_Informative,
+
+ /// A piece of text that holds function qualifiers. Should be inserted
+ /// into the buffer only in definition, not on call.
+ /// e.g. const for a function or a method.
+ CK_FunctionQualifier,
+
/// A piece of text that describes the type of an entity or, for
/// functions and methods, the return type.
CK_ResultType,
@@ -534,7 +540,7 @@ class CodeCompletionString {
union {
/// The text string associated with a CK_Text, CK_Placeholder,
- /// CK_Informative, or CK_Comma chunk.
+ /// CK_Informative, CK_FunctionQualifier, or CK_Comma chunk.
/// The string is owned by the chunk and will be deallocated
/// (with delete[]) when the chunk is destroyed.
const char *Text;
@@ -561,6 +567,9 @@ class CodeCompletionString {
/// Create a new informative chunk.
static Chunk CreateInformative(const char *Informative);
+ /// Create a new declaration informative chunk.
+ static Chunk CreateFunctionQualifier(const char *FunctionQualifier);
+
/// Create a new result type chunk.
static Chunk CreateResultType(const char *ResultType);
@@ -737,6 +746,9 @@ class CodeCompletionBuilder {
/// Add a new informative chunk.
void AddInformativeChunk(const char *Text);
+ /// Add a new function qualifier chunk.
+ void AddFunctionQualifierChunk(const char *Text);
+
/// Add a new result-type chunk.
void AddResultTypeChunk(const char *ResultType);
diff --git a/clang/include/clang/Sema/SemaCodeCompletion.h b/clang/include/clang/Sema/SemaCodeCompletion.h
index 3029e56e5cfe2..99888caad2d0c 100644
--- a/clang/include/clang/Sema/SemaCodeCompletion.h
+++ b/clang/include/clang/Sema/SemaCodeCompletion.h
@@ -101,9 +101,11 @@ class SemaCodeCompletion : public SemaBase {
bool AllowNestedNameSpecifiers);
struct CodeCompleteExpressionData;
- void CodeCompleteExpression(Scope *S, const CodeCompleteExpressionData &Data);
+ void CodeCompleteExpression(Scope *S, const CodeCompleteExpressionData &Data,
+ bool IsAddressOfOperand = false);
void CodeCompleteExpression(Scope *S, QualType PreferredType,
- bool IsParenthesized = false);
+ bool IsParenthesized = false,
+ bool IsAddressOfOperand = false);
void CodeCompleteMemberReferenceExpr(Scope *S, Expr *Base, Expr *OtherOpBase,
SourceLocation OpLoc, bool IsArrow,
bool IsBaseExprStatement,
@@ -156,8 +158,8 @@ class SemaCodeCompletion : public SemaBase {
void CodeCompleteAfterIf(Scope *S, bool IsBracedThen);
void CodeCompleteQualifiedId(Scope *S, CXXScopeSpec &SS, bool EnteringContext,
- bool IsUsingDeclaration, QualType BaseType,
- QualType PreferredType);
+ bool IsUsingDeclaration, bool IsAddressOfOperand,
+ QualType BaseType, QualType PreferredType);
void CodeCompleteUsing(Scope *S);
void CodeCompleteUsingDirective(Scope *S);
void CodeCompleteNamespaceDecl(Scope *S);
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index be6c7824cdbae..9c4dfe83fd622 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -923,7 +923,7 @@ Parser::ParseCastExpression(CastParseKind ParseKind, bool isAddressOfOperand,
Next.isOneOf(tok::coloncolon, tok::less, tok::l_paren,
tok::l_brace)) {
// If TryAnnotateTypeOrScopeToken annotates the token, tail recurse.
- if (TryAnnotateTypeOrScopeToken())
+ if (TryAnnotateTypeOrScopeToken(isAddressOfOperand))
return ExprError();
if (!Tok.is(tok::identifier))
return ParseCastExpression(ParseKind, isAddressOfOperand, NotCastExpr,
@@ -1560,7 +1560,8 @@ Parser::ParseCastExpression(CastParseKind ParseKind, bool isAddressOfOperand,
case tok::code_completion: {
cutOffParsing();
Actions.CodeCompletion().CodeCompleteExpression(
- getCurScope(), PreferredType.get(Tok.getLocation()));
+ getCurScope(), PreferredType.get(Tok.getLocation()),
+ /*IsParenthesized=*/false, /*IsAddressOfOperand=*/isAddressOfOperand);
return ExprError();
}
#define TRANSFORM_TYPE_TRAIT_DEF(_, Trait) case tok::kw___##Trait:
diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp
index 842b52375eb14..8f80ceee49d31 100644
--- a/clang/lib/Parse/ParseExprCXX.cpp
+++ b/clang/lib/Parse/ParseExprCXX.cpp
@@ -108,7 +108,7 @@ bool Parser::ParseOptionalCXXScopeSpecifier(
CXXScopeSpec &SS, ParsedType ObjectType, bool ObjectHadErrors,
bool EnteringContext, bool *MayBePseudoDestructor, bool IsTypename,
const IdentifierInfo **LastII, bool OnlyNamespace, bool InUsingDeclaration,
- bool Disambiguation) {
+ bool Disambiguation, bool isAddressOfOperand) {
assert(getLangOpts().CPlusPlus &&
"Call sites of this function should be guarded by checking for C++");
@@ -237,7 +237,8 @@ bool Parser::ParseOptionalCXXScopeSpecifier(
// completion token follows the '::'.
Actions.CodeCompletion().CodeCompleteQualifiedId(
getCurScope(), SS, EnteringContext, InUsingDeclaration,
- ObjectType.get(), SavedType.get(SS.getBeginLoc()));
+ isAddressOfOperand, ObjectType.get(),
+ SavedType.get(SS.getBeginLoc()));
// Include code completion token into the range of the scope otherwise
// when we try to annotate the scope tokens the dangling code completion
// token will cause assertion in
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index 5d18414b1a746..365aaf8e7d907 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -1853,7 +1853,7 @@ bool Parser::TryKeywordIdentFallback(bool DisableKeyword) {
}
bool Parser::TryAnnotateTypeOrScopeToken(
- ImplicitTypenameContext AllowImplicitTypename) {
+ ImplicitTypenameContext AllowImplicitTypename, bool isAddressOfOperand) {
assert((Tok.is(tok::identifier) || Tok.is(tok::coloncolon) ||
Tok.is(tok::kw_typename) || Tok.is(tok::annot_cxxscope) ||
Tok.is(tok::kw_decltype) || Tok.is(tok::annot_template_id) ||
@@ -1969,9 +1969,11 @@ bool Parser::TryAnnotateTypeOrScopeToken(
CXXScopeSpec SS;
if (getLangOpts().CPlusPlus)
- if (ParseOptionalCXXScopeSpecifier(SS, /*ObjectType=*/nullptr,
- /*ObjectHasErrors=*/false,
- /*EnteringContext*/ false))
+ if (ParseOptionalCXXScopeSpecifier(
+ SS, /*ObjectType=*/nullptr,
+ /*ObjectHasErrors=*/false,
+ /*EnteringContext*/ false,
+ /*isAddressOfOperand=*/isAddressOfOperand))
return true;
return TryAnnotateTypeOrScopeTokenAfterScopeSpec(SS, !WasScopeAnnotation,
diff --git a/clang/lib/Sema/CodeCompleteConsumer.cpp b/clang/lib/Sema/CodeCompleteConsumer.cpp
index 50a552272f421..0a445b8f6f590 100644
--- a/clang/lib/Sema/CodeCompleteConsumer.cpp
+++ b/clang/lib/Sema/CodeCompleteConsumer.cpp
@@ -183,6 +183,7 @@ CodeCompletionString::Chunk::Chunk(ChunkKind Kind, const char *Text)
case CK_Text:
case CK_Placeholder:
case CK_Informative:
+ case CK_FunctionQualifier:
case CK_ResultType:
case CK_CurrentParameter:
this->Text = Text;
@@ -272,6 +273,12 @@ CodeCompletionString::Chunk::CreateInformative(const char *Informative) {
return Chunk(CK_Informative, Informative);
}
+CodeCompletionString::Chunk
+CodeCompletionString::Chunk::CreateFunctionQualifier(
+ const char *FunctionQualifier) {
+ return Chunk(CK_FunctionQualifier, FunctionQualifier);
+}
+
CodeCompletionString::Chunk
CodeCompletionString::Chunk::CreateResultType(const char *ResultType) {
return Chunk(CK_ResultType, ResultType);
@@ -326,6 +333,7 @@ std::string CodeCompletionString::getAsString() const {
OS << "<#" << C.Text << "#>";
break;
case CK_Informative:
+ case CK_FunctionQualifier:
case CK_ResultType:
OS << "[#" << C.Text << "#]";
break;
@@ -461,6 +469,10 @@ void CodeCompletionBuilder::AddInformativeChunk(const char *Text) {
Chunks.push_back(Chunk::CreateInformative(Text));
}
+void CodeCompletionBuilder::AddFunctionQualifierChunk(const char *Text) {
+ Chunks.push_back(Chunk::CreateFunctionQualifier(Text));
+}
+
void CodeCompletionBuilder::AddResultTypeChunk(const char *ResultType) {
Chunks.push_back(Chunk::CreateResultType(ResultType));
}
@@ -727,6 +739,7 @@ static std::string getOverloadAsString(const CodeCompletionString &CCS) {
for (auto &C : CCS) {
switch (C.Kind) {
case CodeCompletionString::CK_Informative:
+ case CodeCompletionString::CK_FunctionQualifier:
case CodeCompletionString::CK_ResultType:
OS << "[#" << C.Text << "#]";
break;
diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp
index 4a3559955ade3..dbbf9f9af5a54 100644
--- a/clang/lib/Sema/SemaCodeComplete.cpp
+++ b/clang/lib/Sema/SemaCodeComplete.cpp
@@ -325,7 +325,9 @@ class ResultBuilder {
///
/// \param BaseExprType the object type in a member access expression,
/// if any.
- bool canFunctionBeCalled(const NamedDecl *ND, QualType BaseExprType) const;
+ bool canFunctionBeCalled(const NamedDecl *ND, QualType BaseExprType,
+ bool IsBorrowedContext,
+ bool IsAddressOfOperand) const;
/// Decide whether or not a use of member function Decl can be a call.
///
@@ -369,7 +371,8 @@ class ResultBuilder {
/// \param BaseExprType the type of expression that precedes the "." or "->"
/// in a member access expression.
void AddResult(Result R, DeclContext *CurContext, NamedDecl *Hiding,
- bool InBaseClass, QualType BaseExprType);
+ bool InBaseClass, QualType BaseExprType,
+ bool IsBorrowedContext, bool IsAddressOfOperand);
/// Add a new non-declaration result to this result set.
void AddResult(Result R);
@@ -1342,7 +1345,13 @@ bool ResultBuilder::canCxxMethodBeCalled(const CXXMethodDecl *Method,
}
bool ResultBuilder::canFunctionBeCalled(const NamedDecl *ND,
- QualType BaseExprType) const {
+ QualType BaseExprType,
+ bool IsBorrowedContext = false,
+ bool IsAddressOfOperand = false) const {
+ // If context is borrowed, it is always a declaration so function cannot be
+ // called. If we are waiting for the address of operand, it is not a call.
+ if (IsBorrowedContext || IsAddressOfOperand)
+ return false;
// We apply heuristics only to CCC_Symbol:
// * CCC_{Arrow,Dot}MemberAccess reflect member access expressions:
// f.method() and f->method(). These are always calls.
@@ -1365,7 +1374,9 @@ bool ResultBuilder::canFunctionBeCalled(const NamedDecl *ND,
void ResultBuilder::AddResult(Result R, DeclContext *CurContext,
NamedDecl *Hiding, bool InBaseClass = false,
- QualType BaseExprType = QualType()) {
+ QualType BaseExprType = QualType(),
+ bool IsBorrowedContext = false,
+ bool IsAddressOfOperand = false) {
if (R.Kind != Result::RK_Declaration) {
// For non-declaration results, just add the result.
Results.push_back(R);
@@ -1504,7 +1515,10 @@ void ResultBuilder::AddResult(Result R, DeclContext *CurContext,
OverloadSet.Add(Method, Results.size());
}
- R.FunctionCanBeCall = canFunctionBeCalled(R.getDeclaration(), BaseExprType);
+ // if Context is borrowed, we are in a defintion, meaning not a call
+ R.FunctionCanBeCall = canFunctionBeCalled(
+ R.getDeclaration(), BaseExprType, IsBorrowedContext, IsAddressOfOperand);
+ R.DeclaringEntity = IsBorrowedContext;
// Insert this result into the set of results.
Results.push_back(R);
@@ -1762,7 +1776,8 @@ class CodeCompletionDeclConsumer : public VisibleDeclConsumer {
QualType BaseType = QualType(),
std::vector<FixItHint> FixIts = std::vector<FixItHint>())
: Results(Results), InitialLookupCtx(InitialLookupCtx),
- FixIts(std::move(FixIts)) {
+ FixIts(std::move(FixIts)), IsBorrowedContext(false),
+ IsAddressOfOperand(false) {
NamingClass = llvm::dyn_cast<CXXRecordDecl>(InitialLookupCtx);
// If BaseType was not provided explicitly, emulate implicit 'this->'.
if (BaseType.isNull()) {
@@ -1777,13 +1792,22 @@ class CodeCompletionDeclConsumer : public VisibleDeclConsumer {
this->BaseType = BaseType;
}
+ void setIsBorrowedContext(bool isBorrowedContext) {
+ IsBorrowedContext = isBorrowedContext;
+ }
+
+ void setIsAddressOfOperand(bool isAddressOfOperand) {
+ IsAddressOfOperand = isAddressOfOperand;
+ }
+
void FoundDecl(NamedDecl *ND, NamedDecl *Hiding, DeclContext *Ctx,
bool InBaseClass) override {
ResultBuilder::Result Result(ND, Results.getBasePriority(ND),
/*Qualifier=*/std::nullopt,
/*QualifierIsInformative=*/false,
IsAccessible(ND, Ctx), FixIts);
- Results.AddResult(Result, InitialLookupCtx, Hiding, InBaseClass, BaseType);
+ Results.AddResult(Result, InitialLookupCtx, Hiding, InBaseClass, BaseType,
+ IsBorrowedContext, IsAddressOfOperand);
}
void EnteredContext(DeclContext *Ctx) override {
@@ -1791,6 +1815,8 @@ class CodeCompletionDeclConsumer : public VisibleDeclConsumer {
}
private:
+ bool IsBorrowedContext;
+ bool IsAddressOfOperand;
bool IsAccessible(NamedDecl *ND, DeclContext *Ctx) {
// Naming class to use for access check. In most cases it was provided
// explicitly (e.g. member access (lhs.foo) or qualified lookup (X::)),
@@ -3440,17 +3466,17 @@ static void AddFunctionTypeQuals(CodeCompletionBuilder &Result,
// Handle single qualifiers without copying
if (Quals.hasOnlyConst()) {
- Result.AddInformativeChunk(" const");
+ Result.AddFunctionQualifierChunk(" const");
return;
}
if (Quals.hasOnlyVolatile()) {
- Result.AddInformativeChunk(" volatile");
+ Result.AddFunctionQualifierChunk(" volatile");
return;
}
if (Quals.hasOnlyRestrict()) {
- Result.AddInformativeChunk(" restrict");
+ Result.AddFunctionQualifierChunk(" restrict");
return;
}
@@ -3462,7 +3488,7 @@ static void AddFunctionTypeQuals(CodeCompletionBuilder &Result,
QualsStr += " volatile";
if (Quals.hasRestrict())
QualsStr += " restrict";
- Result.AddInformativeChunk(Result.getAllocator().CopyString(QualsStr));
+ Result.AddFunctionQualifierChunk(Result.getAllocator().CopyString(QualsStr));
}
static void
@@ -5072,7 +5098,7 @@ static void AddLambdaCompletion(ResultBuilder &Results,
/// Perform code-completion in an expression context when we know what
/// type we're looking for.
void SemaCodeCompletion::CodeCompleteExpression(
- Scope *S, const CodeCompleteExpressionData &Data) {
+ Scope *S, const CodeCompleteExpressionData &Data, bool IsAddressOfOperand) {
ResultBuilder Results(
SemaRef, CodeCompleter->getAllocator(),
CodeCompleter->getCodeCompletionTUInfo(),
@@ -5100,6 +5126,7 @@ void SemaCodeCompletion::CodeCompleteExpression(
Results.Ignore(Data.IgnoreDecls[I]);
CodeCompletionDeclConsumer Consumer(Results, SemaRef.CurContext);
+ Consumer.setIsAddressOfOperand(IsAddressOfOperand);
SemaRef.LookupVisibleDecls(S, Sema::LookupOrdinaryName, Consumer,
CodeCompleter->includeGlobals(),
CodeCompleter->loadExternal());
@@ -5143,9 +5170,11 @@ void SemaCodeCompletion::CodeCompleteExpression(
void SemaCodeCompletion::CodeCompleteExpression(Scope *S,
QualType PreferredType,
- bool IsParenthesized) {
+ bool IsParenthesized,
+ bool IsAddressOfOperand) {
return CodeCompleteExpression(
- S, CodeCompleteExpressionData(PreferredType, IsParenthesized));
+ S, CodeCompleteExpressionData(PreferredType, IsParenthesized),
+ IsAddressOfOperand);
}
void SemaCodeCompletion::CodeCompletePostfixExpression(Scope *S, ExprResult E,
@@ -6820,11 +6849,9 @@ void SemaCodeCompletion::CodeCompleteAfterIf(Scope *S, bool IsBracedThen) {
Results.size());
}
-void SemaCodeCompletion::CodeCompleteQualifiedId(Scope *S, CXXScopeSpec &SS,
- bool EnteringContext,
- bool IsUsingDeclaration,
- QualType BaseType,
- QualType PreferredType) {
+void SemaCodeCompletion::CodeCompleteQualifiedId(
+ Scope *S, CXXScopeSpec &SS, bool EnteringContext, bool IsUsingDeclaration,
+ bool IsAddressOfOperand, QualType BaseType, QualType PreferredType) {
if (SS.isEmpty() || !CodeCompleter)
return;
@@ -6859,12 +6886,25 @@ void SemaCodeCompletion::CodeCompleteQualifiedId(Scope *S, CXXScopeSpec &SS,
// resolves to a dependent record.
DeclContext *Ctx = SemaRef.computeDeclContext(SS, /*EnteringContext=*/true);
+ // This is used to borrow context to access private methods for completion
+ DeclContext *SavedContext = nullptr;
+ if (!SemaRef.CurContext->isFunctionOrMethod() &&
+ !SemaRef.CurContext->isRecord()) {
+ // We are in global scope or namespace
+ SavedContext = SemaRef.CurContext;
+ SemaRef.CurContext = Ctx; // Simulate that we are in class scope
+ // to access private methods
+ }
+
// Try to instantiate any non-dependent declaration contexts before
// we look in them. Bail out if we fail.
NestedNameSpecifier NNS = SS.getScopeRep();
if (NNS && !NNS.isDependent()) {
- if (Ctx == nullptr || SemaRef.RequireCompleteDeclContext(SS, Ctx))
+ if (Ctx == nullptr || SemaRef.RequireCompleteDeclContext(SS, Ctx)) {
+ if (SavedContext)
+ SemaRef.CurContext = SavedContext;
return;
+ }
}
ResultBuilder Results(SemaRef, CodeCompleter->getAllocator(),
@@ -6905,11 +6945,15 @@ void SemaCodeCompletion::CodeCompleteQualifiedId(Scope *S, CXXScopeSpec &SS,
if (Ctx &&
(CodeCompleter->includeNamespaceLevelDecls() || !Ctx->isFileContext())) {
CodeCompletionDeclConsumer Consumer(Results, Ctx, BaseType);
+ Consumer.setIsBorrowedContext(SavedContext != nullptr);
+ Consumer.setIsAddressOfOperand(IsAddressOfOperand);
SemaRef.LookupVisibleDecls(Ctx, Sema::LookupOrdinaryName, Consumer,
/*IncludeGlobalScope=*/true,
/*IncludeDependentBases=*/true,
CodeCompleter->loadExternal());
}
+ if (SavedContext)
+ SemaRef.CurContext = SavedContext;
HandleCodeCompleteResults(&SemaRef, CodeCompleter,
Results.getCompletionContext(), Results.data(),
diff --git a/clang/tools/libclang/CIndexCodeCompletion.cpp b/clang/tools/libclang/CIndexCodeCompletion.cpp
index 81448b4d11342..816afd28e6568 100644
--- a/clang/tools/libclang/CIndexCodeCompletion.cpp
+++ b/clang/tools/libclang/CIndexCodeCompletion.cpp
@@ -70,6 +70,8 @@ clang_getCompletionChunkKind(CXCompletionString completion_string,
case CodeCompletionString::CK_Placeholder:
return CXCompletionChunk_Placeholder;
case CodeCompletionString::CK_Informative:
+ case CodeCompletionString::CK_FunctionQualifier:
+ // as FunctionQualifier are informative, except for completion.
return CXCompletionChunk_Informative;
case CodeCompletionString::CK_ResultType:
return CXCompletionChunk_ResultType;
@@ -120,6 +122,7 @@ CXString clang_getCompletionChunkText(CXCompletionString completion_string,
case CodeCompletionString::CK_Placeholder:
case CodeCompletionString::CK_CurrentParameter:
case CodeCompletionString::CK_Informative:
+ case CodeCompletionString::CK_FunctionQualifier:
case CodeCompletionString::CK_LeftParen:
case CodeCompletionString::CK_RightParen:
case CodeCompletionString::CK_LeftBracket:
@@ -159,6 +162,7 @@ clang_getCompletionChunkCompletionString(CXCompletionString completion_string,
case CodeCompletionString::CK_Placeholder:
case CodeCompletionString::CK_CurrentParameter:
case CodeCompletionString::CK_Informative:
+ case CodeCompletionString::CK_FunctionQualifier:
case CodeCompletionString::CK_LeftParen:
case CodeCompletionString::CK_RightParen:
case CodeCompletionString::CK_LeftBracket:
More information about the cfe-commits
mailing list