https://github.com/etyloppihacilem updated https://github.com/llvm/llvm-project/pull/165916
>From 3deeac740596ad9a60cbfc041a2b42655908c49c 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 --- clang-tools-extra/clangd/CodeComplete.cpp | 2 + .../clangd/CodeCompletionStrings.cpp | 96 +++++++++---- .../clangd/CodeCompletionStrings.h | 5 +- .../clangd/unittests/CodeCompleteTests.cpp | 132 +++++++++++++++--- .../unittests/CodeCompletionStringsTests.cpp | 31 +++- clang/include/clang/Parse/Parser.h | 27 +++- .../include/clang/Sema/CodeCompleteConsumer.h | 30 +++- 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/CodeCompleteConsumer.cpp | 13 ++ clang/lib/Sema/SemaCodeComplete.cpp | 86 +++++++++--- clang/tools/libclang/CIndexCodeCompletion.cpp | 4 + 15 files changed, 375 insertions(+), 84 deletions(-) diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index 7c390f9c8219d..15514de55a8c8 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -480,6 +480,8 @@ struct CodeCompletionBuilder { getSignature(*SemaCCS, &S.Signature, &S.SnippetSuffix, C.SemaResult->Kind, C.SemaResult->CursorKind, /*IncludeFunctionArguments=*/C.SemaResult->FunctionCanBeCall, + /*IsDefinition=*/C.SemaResult->DeclaringEntity, + /*IsAddressOfOperand=*/C.SemaResult->IsAddressOfOperand, /*RequiredQualifiers=*/&Completion.RequiredQualifier); S.ReturnType = getReturnType(*SemaCCS); if (C.SemaResult->Kind == CodeCompletionResult::RK_Declaration) diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp index 9c4241b54057a..5ede830e257b7 100644 --- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp +++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp @@ -39,16 +39,29 @@ void appendEscapeSnippet(const llvm::StringRef Text, std::string *Out) { } } -void appendOptionalChunk(const CodeCompletionString &CCS, std::string *Out) { +/// Removes the value for defaults arguments. +static void addWithoutValue(std::string *Out, const std::string &ToAdd) { + size_t Val = ToAdd.find('='); + if (Val != ToAdd.npos) + *Out += ToAdd.substr(0, Val - 1); // removing value in definition + else + *Out += ToAdd; +} + +void appendOptionalChunk(const CodeCompletionString &CCS, std::string *Out, + bool RemoveValues = false) { for (const CodeCompletionString::Chunk &C : CCS) { switch (C.Kind) { case CodeCompletionString::CK_Optional: assert(C.Optional && "Expected the optional code completion string to be non-null."); - appendOptionalChunk(*C.Optional, Out); + appendOptionalChunk(*C.Optional, Out, RemoveValues); break; default: - *Out += C.Text; + if (RemoveValues) + addWithoutValue(Out, C.Text); + else + *Out += C.Text; break; } } @@ -164,6 +177,7 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature, std::string *Snippet, CodeCompletionResult::ResultKind ResultKind, CXCursorKind CursorKind, bool IncludeFunctionArguments, + bool IsInDeclarationContext, bool IsAddressOfOperand, std::string *RequiredQualifiers) { // Placeholder with this index will be $0 to mark final cursor position. // Usually we do not add $0, so the cursor is placed at end of completed text. @@ -184,8 +198,20 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature, unsigned SnippetArg = 0; bool HadObjCArguments = false; bool HadInformativeChunks = false; + int IsTemplateArgument = 0; + + // Allow arguments with placeholders (got priority over ones without + // placeholders) + bool AddArgumentsPlaceholders = + !IsInDeclarationContext && !IsAddressOfOperand; + // Allow arguments without placeholders + bool AddArgumentsNoPlaceholders = + IsInDeclarationContext && !IsAddressOfOperand; + // Allow Template arguments (always with placeholders) + bool AddTemplateArguments = AddArgumentsPlaceholders || IsAddressOfOperand; + // Allow optional chunks, such as 'const' qualifier in definition + bool AddOptionalChunk = IsInDeclarationContext && !IsAddressOfOperand; - std::optional<unsigned> TruncateSnippetAt; for (const auto &Chunk : CCS) { // Informative qualifier chunks only clutter completion results, skip // them. @@ -252,26 +278,39 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature, } } break; + case CodeCompletionString::CK_FunctionQualifier: + if (AddOptionalChunk) // Only for definition + *Snippet += Chunk.Text; + *Signature += Chunk.Text; + break; case CodeCompletionString::CK_Text: *Signature += Chunk.Text; - *Snippet += Chunk.Text; break; case CodeCompletionString::CK_Optional: assert(Chunk.Optional); // No need to create placeholders for default arguments in Snippet. appendOptionalChunk(*Chunk.Optional, Signature); + // complete args without default value in definition + if (AddOptionalChunk) + appendOptionalChunk(*Chunk.Optional, Snippet, /*RemoveValues=*/true); break; case CodeCompletionString::CK_Placeholder: *Signature += Chunk.Text; - ++SnippetArg; - if (SnippetArg == CursorSnippetArg) { - // We'd like to make $0 a placeholder too, but vscode does not support - // this (https://github.com/microsoft/vscode/issues/152837). - *Snippet += "$0"; - } else { - *Snippet += "${" + std::to_string(SnippetArg) + ':'; - appendEscapeSnippet(Chunk.Text, Snippet); - *Snippet += '}'; + if (AddArgumentsPlaceholders || + (IsTemplateArgument && AddTemplateArguments)) { + ++SnippetArg; + if (SnippetArg == CursorSnippetArg) { + // We'd like to make $0 a placeholder too, but vscode does not support + // this (https://github.com/microsoft/vscode/issues/152837). + *Snippet += "$0"; + } else { + *Snippet += "${" + std::to_string(SnippetArg) + ':'; + appendEscapeSnippet(Chunk.Text, Snippet); + *Snippet += '}'; + } + } else if (AddArgumentsNoPlaceholders && !IsTemplateArgument) { + // no template arguments in definition + *Snippet += Chunk.Text; } break; case CodeCompletionString::CK_Informative: @@ -290,28 +329,35 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature, llvm_unreachable("Unexpected CK_CurrentParameter while collecting " "CompletionItems"); break; + case CodeCompletionString::CK_LeftAngle: + // Do not add template arguments in address of a function + IsTemplateArgument++; + if (AddTemplateArguments) + *Snippet += Chunk.Text; + *Signature += Chunk.Text; + break; + case CodeCompletionString::CK_RightAngle: + IsTemplateArgument--; + if (AddTemplateArguments) + *Snippet += Chunk.Text; + *Signature += Chunk.Text; + break; case CodeCompletionString::CK_LeftParen: - // We're assuming that a LeftParen in a declaration starts a function - // call, and arguments following the parenthesis could be discarded if - // IncludeFunctionArguments is false. - if (!IncludeFunctionArguments && - ResultKind == CodeCompletionResult::RK_Declaration) - TruncateSnippetAt.emplace(Snippet->size()); - [[fallthrough]]; case CodeCompletionString::CK_RightParen: case CodeCompletionString::CK_LeftBracket: case CodeCompletionString::CK_RightBracket: case CodeCompletionString::CK_LeftBrace: case CodeCompletionString::CK_RightBrace: - case CodeCompletionString::CK_LeftAngle: - case CodeCompletionString::CK_RightAngle: case CodeCompletionString::CK_Comma: case CodeCompletionString::CK_Colon: case CodeCompletionString::CK_SemiColon: case CodeCompletionString::CK_Equal: case CodeCompletionString::CK_HorizontalSpace: *Signature += Chunk.Text; - *Snippet += Chunk.Text; + if (AddArgumentsPlaceholders || + ((AddArgumentsNoPlaceholders && !IsTemplateArgument) // XOR + || (!AddArgumentsNoPlaceholders && IsTemplateArgument))) + *Snippet += Chunk.Text; break; case CodeCompletionString::CK_VerticalSpace: *Snippet += Chunk.Text; @@ -319,8 +365,6 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature, break; } } - if (TruncateSnippetAt) - *Snippet = Snippet->substr(0, *TruncateSnippetAt); } std::string formatDocumentation(const CodeCompletionString &CCS, diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.h b/clang-tools-extra/clangd/CodeCompletionStrings.h index fa81ad64d406c..7b69ab9c8f764 100644 --- a/clang-tools-extra/clangd/CodeCompletionStrings.h +++ b/clang-tools-extra/clangd/CodeCompletionStrings.h @@ -52,7 +52,10 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &D); void getSignature(const CodeCompletionString &CCS, std::string *Signature, std::string *Snippet, CodeCompletionResult::ResultKind ResultKind, - CXCursorKind CursorKind, bool IncludeFunctionArguments = true, + CXCursorKind CursorKind, + bool IncludeFunctionArguments = false, + bool IsInDeclarationContext = false, + bool IsAddressOfOperand = false, std::string *RequiredQualifiers = nullptr); /// Assembles formatted documentation for a completion result. This includes diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 31f2d8bd68703..675c3856f2b5f 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,128 @@ 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-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp index de5f533d31645..b155ea58ac29d 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp @@ -26,12 +26,16 @@ class CompletionStringTest : public ::testing::Test { void computeSignature(const CodeCompletionString &CCS, CodeCompletionResult::ResultKind ResultKind = CodeCompletionResult::ResultKind::RK_Declaration, - bool IncludeFunctionArguments = true) { + bool IncludeFunctionArguments = true, + bool IsDefinition = false, + bool IsAddressOfOperand = false) { Signature.clear(); Snippet.clear(); getSignature(CCS, &Signature, &Snippet, ResultKind, /*CursorKind=*/CXCursorKind::CXCursor_NotImplemented, /*IncludeFunctionArguments=*/IncludeFunctionArguments, + /*IsDefinition=*/IsDefinition, + /*IsAddressOfOperand=*/IsAddressOfOperand, /*RequiredQualifiers=*/nullptr); } @@ -158,6 +162,28 @@ TEST_F(CompletionStringTest, SnippetsInPatterns) { EXPECT_EQ(Snippet, " ${1:name} = $0;"); } +TEST_F(CompletionStringTest, DropFunctionPlaceholders) { + Builder.AddTypedTextChunk("foo"); + Builder.AddChunk(CodeCompletionString::CK_LeftAngle); + Builder.AddPlaceholderChunk("typename T"); + Builder.AddChunk(CodeCompletionString::CK_Comma); + Builder.AddPlaceholderChunk("int U"); + Builder.AddChunk(CodeCompletionString::CK_RightAngle); + Builder.AddChunk(CodeCompletionString::CK_LeftParen); + Builder.AddPlaceholderChunk("arg1"); + Builder.AddChunk(CodeCompletionString::CK_Comma); + Builder.AddPlaceholderChunk("arg2"); + Builder.AddChunk(CodeCompletionString::CK_RightParen); + + computeSignature( + *Builder.TakeString(), + /*ResultKind=*/CodeCompletionResult::ResultKind::RK_Declaration, + /*IncludeFunctionArguments=*/false, /*IsDefinition=*/true); + // Arguments placeholders dropped from snippet, kept in signature. + EXPECT_EQ(Signature, "<typename T, int U>(arg1, arg2)"); + EXPECT_EQ(Snippet, "(arg1, arg2)"); +} + TEST_F(CompletionStringTest, DropFunctionArguments) { Builder.AddTypedTextChunk("foo"); Builder.AddChunk(CodeCompletionString::CK_LeftAngle); @@ -174,7 +200,8 @@ TEST_F(CompletionStringTest, DropFunctionArguments) { computeSignature( *Builder.TakeString(), /*ResultKind=*/CodeCompletionResult::ResultKind::RK_Declaration, - /*IncludeFunctionArguments=*/false); + /*IncludeFunctionArguments=*/false, /*IsDefinition=*/false, + /*IsAddressOfOperand=*/true); // Arguments dropped from snippet, kept in signature. EXPECT_EQ(Signature, "<typename T, int U>(arg1, arg2)"); EXPECT_EQ(Snippet, "<${1:typename T}, ${2:int U}>"); diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 5ae02e2b4e8ad..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..d9423e280229b 100644 --- a/clang/include/clang/Sema/CodeCompleteConsumer.h +++ b/clang/include/clang/Sema/CodeCompleteConsumer.h @@ -473,6 +473,12 @@ class CodeCompletionString { /// A piece of text that describes something about the result but /// should not be inserted into the buffer. CK_Informative, + + /// A piece of text that holds function qualifiers. Should be inserted + /// into the buffer only in definition, not on call. + /// e.g. const for a function or a method. + CK_FunctionQualifier, + /// A piece of text that describes the type of an entity or, for /// functions and methods, the return type. CK_ResultType, @@ -534,7 +540,7 @@ class CodeCompletionString { union { /// The text string associated with a CK_Text, CK_Placeholder, - /// CK_Informative, or CK_Comma chunk. + /// CK_Informative, CK_FunctionQualifier, or CK_Comma chunk. /// The string is owned by the chunk and will be deallocated /// (with delete[]) when the chunk is destroyed. const char *Text; @@ -561,6 +567,9 @@ class CodeCompletionString { /// Create a new informative chunk. static Chunk CreateInformative(const char *Informative); + /// Create a new declaration informative chunk. + static Chunk CreateFunctionQualifier(const char *FunctionQualifier); + /// Create a new result type chunk. static Chunk CreateResultType(const char *ResultType); @@ -737,6 +746,9 @@ class CodeCompletionBuilder { /// Add a new informative chunk. void AddInformativeChunk(const char *Text); + /// Add a new function qualifier chunk. + void AddFunctionQualifierChunk(const char *Text); + /// Add a new result-type chunk. void AddResultTypeChunk(const char *ResultType); @@ -865,6 +877,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 +906,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 +918,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 +928,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 +940,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 +950,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 df9e3878bffc0..c3a933a9cc1fa 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -6371,7 +6371,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 842b52375eb14..1efaa9bb327ac 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/CodeCompleteConsumer.cpp b/clang/lib/Sema/CodeCompleteConsumer.cpp index 50a552272f421..0a445b8f6f590 100644 --- a/clang/lib/Sema/CodeCompleteConsumer.cpp +++ b/clang/lib/Sema/CodeCompleteConsumer.cpp @@ -183,6 +183,7 @@ CodeCompletionString::Chunk::Chunk(ChunkKind Kind, const char *Text) case CK_Text: case CK_Placeholder: case CK_Informative: + case CK_FunctionQualifier: case CK_ResultType: case CK_CurrentParameter: this->Text = Text; @@ -272,6 +273,12 @@ CodeCompletionString::Chunk::CreateInformative(const char *Informative) { return Chunk(CK_Informative, Informative); } +CodeCompletionString::Chunk +CodeCompletionString::Chunk::CreateFunctionQualifier( + const char *FunctionQualifier) { + return Chunk(CK_FunctionQualifier, FunctionQualifier); +} + CodeCompletionString::Chunk CodeCompletionString::Chunk::CreateResultType(const char *ResultType) { return Chunk(CK_ResultType, ResultType); @@ -326,6 +333,7 @@ std::string CodeCompletionString::getAsString() const { OS << "<#" << C.Text << "#>"; break; case CK_Informative: + case CK_FunctionQualifier: case CK_ResultType: OS << "[#" << C.Text << "#]"; break; @@ -461,6 +469,10 @@ void CodeCompletionBuilder::AddInformativeChunk(const char *Text) { Chunks.push_back(Chunk::CreateInformative(Text)); } +void CodeCompletionBuilder::AddFunctionQualifierChunk(const char *Text) { + Chunks.push_back(Chunk::CreateFunctionQualifier(Text)); +} + void CodeCompletionBuilder::AddResultTypeChunk(const char *ResultType) { Chunks.push_back(Chunk::CreateResultType(ResultType)); } @@ -727,6 +739,7 @@ static std::string getOverloadAsString(const CodeCompletionString &CCS) { for (auto &C : CCS) { switch (C.Kind) { case CodeCompletionString::CK_Informative: + case CodeCompletionString::CK_FunctionQualifier: case CodeCompletionString::CK_ResultType: OS << "[#" << C.Text << "#]"; break; diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp index 4a3559955ade3..3a5cceda0ec50 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,26 @@ void ResultBuilder::AddResult(Result R, DeclContext *CurContext, } OverloadSet.Add(Method, Results.size()); } - R.FunctionCanBeCall = canFunctionBeCalled(R.getDeclaration(), BaseExprType); + R.DeclaringEntity = IsInDeclarationContext; + R.IsAddressOfOperand = IsAddressOfOperand; + + // 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 +1783,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 +1799,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 +1822,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::)), @@ -3440,17 +3473,17 @@ static void AddFunctionTypeQuals(CodeCompletionBuilder &Result, // Handle single qualifiers without copying if (Quals.hasOnlyConst()) { - Result.AddInformativeChunk(" const"); + Result.AddFunctionQualifierChunk(" const"); return; } if (Quals.hasOnlyVolatile()) { - Result.AddInformativeChunk(" volatile"); + Result.AddFunctionQualifierChunk(" volatile"); return; } if (Quals.hasOnlyRestrict()) { - Result.AddInformativeChunk(" restrict"); + Result.AddFunctionQualifierChunk(" restrict"); return; } @@ -3462,7 +3495,7 @@ static void AddFunctionTypeQuals(CodeCompletionBuilder &Result, QualsStr += " volatile"; if (Quals.hasRestrict()) QualsStr += " restrict"; - Result.AddInformativeChunk(Result.getAllocator().CopyString(QualsStr)); + Result.AddFunctionQualifierChunk(Result.getAllocator().CopyString(QualsStr)); } static void @@ -5072,7 +5105,7 @@ static void AddLambdaCompletion(ResultBuilder &Results, /// Perform code-completion in an expression context when we know what /// type we're looking for. void SemaCodeCompletion::CodeCompleteExpression( - Scope *S, const CodeCompleteExpressionData &Data) { + Scope *S, const CodeCompleteExpressionData &Data, bool IsAddressOfOperand) { ResultBuilder Results( SemaRef, CodeCompleter->getAllocator(), CodeCompleter->getCodeCompletionTUInfo(), @@ -5100,6 +5133,7 @@ void SemaCodeCompletion::CodeCompleteExpression( Results.Ignore(Data.IgnoreDecls[I]); CodeCompletionDeclConsumer Consumer(Results, SemaRef.CurContext); + Consumer.setIsAddressOfOperand(IsAddressOfOperand); SemaRef.LookupVisibleDecls(S, Sema::LookupOrdinaryName, Consumer, CodeCompleter->includeGlobals(), CodeCompleter->loadExternal()); @@ -5143,9 +5177,11 @@ void SemaCodeCompletion::CodeCompleteExpression( void SemaCodeCompletion::CodeCompleteExpression(Scope *S, QualType PreferredType, - bool IsParenthesized) { + bool IsParenthesized, + bool IsAddressOfOperand) { return CodeCompleteExpression( - S, CodeCompleteExpressionData(PreferredType, IsParenthesized)); + S, CodeCompleteExpressionData(PreferredType, IsParenthesized), + IsAddressOfOperand); } void SemaCodeCompletion::CodeCompletePostfixExpression(Scope *S, ExprResult E, @@ -6820,11 +6856,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; @@ -6859,12 +6894,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(), @@ -6905,11 +6951,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/tools/libclang/CIndexCodeCompletion.cpp b/clang/tools/libclang/CIndexCodeCompletion.cpp index 81448b4d11342..816afd28e6568 100644 --- a/clang/tools/libclang/CIndexCodeCompletion.cpp +++ b/clang/tools/libclang/CIndexCodeCompletion.cpp @@ -70,6 +70,8 @@ clang_getCompletionChunkKind(CXCompletionString completion_string, case CodeCompletionString::CK_Placeholder: return CXCompletionChunk_Placeholder; case CodeCompletionString::CK_Informative: + case CodeCompletionString::CK_FunctionQualifier: + // as FunctionQualifier are informative, except for completion. return CXCompletionChunk_Informative; case CodeCompletionString::CK_ResultType: return CXCompletionChunk_ResultType; @@ -120,6 +122,7 @@ CXString clang_getCompletionChunkText(CXCompletionString completion_string, case CodeCompletionString::CK_Placeholder: case CodeCompletionString::CK_CurrentParameter: case CodeCompletionString::CK_Informative: + case CodeCompletionString::CK_FunctionQualifier: case CodeCompletionString::CK_LeftParen: case CodeCompletionString::CK_RightParen: case CodeCompletionString::CK_LeftBracket: @@ -159,6 +162,7 @@ clang_getCompletionChunkCompletionString(CXCompletionString completion_string, case CodeCompletionString::CK_Placeholder: case CodeCompletionString::CK_CurrentParameter: case CodeCompletionString::CK_Informative: + case CodeCompletionString::CK_FunctionQualifier: case CodeCompletionString::CK_LeftParen: case CodeCompletionString::CK_RightParen: case CodeCompletionString::CK_LeftBracket: _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
