Author: Hippolyte Melica Date: 2026-03-22T22:25:54Z New Revision: 98f84f9bf2dfdf8a77c025303e594ede8336eb8e
URL: https://github.com/llvm/llvm-project/commit/98f84f9bf2dfdf8a77c025303e594ede8336eb8e DIFF: https://github.com/llvm/llvm-project/commit/98f84f9bf2dfdf8a77c025303e594ede8336eb8e.diff LOG: [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 Added: Modified: clang-tools-extra/clangd/CodeComplete.cpp clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp clang/include/clang/Parse/Parser.h clang/include/clang/Sema/SemaCodeCompletion.h clang/lib/Parse/ParseDecl.cpp clang/lib/Parse/ParseExpr.cpp clang/lib/Parse/ParseExprCXX.cpp clang/lib/Parse/Parser.cpp clang/lib/Sema/SemaCodeComplete.cpp clang/test/CodeCompletion/cpp23-explicit-object.cpp clang/test/CodeCompletion/member-access.cpp clang/test/Index/complete-qualified.cpp Removed: ################################################################################ diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index 7c390f9c8219d..28f81cd5267d5 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -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()) diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 31f2d8bd68703..386ffb54924a7 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -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})")))); + 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 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}>()")))); } - // static method will always keep the snippet - for (const auto &P : Code.points()) { + for (const auto &P : Code.points("canBeCallNoStatic")) { auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts); EXPECT_THAT(Results.Completions, - Contains(AllOf(named("staticMethod"), signature("(int)"), - 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 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) { diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 5ae02e2b4e8ad..08a3d88ee6a36 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -354,9 +354,20 @@ class Parser : public CodeCompletionHandler { /// 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 @@ class Parser : public CodeCompletionHandler { /// 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 diff erent 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 @@ class Parser : public CodeCompletionHandler { /// /// \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 @@ 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 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 diff --git a/clang/include/clang/Sema/SemaCodeCompletion.h b/clang/include/clang/Sema/SemaCodeCompletion.h index 3029e56e5cfe2..abdfb51900318 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,7 +158,8 @@ class SemaCodeCompletion : public SemaBase { 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); diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 72935f427b7f8..d448cb8a552bc 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -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)) { 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 b3d50daf66b10..d0d9def01a4c2 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, 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 diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index 5d18414b1a746..c4f745612e06c 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/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp index 98d78d2a461f1..0cd9819dc2964 100644 --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -369,7 +369,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 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 @@ 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)), 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 @@ class CodeCompletionDeclConsumer : public VisibleDeclConsumer { 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()); diff --git a/clang/test/CodeCompletion/cpp23-explicit-object.cpp b/clang/test/CodeCompletion/cpp23-explicit-object.cpp index ea97237ecd24b..82888b76fb918 100644 --- a/clang/test/CodeCompletion/cpp23-explicit-object.cpp +++ b/clang/test/CodeCompletion/cpp23-explicit-object.cpp @@ -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)( diff --git a/clang/test/CodeCompletion/member-access.cpp b/clang/test/CodeCompletion/member-access.cpp index 8526ed7273474..2de96b43ff2ba 100644 --- a/clang/test/CodeCompletion/member-access.cpp +++ b/clang/test/CodeCompletion/member-access.cpp @@ -171,7 +171,7 @@ class Template { 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 { diff --git a/clang/test/Index/complete-qualified.cpp b/clang/test/Index/complete-qualified.cpp index 11abd53b86c28..dcb06439e5778 100644 --- a/clang/test/Index/complete-qualified.cpp +++ b/clang/test/Index/complete-qualified.cpp @@ -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) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
