[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:
parent
ce288f4441
commit
98f84f9bf2
@ -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())
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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)(
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user