[clangd] Code completion for declaration of class method (#165916)

Code completion previously could not tell apart the declaration of
a method from a call to it, and provided call-like behaviour even
in declaration contexts. This included things like not offering 
completion for private methods, and inserting placeholders for
the parameters as though the user was going to fill in arguments.

This patch adds support to Parser and SemaCodeComplete to
detect and provide dedicated behaviour for declaration contexts.
In these contexts, the flag CodeCompletionResult::DeclaringEntity
is set, and createCodeCompletionString() is adjusted to handle this
flag, e.g. by inserting parameter declarations as text chunks rather
than placeholder chunks.

The DeclaringEntity flag is also available for consumers of
SemaCodeComplete, such as clangd, to customize their behaviour.

In addition, the patch tweaks the conditions under which the
existing CodeCompletionResult::FunctionCanBeCall flag is set to
be more accurate, excluding declaration contexts and cases where
the address of the function is likely being taken.

Fixes clangd/clangd#753
Fixes clangd/clangd#880
Fixes clangd/clangd#1752
This commit is contained in:
Hippolyte Melica 2026-03-22 23:25:54 +01:00 committed by GitHub
parent ce288f4441
commit 98f84f9bf2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 350 additions and 98 deletions

View File

@ -477,10 +477,12 @@ struct CodeCompletionBuilder {
BundledEntry &S = Bundled.back();
bool IsConcept = false;
if (C.SemaResult) {
getSignature(*SemaCCS, &S.Signature, &S.SnippetSuffix, C.SemaResult->Kind,
C.SemaResult->CursorKind,
/*IncludeFunctionArguments=*/C.SemaResult->FunctionCanBeCall,
/*RequiredQualifiers=*/&Completion.RequiredQualifier);
getSignature(
*SemaCCS, &S.Signature, &S.SnippetSuffix, C.SemaResult->Kind,
C.SemaResult->CursorKind,
/*IncludeFunctionArguments=*/C.SemaResult->FunctionCanBeCall ||
C.SemaResult->DeclaringEntity,
/*RequiredQualifiers=*/&Completion.RequiredQualifier);
S.ReturnType = getReturnType(*SemaCCS);
if (C.SemaResult->Kind == CodeCompletionResult::RK_Declaration)
if (const auto *D = C.SemaResult->getDeclaration())

View File

@ -531,19 +531,26 @@ 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^ {
}
;
int i = Foo::$canBeCallStaticOnly^
;
struct Derived : Foo {
using Foo::method;
using Foo::generic;
@ -556,9 +563,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 +581,7 @@ TEST(CompletionTest, HeuristicsForMemberFunctionCompletion) {
f.$canBeCall^
; // Prevent parsing as 'f.f'
f.Foo::$canBeCall^
;
&Foo::$canNotBeCall^
;
d.Foo::$canBeCall^
@ -585,39 +594,129 @@ 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("canBeCallStaticOnly")) {
auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts);
EXPECT_THAT(Results.Completions,
Contains(AllOf(named("method"), signature("(int name) const"),
snippetSuffix(""))));
EXPECT_THAT(
Results.Completions,
Contains(AllOf(named("generic"),
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("(${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("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, PrivateMemberDefinition) {
clangd::CodeCompleteOptions Opts;
Opts.EnableSnippets = true;
auto Results = completions(
R"cpp(
class Foo {
int func(int a, int b);
};
int Foo::func^
)cpp",
/*IndexSymbols=*/{}, Opts);
EXPECT_THAT(Results.Completions,
Contains(AllOf(named("func"), signature("(int a, int b)"),
snippetSuffix("(int a, int b)"))));
}
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) {

View File

@ -354,9 +354,20 @@ public:
/// Note that this routine emits an error if you call it with ::new or
/// ::delete as the current tokens, so only call it in contexts where these
/// are invalid.
///
/// \param IsAddressOfOperand A hint indicating whether the current token
/// sequence is likely part of an address-of operation. Used by code
/// completion to filter results; may not be set by all callers.
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
@ -3849,6 +3860,9 @@ private:
/// of address-of gets special treatment due to member pointers. NotCastExpr
/// is set to true if the token is not the start of a cast-expression, and no
/// diagnostic is emitted in this case and no tokens are consumed.
/// In addition, isAddressOfOperand is propagated to SemaCodeCompletion
/// as a heuristic for function completions (to provide different behavior
/// when the user is likely taking the address of a function vs. calling it).
///
/// \verbatim
/// cast-expression: [C99 6.5.4]
@ -4561,6 +4575,14 @@ private:
///
/// \param OnlyNamespace If true, only considers namespaces in lookup.
///
/// \param IsAddressOfOperand A hint indicating the expression is part of
/// an address-of operation (e.g. '&'). Used by code completion to filter
/// results; may not be set by all callers.
///
/// \param IsInDeclarationContext A hint indicating whether the current
/// context is likely a declaration. Used by code completion to filter
/// results; may not be set by all callers.
///
///
/// \returns true if there was an error parsing a scope specifier
bool ParseOptionalCXXScopeSpecifier(
@ -4568,7 +4590,23 @@ private:
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 IsInDeclarationContext = 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

View File

@ -101,9 +101,11 @@ public:
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,7 +158,8 @@ public:
void CodeCompleteAfterIf(Scope *S, bool IsBracedThen);
void CodeCompleteQualifiedId(Scope *S, CXXScopeSpec &SS, bool EnteringContext,
bool IsUsingDeclaration, QualType BaseType,
bool IsUsingDeclaration, bool IsAddressOfOperand,
bool IsInDeclarationContext, QualType BaseType,
QualType PreferredType);
void CodeCompleteUsing(Scope *S);
void CodeCompleteUsingDirective(Scope *S);

View File

@ -6415,7 +6415,9 @@ void Parser::ParseDeclaratorInternal(Declarator &D,
/*IsTypename=*/false, /*LastII=*/nullptr,
/*OnlyNamespace=*/false,
/*InUsingDeclaration=*/false,
/*Disambiguation=*/EnteringContext) ||
/*Disambiguation=*/EnteringContext,
/*IsAddressOfOperand=*/false,
/*IsInDeclarationContext=*/true) ||
SS.isEmpty() || SS.isInvalid() || !EnteringContext ||
Tok.is(tok::star)) {

View File

@ -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:

View File

@ -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, bool IsInDeclarationContext) {
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, IsInDeclarationContext, 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

View File

@ -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,

View File

@ -369,7 +369,8 @@ public:
/// \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 IsInDeclarationContext, bool IsAddressOfOperand);
/// Add a new non-declaration result to this result set.
void AddResult(Result R);
@ -1365,7 +1366,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 IsInDeclarationContext = false,
bool IsAddressOfOperand = false) {
if (R.Kind != Result::RK_Declaration) {
// For non-declaration results, just add the result.
Results.push_back(R);
@ -1503,8 +1506,13 @@ void ResultBuilder::AddResult(Result R, DeclContext *CurContext,
}
OverloadSet.Add(Method, Results.size());
}
R.FunctionCanBeCall = canFunctionBeCalled(R.getDeclaration(), BaseExprType);
R.DeclaringEntity = IsInDeclarationContext;
R.FunctionCanBeCall =
canFunctionBeCalled(R.getDeclaration(), BaseExprType) &&
// If the user wrote `&` before the function name, assume the
// user is more likely to take the address of the function rather
// than call it and take the address of the result.
!IsAddressOfOperand;
// Insert this result into the set of results.
Results.push_back(R);
@ -1755,6 +1763,9 @@ class CodeCompletionDeclConsumer : public VisibleDeclConsumer {
CXXRecordDecl *NamingClass;
QualType BaseType;
std::vector<FixItHint> FixIts;
bool IsInDeclarationContext;
// Completion is invoked after an identifier preceded by '&'.
bool IsAddressOfOperand;
public:
CodeCompletionDeclConsumer(
@ -1762,7 +1773,8 @@ public:
QualType BaseType = QualType(),
std::vector<FixItHint> FixIts = std::vector<FixItHint>())
: Results(Results), InitialLookupCtx(InitialLookupCtx),
FixIts(std::move(FixIts)) {
FixIts(std::move(FixIts)), IsInDeclarationContext(false),
IsAddressOfOperand(false) {
NamingClass = llvm::dyn_cast<CXXRecordDecl>(InitialLookupCtx);
// If BaseType was not provided explicitly, emulate implicit 'this->'.
if (BaseType.isNull()) {
@ -1777,13 +1789,22 @@ public:
this->BaseType = BaseType;
}
void setIsInDeclarationContext(bool IsInDeclarationContext) {
this->IsInDeclarationContext = IsInDeclarationContext;
}
void setIsAddressOfOperand(bool IsAddressOfOperand) {
this->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,
IsInDeclarationContext, IsAddressOfOperand);
}
void EnteredContext(DeclContext *Ctx) override {
@ -3273,18 +3294,19 @@ static std::string GetDefaultValueString(const ParmVarDecl *Param,
}
/// Add function parameter chunks to the given code completion string.
static void AddFunctionParameterChunks(Preprocessor &PP,
const PrintingPolicy &Policy,
const FunctionDecl *Function,
CodeCompletionBuilder &Result,
unsigned Start = 0,
bool InOptional = false) {
static void AddFunctionParameterChunks(
Preprocessor &PP, const PrintingPolicy &Policy,
const FunctionDecl *Function, CodeCompletionBuilder &Result,
unsigned Start = 0, bool InOptional = false, bool FunctionCanBeCall = true,
bool IsInDeclarationContext = false) {
bool FirstParameter = true;
bool AsInformativeChunk = !(FunctionCanBeCall || IsInDeclarationContext);
for (unsigned P = Start, N = Function->getNumParams(); P != N; ++P) {
const ParmVarDecl *Param = Function->getParamDecl(P);
if (Param->hasDefaultArg() && !InOptional) {
if (Param->hasDefaultArg() && !InOptional && !IsInDeclarationContext &&
!AsInformativeChunk) {
// When we see an optional default argument, put that argument and
// the remaining default arguments into a new, optional string.
CodeCompletionBuilder Opt(Result.getAllocator(),
@ -3305,23 +3327,42 @@ static void AddFunctionParameterChunks(Preprocessor &PP,
if (FirstParameter)
FirstParameter = false;
else
Result.AddChunk(CodeCompletionString::CK_Comma);
else {
if (AsInformativeChunk)
Result.AddInformativeChunk(", ");
else
Result.AddChunk(CodeCompletionString::CK_Comma);
}
InOptional = false;
// Format the placeholder string.
std::string PlaceholderStr = FormatFunctionParameter(Policy, Param);
if (Param->hasDefaultArg())
PlaceholderStr +=
GetDefaultValueString(Param, PP.getSourceManager(), PP.getLangOpts());
std::string DefaultValue;
if (Param->hasDefaultArg()) {
if (IsInDeclarationContext)
DefaultValue = GetDefaultValueString(Param, PP.getSourceManager(),
PP.getLangOpts());
else
PlaceholderStr += GetDefaultValueString(Param, PP.getSourceManager(),
PP.getLangOpts());
}
if (Function->isVariadic() && P == N - 1)
PlaceholderStr += ", ...";
// Add the placeholder string.
Result.AddPlaceholderChunk(
Result.getAllocator().CopyString(PlaceholderStr));
if (AsInformativeChunk)
Result.AddInformativeChunk(
Result.getAllocator().CopyString(PlaceholderStr));
else if (IsInDeclarationContext) { // No placeholders in declaration context
Result.AddTextChunk(Result.getAllocator().CopyString(PlaceholderStr));
if (DefaultValue.length() != 0)
Result.AddInformativeChunk(
Result.getAllocator().CopyString(DefaultValue));
} else
Result.AddPlaceholderChunk(
Result.getAllocator().CopyString(PlaceholderStr));
}
if (const auto *Proto = Function->getType()->getAs<FunctionProtoType>())
@ -3337,7 +3378,8 @@ static void AddFunctionParameterChunks(Preprocessor &PP,
static void AddTemplateParameterChunks(
ASTContext &Context, const PrintingPolicy &Policy,
const TemplateDecl *Template, CodeCompletionBuilder &Result,
unsigned MaxParameters = 0, unsigned Start = 0, bool InDefaultArg = false) {
unsigned MaxParameters = 0, unsigned Start = 0, bool InDefaultArg = false,
bool AsInformativeChunk = false) {
bool FirstParameter = true;
// Prefer to take the template parameter names from the first declaration of
@ -3388,7 +3430,7 @@ static void AddTemplateParameterChunks(
HasDefaultArg = TTP->hasDefaultArgument();
}
if (HasDefaultArg && !InDefaultArg) {
if (HasDefaultArg && !InDefaultArg && !AsInformativeChunk) {
// When we see an optional default argument, put that argument and
// the remaining default arguments into a new, optional string.
CodeCompletionBuilder Opt(Result.getAllocator(),
@ -3405,12 +3447,19 @@ static void AddTemplateParameterChunks(
if (FirstParameter)
FirstParameter = false;
else
Result.AddChunk(CodeCompletionString::CK_Comma);
else {
if (AsInformativeChunk)
Result.AddInformativeChunk(", ");
else
Result.AddChunk(CodeCompletionString::CK_Comma);
}
// Add the placeholder string.
Result.AddPlaceholderChunk(
Result.getAllocator().CopyString(PlaceholderStr));
if (AsInformativeChunk)
Result.AddInformativeChunk(
Result.getAllocator().CopyString(PlaceholderStr));
else // Add the placeholder string.
Result.AddPlaceholderChunk(
Result.getAllocator().CopyString(PlaceholderStr));
}
}
@ -3436,22 +3485,32 @@ static void AddQualifierToCompletionString(CodeCompletionBuilder &Result,
}
static void AddFunctionTypeQuals(CodeCompletionBuilder &Result,
const Qualifiers Quals) {
const Qualifiers Quals,
bool AsInformativeChunk = true) {
// FIXME: Add ref-qualifier!
// Handle single qualifiers without copying
if (Quals.hasOnlyConst()) {
Result.AddInformativeChunk(" const");
if (AsInformativeChunk)
Result.AddInformativeChunk(" const");
else
Result.AddTextChunk(" const");
return;
}
if (Quals.hasOnlyVolatile()) {
Result.AddInformativeChunk(" volatile");
if (AsInformativeChunk)
Result.AddInformativeChunk(" volatile");
else
Result.AddTextChunk(" volatile");
return;
}
if (Quals.hasOnlyRestrict()) {
Result.AddInformativeChunk(" restrict");
if (AsInformativeChunk)
Result.AddInformativeChunk(" restrict");
else
Result.AddTextChunk(" restrict");
return;
}
@ -3463,12 +3522,17 @@ static void AddFunctionTypeQuals(CodeCompletionBuilder &Result,
QualsStr += " volatile";
if (Quals.hasRestrict())
QualsStr += " restrict";
Result.AddInformativeChunk(Result.getAllocator().CopyString(QualsStr));
if (AsInformativeChunk)
Result.AddInformativeChunk(Result.getAllocator().CopyString(QualsStr));
else
Result.AddTextChunk(Result.getAllocator().CopyString(QualsStr));
}
static void
AddFunctionTypeQualsToCompletionString(CodeCompletionBuilder &Result,
const FunctionDecl *Function) {
const FunctionDecl *Function,
bool AsInformativeChunks = true) {
if (auto *CxxMethodDecl = llvm::dyn_cast_if_present<CXXMethodDecl>(Function);
CxxMethodDecl && CxxMethodDecl->hasCXXExplicitFunctionObjectParameter()) {
// if explicit object method, infer quals from the object parameter
@ -3476,13 +3540,13 @@ AddFunctionTypeQualsToCompletionString(CodeCompletionBuilder &Result,
if (!Quals.hasQualifiers())
return;
AddFunctionTypeQuals(Result, Quals.getQualifiers());
AddFunctionTypeQuals(Result, Quals.getQualifiers(), AsInformativeChunks);
} else {
const auto *Proto = Function->getType()->getAs<FunctionProtoType>();
if (!Proto || !Proto->getMethodQuals())
return;
AddFunctionTypeQuals(Result, Proto->getMethodQuals());
AddFunctionTypeQuals(Result, Proto->getMethodQuals(), AsInformativeChunks);
}
}
@ -3774,10 +3838,21 @@ CodeCompletionString *CodeCompletionResult::createCodeCompletionStringForDecl(
AddQualifierToCompletionString(Result, Qualifier, QualifierIsInformative,
Ctx, Policy);
AddTypedNameChunk(Ctx, Policy, ND, Result);
Result.AddChunk(CodeCompletionString::CK_LeftParen);
AddFunctionParameterChunks(PP, Policy, Function, Result);
Result.AddChunk(CodeCompletionString::CK_RightParen);
AddFunctionTypeQualsToCompletionString(Result, Function);
bool InsertParameters = FunctionCanBeCall || DeclaringEntity;
if (InsertParameters)
Result.AddChunk(CodeCompletionString::CK_LeftParen);
else
Result.AddInformativeChunk("(");
AddFunctionParameterChunks(PP, Policy, Function, Result, /*Start=*/0,
/*InOptional=*/false,
/*FunctionCanBeCall=*/FunctionCanBeCall,
/*IsInDeclarationContext=*/DeclaringEntity);
if (InsertParameters)
Result.AddChunk(CodeCompletionString::CK_RightParen);
else
Result.AddInformativeChunk(")");
AddFunctionTypeQualsToCompletionString(
Result, Function, /*AsInformativeChunks=*/!DeclaringEntity);
};
if (const auto *Function = dyn_cast<FunctionDecl>(ND)) {
@ -3849,16 +3924,35 @@ CodeCompletionString *CodeCompletionResult::createCodeCompletionStringForDecl(
// e.g.,
// template <class T> void foo(T);
// void (*f)(int) = foo;
Result.AddChunk(CodeCompletionString::CK_LeftAngle);
AddTemplateParameterChunks(Ctx, Policy, FunTmpl, Result,
LastDeducibleArgument);
Result.AddChunk(CodeCompletionString::CK_RightAngle);
if (!DeclaringEntity)
Result.AddChunk(CodeCompletionString::CK_LeftAngle);
else
Result.AddInformativeChunk("<");
AddTemplateParameterChunks(
Ctx, Policy, FunTmpl, Result, LastDeducibleArgument, /*Start=*/0,
/*InDefaultArg=*/false, /*AsInformativeChunk=*/DeclaringEntity);
// Only adds template arguments as informative chunks in declaration
// context.
if (!DeclaringEntity)
Result.AddChunk(CodeCompletionString::CK_RightAngle);
else
Result.AddInformativeChunk(">");
}
// Add the function parameters
Result.AddChunk(CodeCompletionString::CK_LeftParen);
AddFunctionParameterChunks(PP, Policy, Function, Result);
Result.AddChunk(CodeCompletionString::CK_RightParen);
bool InsertParameters = FunctionCanBeCall || DeclaringEntity;
if (InsertParameters)
Result.AddChunk(CodeCompletionString::CK_LeftParen);
else
Result.AddInformativeChunk("(");
AddFunctionParameterChunks(PP, Policy, Function, Result, /*Start=*/0,
/*InOptional=*/false,
/*FunctionCanBeCall=*/FunctionCanBeCall,
/*IsInDeclarationContext=*/DeclaringEntity);
if (InsertParameters)
Result.AddChunk(CodeCompletionString::CK_RightParen);
else
Result.AddInformativeChunk(")");
AddFunctionTypeQualsToCompletionString(Result, Function);
return Result.TakeString();
}
@ -5073,7 +5167,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(),
@ -5101,6 +5195,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());
@ -5144,9 +5239,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,
@ -6821,11 +6918,10 @@ 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, bool IsInDeclarationContext, QualType BaseType,
QualType PreferredType) {
if (SS.isEmpty() || !CodeCompleter)
return;
@ -6860,6 +6956,12 @@ void SemaCodeCompletion::CodeCompleteQualifiedId(Scope *S, CXXScopeSpec &SS,
// resolves to a dependent record.
DeclContext *Ctx = SemaRef.computeDeclContext(SS, /*EnteringContext=*/true);
std::optional<Sema::ContextRAII> SimulateContext;
// When completing a definition, simulate that we are in class scope to access
// private methods.
if (IsInDeclarationContext && Ctx != nullptr)
SimulateContext.emplace(SemaRef, Ctx);
// Try to instantiate any non-dependent declaration contexts before
// we look in them. Bail out if we fail.
NestedNameSpecifier NNS = SS.getScopeRep();
@ -6906,12 +7008,14 @@ void SemaCodeCompletion::CodeCompleteQualifiedId(Scope *S, CXXScopeSpec &SS,
if (Ctx &&
(CodeCompleter->includeNamespaceLevelDecls() || !Ctx->isFileContext())) {
CodeCompletionDeclConsumer Consumer(Results, Ctx, BaseType);
Consumer.setIsInDeclarationContext(IsInDeclarationContext);
Consumer.setIsAddressOfOperand(IsAddressOfOperand);
SemaRef.LookupVisibleDecls(Ctx, Sema::LookupOrdinaryName, Consumer,
/*IncludeGlobalScope=*/true,
/*IncludeDependentBases=*/true,
CodeCompleter->loadExternal());
}
SimulateContext.reset();
HandleCodeCompleteResults(&SemaRef, CodeCompleter,
Results.getCompletionContext(), Results.data(),
Results.size());

View File

@ -36,9 +36,9 @@ int func3() {
(&A::bar)
}
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:%(line-3):10 -std=c++23 %s | FileCheck -check-prefix=CHECK-CC3 %s
// CHECK-CC3: COMPLETION: foo : [#void#]foo<<#class self:auto#>>(<#int arg#>)
// CHECK-CC3: COMPLETION: foo : [#void#]foo<<#class self:auto#>>[#(#][#int arg#][#)#]
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:%(line-4):10 -std=c++23 %s | FileCheck -check-prefix=CHECK-CC4 %s
// CHECK-CC4: COMPLETION: bar : [#void#]bar(<#int arg#>)
// CHECK-CC4: COMPLETION: bar : [#void#]bar[#(#][#int arg#][#)#]
int func4() {
// TODO (&A::foo)(

View File

@ -171,7 +171,7 @@ public:
template<typename T>
void dependentColonColonCompletion() {
Template<T>::staticFn();
// CHECK-CC7: function : [#void#]function()
// CHECK-CC7: function : [#void#]function[#(#][#)#]
// CHECK-CC7: Nested : Nested
// CHECK-CC7: o1 : [#BaseTemplate<int>#]o1
// CHECK-CC7: o2 : [#BaseTemplate<T>#]o2
@ -352,7 +352,7 @@ namespace function_can_be_call {
&S::f
}
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:352:9 %s -o - | FileCheck -check-prefix=CHECK_FUNCTION_CAN_BE_CALL %s
// CHECK_FUNCTION_CAN_BE_CALL: COMPLETION: foo : [#T#]foo<<#typename T#>, <#typename U#>>(<#U#>, <#V#>)
// CHECK_FUNCTION_CAN_BE_CALL: COMPLETION: foo : [#T#]foo<<#typename T#>, <#typename U#>>[#(#][#U#][#, #][#V#][#)#]
}
namespace deref_dependent_this {

View File

@ -16,5 +16,5 @@ void foo()
// RUN: c-index-test -code-completion-at=%s:14:8 %s -o - | FileCheck -check-prefix=CHECK-CC1 %s
// CHECK-CC1: FieldDecl:{ResultType C<Foo, class Bar>}{TypedText c} (35)
// CHECK-CC1: ClassDecl:{TypedText Foo} (35)
// CHECK-CC1: CXXMethod:{ResultType Foo &}{TypedText operator=}{LeftParen (}{Placeholder const Foo &}{RightParen )}
// CHECK-CC1: CXXDestructor:{ResultType void}{TypedText ~Foo}{LeftParen (}{RightParen )} (80)
// CHECK-CC1: CXXMethod:{ResultType Foo &}{TypedText operator=}{Informative (}{Informative const Foo &}{Informative )} (80)
// CHECK-CC1: CXXDestructor:{ResultType void}{TypedText ~Foo}{Informative (}{Informative )} (80)