https://github.com/etyloppihacilem updated https://github.com/llvm/llvm-project/pull/165916
>From f44e756236b96bee574736b13fd7a134f715f6b9 Mon Sep 17 00:00:00 2001 From: Hippolyte Melica <[email protected]> Date: Fri, 31 Oct 2025 12:08:07 +0100 Subject: [PATCH] [clangd] Autocomplete fixes for methods and arguments --- .../clangd/unittests/CodeCompleteTests.cpp | 131 ++++++++-- clang/include/clang/Parse/Parser.h | 27 +- .../include/clang/Sema/CodeCompleteConsumer.h | 16 +- clang/include/clang/Sema/SemaCodeCompletion.h | 9 +- clang/lib/Parse/ParseDecl.cpp | 4 +- clang/lib/Parse/ParseExpr.cpp | 5 +- clang/lib/Parse/ParseExprCXX.cpp | 5 +- clang/lib/Parse/Parser.cpp | 10 +- clang/lib/Sema/SemaCodeComplete.cpp | 231 +++++++++++++----- .../CodeCompletion/cpp23-explicit-object.cpp | 4 +- clang/test/CodeCompletion/member-access.cpp | 4 +- clang/test/Index/complete-qualified.cpp | 4 +- 12 files changed, 353 insertions(+), 97 deletions(-) diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 31f2d8bd68703..80103ac7f96a1 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,127 @@ 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 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}>()")))); + } + + 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, 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})")))); } - // static method will always keep the snippet - for (const auto &P : Code.points()) { + for (const auto &P : Code.points("canBeCallStaticOnly")) { 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("")))); + 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>(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..12f9e30d5fad3 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -356,7 +356,14 @@ class Parser : public CodeCompletionHandler { /// are invalid. bool TryAnnotateTypeOrScopeToken(ImplicitTypenameContext AllowImplicitTypename = - ImplicitTypenameContext::No); + ImplicitTypenameContext::No, + bool IsAddressOfOperand = false); + + bool TryAnnotateTypeOrScopeToken(bool IsAddressOfOperand) { + return TryAnnotateTypeOrScopeToken( + /*AllowImplicitTypename=*/ImplicitTypenameContext::No, + /*IsAddressOfOperand=*/IsAddressOfOperand); + } /// Try to annotate a type or scope token, having already parsed an /// optional scope specifier. \p IsNewScope should be \c true unless the scope @@ -4568,7 +4575,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/CodeCompleteConsumer.h b/clang/include/clang/Sema/CodeCompleteConsumer.h index c26f4e33d289c..babb7288247d2 100644 --- a/clang/include/clang/Sema/CodeCompleteConsumer.h +++ b/clang/include/clang/Sema/CodeCompleteConsumer.h @@ -865,6 +865,9 @@ class CodeCompletionResult { /// be a call. bool FunctionCanBeCall : 1; + /// Whether the completion expects the address of the operand. + bool IsAddressOfOperand : 1; + /// If the result should have a nested-name-specifier, this is it. /// When \c QualifierIsInformative, the nested-name-specifier is /// informative rather than required. @@ -891,7 +894,8 @@ class CodeCompletionResult { FixIts(std::move(FixIts)), Hidden(false), InBaseClass(false), QualifierIsInformative(QualifierIsInformative), StartsNestedNameSpecifier(false), AllParametersAreInformative(false), - DeclaringEntity(false), FunctionCanBeCall(true), Qualifier(Qualifier) { + DeclaringEntity(false), FunctionCanBeCall(true), + IsAddressOfOperand(false), Qualifier(Qualifier) { // FIXME: Add assert to check FixIts range requirements. computeCursorKindAndAvailability(Accessible); } @@ -902,7 +906,7 @@ class CodeCompletionResult { CursorKind(CXCursor_NotImplemented), Hidden(false), InBaseClass(false), QualifierIsInformative(false), StartsNestedNameSpecifier(false), AllParametersAreInformative(false), DeclaringEntity(false), - FunctionCanBeCall(true) {} + FunctionCanBeCall(true), IsAddressOfOperand(false) {} /// Build a result that refers to a macro. CodeCompletionResult(const IdentifierInfo *Macro, @@ -912,7 +916,7 @@ class CodeCompletionResult { CursorKind(CXCursor_MacroDefinition), Hidden(false), InBaseClass(false), QualifierIsInformative(false), StartsNestedNameSpecifier(false), AllParametersAreInformative(false), DeclaringEntity(false), - FunctionCanBeCall(true), MacroDefInfo(MI) {} + FunctionCanBeCall(true), IsAddressOfOperand(false), MacroDefInfo(MI) {} /// Build a result that refers to a pattern. CodeCompletionResult( @@ -924,7 +928,8 @@ class CodeCompletionResult { CursorKind(CursorKind), Availability(Availability), Hidden(false), InBaseClass(false), QualifierIsInformative(false), StartsNestedNameSpecifier(false), AllParametersAreInformative(false), - DeclaringEntity(false), FunctionCanBeCall(true) {} + DeclaringEntity(false), FunctionCanBeCall(true), + IsAddressOfOperand(false) {} /// Build a result that refers to a pattern with an associated /// declaration. @@ -933,7 +938,8 @@ class CodeCompletionResult { : Declaration(D), Pattern(Pattern), Priority(Priority), Kind(RK_Pattern), Hidden(false), InBaseClass(false), QualifierIsInformative(false), StartsNestedNameSpecifier(false), AllParametersAreInformative(false), - DeclaringEntity(false), FunctionCanBeCall(true) { + DeclaringEntity(false), FunctionCanBeCall(true), + IsAddressOfOperand(false) { computeCursorKindAndAvailability(); } 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 d0d006a78274e..2d489fbbf497b 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 1a7564eab99cd..696507507f92e 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,27 @@ void ResultBuilder::AddResult(Result R, DeclContext *CurContext, } OverloadSet.Add(Method, Results.size()); } - - R.FunctionCanBeCall = canFunctionBeCalled(R.getDeclaration(), BaseExprType); + R.DeclaringEntity = IsInDeclarationContext; + R.IsAddressOfOperand = IsAddressOfOperand; + R.FunctionCanBeCall = canFunctionBeCalled(R.getDeclaration(), BaseExprType) || + IsInDeclarationContext; + + // We need to force IsAddressOfOperand when completing a ScopeSpecifier + // for non static member function that could be a call. + if (!IsInDeclarationContext && !IsAddressOfOperand) { // it may be a call + const CXXMethodDecl *Method = + llvm::dyn_cast<clang::CXXMethodDecl>(R.Declaration); + if (!Method) { + if (auto *FTD = + llvm::dyn_cast<clang::FunctionTemplateDecl>(R.Declaration)) { + Method = llvm::dyn_cast<clang::CXXMethodDecl>(FTD->getTemplatedDecl()); + } + } + if (Method) { + // No call completion after :: for non static member function. + R.IsAddressOfOperand = !Method->isStatic() && BaseExprType.isNull(); + } + } // Insert this result into the set of results. Results.push_back(R); @@ -1762,7 +1784,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 +1800,22 @@ class CodeCompletionDeclConsumer : public VisibleDeclConsumer { this->BaseType = BaseType; } + void setIsInDeclarationContext(bool IsInDeclarationContext) { + this->IsInDeclarationContext = IsInDeclarationContext; + } + + void setIsAddressOfOperand(bool isAddressOfOperand) { + IsAddressOfOperand = isAddressOfOperand; + } + void FoundDecl(NamedDecl *ND, NamedDecl *Hiding, DeclContext *Ctx, bool InBaseClass) override { ResultBuilder::Result Result(ND, Results.getBasePriority(ND), /*Qualifier=*/std::nullopt, /*QualifierIsInformative=*/false, IsAccessible(ND, Ctx), FixIts); - Results.AddResult(Result, InitialLookupCtx, Hiding, InBaseClass, BaseType); + Results.AddResult(Result, InitialLookupCtx, Hiding, InBaseClass, BaseType, + IsInDeclarationContext, IsAddressOfOperand); } void EnteredContext(DeclContext *Ctx) override { @@ -1791,6 +1823,8 @@ class CodeCompletionDeclConsumer : public VisibleDeclConsumer { } private: + bool IsInDeclarationContext; + bool IsAddressOfOperand; bool IsAccessible(NamedDecl *ND, DeclContext *Ctx) { // Naming class to use for access check. In most cases it was provided // explicitly (e.g. member access (lhs.foo) or qualified lookup (X::)), @@ -3273,18 +3307,18 @@ 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 OnlyInformative = false, + bool IsInDeclarationContext = false) { bool FirstParameter = true; 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 && + !OnlyInformative) { // 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 +3339,42 @@ static void AddFunctionParameterChunks(Preprocessor &PP, if (FirstParameter) FirstParameter = false; - else - Result.AddChunk(CodeCompletionString::CK_Comma); + else { + if (OnlyInformative) + 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 (OnlyInformative) + 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 +3390,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 OnlyInformative = false) { bool FirstParameter = true; // Prefer to take the template parameter names from the first declaration of @@ -3388,7 +3442,7 @@ static void AddTemplateParameterChunks( HasDefaultArg = TTP->hasDefaultArgument(); } - if (HasDefaultArg && !InDefaultArg) { + if (HasDefaultArg && !InDefaultArg && !OnlyInformative) { // 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 +3459,19 @@ static void AddTemplateParameterChunks( if (FirstParameter) FirstParameter = false; - else - Result.AddChunk(CodeCompletionString::CK_Comma); + else { + if (OnlyInformative) + Result.AddInformativeChunk(", "); + else + Result.AddChunk(CodeCompletionString::CK_Comma); + } - // Add the placeholder string. - Result.AddPlaceholderChunk( - Result.getAllocator().CopyString(PlaceholderStr)); + if (OnlyInformative) + Result.AddInformativeChunk( + Result.getAllocator().CopyString(PlaceholderStr)); + else // Add the placeholder string. + Result.AddPlaceholderChunk( + Result.getAllocator().CopyString(PlaceholderStr)); } } @@ -3436,22 +3497,32 @@ static void AddQualifierToCompletionString(CodeCompletionBuilder &Result, } static void AddFunctionTypeQuals(CodeCompletionBuilder &Result, - const Qualifiers Quals) { + const Qualifiers Quals, + bool AsInformativeChunks = true) { // FIXME: Add ref-qualifier! // Handle single qualifiers without copying if (Quals.hasOnlyConst()) { - Result.AddInformativeChunk(" const"); + if (AsInformativeChunks) + Result.AddInformativeChunk(" const"); + else + Result.AddTextChunk(" const"); return; } if (Quals.hasOnlyVolatile()) { - Result.AddInformativeChunk(" volatile"); + if (AsInformativeChunks) + Result.AddInformativeChunk(" volatile"); + else + Result.AddTextChunk(" volatile"); return; } if (Quals.hasOnlyRestrict()) { - Result.AddInformativeChunk(" restrict"); + if (AsInformativeChunks) + Result.AddInformativeChunk(" restrict"); + else + Result.AddTextChunk(" restrict"); return; } @@ -3463,12 +3534,17 @@ static void AddFunctionTypeQuals(CodeCompletionBuilder &Result, QualsStr += " volatile"; if (Quals.hasRestrict()) QualsStr += " restrict"; - Result.AddInformativeChunk(Result.getAllocator().CopyString(QualsStr)); + + if (AsInformativeChunks) + 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 +3552,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 +3850,20 @@ 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); + if (!IsAddressOfOperand) + Result.AddChunk(CodeCompletionString::CK_LeftParen); + else + Result.AddInformativeChunk("("); + AddFunctionParameterChunks(PP, Policy, Function, Result, /*Start=*/0, + /*InOptional=*/false, + /*OnlyInformative=*/IsAddressOfOperand, + /*IsInDeclarationContext=*/DeclaringEntity); + if (!IsAddressOfOperand) + Result.AddChunk(CodeCompletionString::CK_RightParen); + else + Result.AddInformativeChunk(")"); + AddFunctionTypeQualsToCompletionString( + Result, Function, /*AsInformativeChunks=*/!DeclaringEntity); }; if (const auto *Function = dyn_cast<FunctionDecl>(ND)) { @@ -3849,16 +3935,34 @@ 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, /*OnlyInformative=*/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); + if (!IsAddressOfOperand) + Result.AddChunk(CodeCompletionString::CK_LeftParen); + else + Result.AddInformativeChunk("("); + AddFunctionParameterChunks(PP, Policy, Function, Result, /*Start=*/0, + /*InOptional=*/false, + /*OnlyInformative=*/IsAddressOfOperand, + /*IsInDeclarationContext=*/DeclaringEntity); + if (!IsAddressOfOperand) + Result.AddChunk(CodeCompletionString::CK_RightParen); + else + Result.AddInformativeChunk(")"); AddFunctionTypeQualsToCompletionString(Result, Function); return Result.TakeString(); } @@ -5073,7 +5177,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 +5205,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 +5249,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 +6928,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,12 +6966,23 @@ void SemaCodeCompletion::CodeCompleteQualifiedId(Scope *S, CXXScopeSpec &SS, // resolves to a dependent record. DeclContext *Ctx = SemaRef.computeDeclContext(SS, /*EnteringContext=*/true); + DeclContext *SavedContext = nullptr; + // When completing a definition, simulate that we are in class scope to access + // private methods. + if (IsInDeclarationContext && Ctx != nullptr) { + SavedContext = SemaRef.CurContext; + SemaRef.CurContext = Ctx; + } + // Try to instantiate any non-dependent declaration contexts before // we look in them. Bail out if we fail. NestedNameSpecifier NNS = SS.getScopeRep(); if (NNS && !NNS.isDependent()) { - if (Ctx == nullptr || SemaRef.RequireCompleteDeclContext(SS, Ctx)) + if (Ctx == nullptr || SemaRef.RequireCompleteDeclContext(SS, Ctx)) { + if (SavedContext) + SemaRef.CurContext = SavedContext; return; + } } ResultBuilder Results(SemaRef, CodeCompleter->getAllocator(), @@ -6906,11 +7023,15 @@ 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()); } + if (SavedContext) + SemaRef.CurContext = SavedContext; HandleCodeCompleteResults(&SemaRef, CodeCompleter, Results.getCompletionContext(), Results.data(), 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
