https://github.com/etyloppihacilem updated https://github.com/llvm/llvm-project/pull/165916
>From 6e39a3193ce99043946d34c87cfdc0574097147d 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 | 1 + .../clangd/CodeCompletionStrings.cpp | 84 +++++++++----- .../clangd/CodeCompletionStrings.h | 1 + .../clangd/unittests/CodeCompleteTests.cpp | 107 ++++++++++++++---- .../unittests/CodeCompletionStringsTests.cpp | 30 ++++- clang/include/clang/Parse/Parser.h | 26 ++++- .../include/clang/Sema/CodeCompleteConsumer.h | 14 ++- clang/include/clang/Sema/SemaCodeCompletion.h | 10 +- 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 | 84 ++++++++++---- clang/tools/libclang/CIndexCodeCompletion.cpp | 4 + 14 files changed, 306 insertions(+), 88 deletions(-) diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index f43b5e71a1dfa..907fac0c2c9bf 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -480,6 +480,7 @@ struct CodeCompletionBuilder { getSignature(*SemaCCS, &S.Signature, &S.SnippetSuffix, C.SemaResult->Kind, C.SemaResult->CursorKind, /*IncludeFunctionArguments=*/C.SemaResult->FunctionCanBeCall, + /*IsDefinition=*/C.SemaResult->DeclaringEntity, /*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..7f5680e804c41 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,7 +177,7 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature, std::string *Snippet, CodeCompletionResult::ResultKind ResultKind, CXCursorKind CursorKind, bool IncludeFunctionArguments, - std::string *RequiredQualifiers) { + bool IsDefinition, 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. unsigned CursorSnippetArg = std::numeric_limits<unsigned>::max(); @@ -184,8 +197,8 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature, unsigned SnippetArg = 0; bool HadObjCArguments = false; bool HadInformativeChunks = false; + int IsTemplateArgument = 0; - std::optional<unsigned> TruncateSnippetAt; for (const auto &Chunk : CCS) { // Informative qualifier chunks only clutter completion results, skip // them. @@ -252,26 +265,38 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature, } } break; - case CodeCompletionString::CK_Text: + case CodeCompletionString::CK_FunctionQualifier: + if (IsDefinition) // Only for definition + *Snippet += Chunk.Text; *Signature += Chunk.Text; + break; + case CodeCompletionString::CK_Text: *Snippet += Chunk.Text; + *Signature += 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 (IsDefinition) + 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 (IncludeFunctionArguments) { + ++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 (IsDefinition) { // completion without snippets + *Snippet += Chunk.Text; } break; case CodeCompletionString::CK_Informative: @@ -290,28 +315,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 + if (IsDefinition || IncludeFunctionArguments) { + IsTemplateArgument++; + *Snippet += Chunk.Text; + } + *Signature += Chunk.Text; + break; + case CodeCompletionString::CK_RightAngle: + if (IsDefinition || IncludeFunctionArguments || IsTemplateArgument) { + IsTemplateArgument--; + *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 (IncludeFunctionArguments || IsDefinition || IsTemplateArgument) + *Snippet += Chunk.Text; break; case CodeCompletionString::CK_VerticalSpace: *Snippet += Chunk.Text; @@ -319,8 +351,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..baad1346c4c20 100644 --- a/clang-tools-extra/clangd/CodeCompletionStrings.h +++ b/clang-tools-extra/clangd/CodeCompletionStrings.h @@ -53,6 +53,7 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature, std::string *Snippet, CodeCompletionResult::ResultKind ResultKind, CXCursorKind CursorKind, bool IncludeFunctionArguments = true, + bool IsDefinition = 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 267910b571279..946a9995261de 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -531,19 +531,23 @@ 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^ { + } + ; + struct Derived : Foo { using Foo::method; using Foo::generic; @@ -553,12 +557,13 @@ TEST(CompletionTest, HeuristicsForMemberFunctionCompletion) { }; struct OtherClass { - OtherClass() { + OthrClass() { Foo f; Derived d; - f.$canBeCall^ + f.$canBeCallNoStatic^ ; // Prevent parsing as 'f.f' f.Foo::$canBeCall^ + ; // Prevent parsing as 'f.f' &Foo::$canNotBeCall^ ; d.Foo::$canBeCall^ @@ -573,6 +578,7 @@ TEST(CompletionTest, HeuristicsForMemberFunctionCompletion) { f.$canBeCall^ ; // Prevent parsing as 'f.f' f.Foo::$canBeCall^ + ; // Prevent parsing as 'f.f' &Foo::$canNotBeCall^ ; d.Foo::$canBeCall^ @@ -585,39 +591,94 @@ 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)"), - snippetSuffix("<${1:typename T}, ${2:typename U}>")))); + signature("<typename T, typename U>(U nameU, V nameV)"), + snippetSuffix("")))); + 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("")))); } for (const auto &P : Code.points("canBeCall")) { auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts); EXPECT_THAT(Results.Completions, - Contains(AllOf(named("method"), signature("(int) const"), - snippetSuffix("(${1:int})")))); + Contains(AllOf(named("method"), signature("(int name) const"), + snippetSuffix("(${1:int name})")))); EXPECT_THAT( Results.Completions, - Contains(AllOf(named("generic"), signature("<typename T>(U, V)"), - snippetSuffix("<${1:typename T}>(${2:U}, ${3:V})")))); - } - - // static method will always keep the snippet - for (const auto &P : Code.points()) { - auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts); + Contains(AllOf( + named("generic"), signature("<typename T>(U nameU, V nameV)"), + snippetSuffix("<${1:typename T}>(${2:U nameU}, ${3:V nameV})")))); EXPECT_THAT(Results.Completions, - Contains(AllOf(named("staticMethod"), signature("(int)"), - snippetSuffix("(${1:int})")))); + Contains(AllOf(named("staticMethod"), signature("(int name)"), + snippetSuffix("(${1:int name})")))); EXPECT_THAT(Results.Completions, Contains(AllOf( named("staticGeneric"), signature("<typename T, int U>()"), snippetSuffix("<${1:typename T}, ${2:int U}>()")))); } + + for (const auto &P : Code.points("canBeCallNoStatic")) { + auto Results = completions(TU, P, /*IndexSymbols*/ {}, Opts); + EXPECT_THAT(Results.Completions, + Contains(AllOf(named("method"), signature("(int name) const"), + snippetSuffix("(${1:int name})")))); + EXPECT_THAT( + Results.Completions, + Contains(AllOf( + named("generic"), signature("<typename T>(U nameU, V nameV)"), + snippetSuffix("<${1:typename T}>(${2:U nameU}, ${3:V nameV})")))); + } + + for (const auto &P : Code.points("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("<typename T, typename U>(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("<typename T, int U>()")))); + } +} + +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) { @@ -4490,7 +4551,7 @@ TEST(CompletionTest, SkipExplicitObjectParameter) { EXPECT_THAT( Result.Completions, ElementsAre(AllOf(named("foo"), signature("<class self:auto>(int arg)"), - snippetSuffix("<${1:class self:auto}>")))); + snippetSuffix("")))); } { auto Result = codeComplete(testPath(TU.Filename), Code.point("c3"), diff --git a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp index de5f533d31645..019768f969992 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompletionStringsTests.cpp @@ -26,12 +26,14 @@ class CompletionStringTest : public ::testing::Test { void computeSignature(const CodeCompletionString &CCS, CodeCompletionResult::ResultKind ResultKind = CodeCompletionResult::ResultKind::RK_Declaration, - bool IncludeFunctionArguments = true) { + bool IncludeFunctionArguments = true, + bool IsDeclaration = false) { Signature.clear(); Snippet.clear(); getSignature(CCS, &Signature, &Snippet, ResultKind, /*CursorKind=*/CXCursorKind::CXCursor_NotImplemented, /*IncludeFunctionArguments=*/IncludeFunctionArguments, + /*IsDeclaration=*/IsDeclaration, /*RequiredQualifiers=*/nullptr); } @@ -158,6 +160,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, /*IsDeclaration=*/true); + // Arguments placeholders dropped from snippet, kept in signature. + EXPECT_EQ(Signature, "<typename T, int U>(arg1, arg2)"); + EXPECT_EQ(Snippet, "<typename T, int U>(arg1, arg2)"); +} + TEST_F(CompletionStringTest, DropFunctionArguments) { Builder.AddTypedTextChunk("foo"); Builder.AddChunk(CodeCompletionString::CK_LeftAngle); @@ -174,10 +198,10 @@ TEST_F(CompletionStringTest, DropFunctionArguments) { computeSignature( *Builder.TakeString(), /*ResultKind=*/CodeCompletionResult::ResultKind::RK_Declaration, - /*IncludeFunctionArguments=*/false); + /*IncludeFunctionArguments=*/false, /*IsDeclaration=*/false); // 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}>"); + EXPECT_EQ(Snippet, ""); } TEST_F(CompletionStringTest, IgnoreInformativeQualifier) { diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index f7e7b0ec51d80..3a4f4ef13c809 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -355,7 +355,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 @@ -4566,7 +4573,22 @@ 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 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..37821bfb51cda 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); diff --git a/clang/include/clang/Sema/SemaCodeCompletion.h b/clang/include/clang/Sema/SemaCodeCompletion.h index 3029e56e5cfe2..99888caad2d0c 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,8 +158,8 @@ class SemaCodeCompletion : public SemaBase { void CodeCompleteAfterIf(Scope *S, bool IsBracedThen); void CodeCompleteQualifiedId(Scope *S, CXXScopeSpec &SS, bool EnteringContext, - bool IsUsingDeclaration, QualType BaseType, - QualType PreferredType); + bool IsUsingDeclaration, bool IsAddressOfOperand, + QualType BaseType, QualType PreferredType); void CodeCompleteUsing(Scope *S); void CodeCompleteUsingDirective(Scope *S); void CodeCompleteNamespaceDecl(Scope *S); diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index 3515343202de1..ad597b5c47b68 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -902,7 +902,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, @@ -1527,7 +1527,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 7a5d28caf8521..c5bd375a5df3c 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) { 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, 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 8f6f023dd79d0..7f02d9c0bc296 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -1884,7 +1884,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) || @@ -2000,9 +2000,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 aa93507ab5c30..48b39eeb1d632 100644 --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -325,7 +325,9 @@ class ResultBuilder { /// /// \param BaseExprType the object type in a member access expression, /// if any. - bool canFunctionBeCalled(const NamedDecl *ND, QualType BaseExprType) const; + bool canFunctionBeCalled(const NamedDecl *ND, QualType BaseExprType, + bool IsBorrowedContext, + bool IsAddressOfOperand) const; /// Decide whether or not a use of member function Decl can be a call. /// @@ -369,7 +371,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 IsBorrowedContext, bool IsAddressOfOperand); /// Add a new non-declaration result to this result set. void AddResult(Result R); @@ -1342,7 +1345,13 @@ bool ResultBuilder::canCxxMethodBeCalled(const CXXMethodDecl *Method, } bool ResultBuilder::canFunctionBeCalled(const NamedDecl *ND, - QualType BaseExprType) const { + QualType BaseExprType, + bool IsBorrowedContext = false, + bool IsAddressOfOperand = false) const { + // If context is borrowed, it is always a declaration so function cannot be + // called. If we are waiting for the address of operand, it is not a call. + if (IsBorrowedContext || IsAddressOfOperand) + return false; // We apply heuristics only to CCC_Symbol: // * CCC_{Arrow,Dot}MemberAccess reflect member access expressions: // f.method() and f->method(). These are always calls. @@ -1365,7 +1374,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 IsBorrowedContext = false, + bool IsAddressOfOperand = false) { if (R.Kind != Result::RK_Declaration) { // For non-declaration results, just add the result. Results.push_back(R); @@ -1504,7 +1515,10 @@ void ResultBuilder::AddResult(Result R, DeclContext *CurContext, OverloadSet.Add(Method, Results.size()); } - R.FunctionCanBeCall = canFunctionBeCalled(R.getDeclaration(), BaseExprType); + // if Context is borrowed, we are in a defintion, meaning not a call + R.FunctionCanBeCall = canFunctionBeCalled( + R.getDeclaration(), BaseExprType, IsBorrowedContext, IsAddressOfOperand); + R.DeclaringEntity = IsBorrowedContext; // Insert this result into the set of results. Results.push_back(R); @@ -1762,7 +1776,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)), IsBorrowedContext(false), + IsAddressOfOperand(false) { NamingClass = llvm::dyn_cast<CXXRecordDecl>(InitialLookupCtx); // If BaseType was not provided explicitly, emulate implicit 'this->'. if (BaseType.isNull()) { @@ -1777,13 +1792,22 @@ class CodeCompletionDeclConsumer : public VisibleDeclConsumer { this->BaseType = BaseType; } + void setIsBorrowedContext(bool isBorrowedContext) { + IsBorrowedContext = isBorrowedContext; + } + + 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, + IsBorrowedContext, IsAddressOfOperand); } void EnteredContext(DeclContext *Ctx) override { @@ -1791,6 +1815,8 @@ class CodeCompletionDeclConsumer : public VisibleDeclConsumer { } private: + bool IsBorrowedContext; + 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 +3466,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 +3488,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 +5098,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 +5126,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 +5170,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 +6849,9 @@ 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, QualType BaseType, QualType PreferredType) { if (SS.isEmpty() || !CodeCompleter) return; @@ -6859,12 +6886,25 @@ void SemaCodeCompletion::CodeCompleteQualifiedId(Scope *S, CXXScopeSpec &SS, // resolves to a dependent record. DeclContext *Ctx = SemaRef.computeDeclContext(SS, /*EnteringContext=*/true); + // This is used to borrow context to access private methods for completion + DeclContext *SavedContext = nullptr; + if (!SemaRef.CurContext->isFunctionOrMethod() && + !SemaRef.CurContext->isRecord()) { + // We are in global scope or namespace + SavedContext = SemaRef.CurContext; + SemaRef.CurContext = Ctx; // Simulate that we are in class scope + // to access private methods + } + // 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 +6945,15 @@ void SemaCodeCompletion::CodeCompleteQualifiedId(Scope *S, CXXScopeSpec &SS, if (Ctx && (CodeCompleter->includeNamespaceLevelDecls() || !Ctx->isFileContext())) { CodeCompletionDeclConsumer Consumer(Results, Ctx, BaseType); + Consumer.setIsBorrowedContext(SavedContext != nullptr); + 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
