diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 3f1623904d8cc..7640569128172 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -4532,6 +4532,139 @@ TEST(CompletionTest, MemberAccessInExplicitObjMemfn) { EXPECT_THAT(Result.Completions, ElementsAre()); } } + +TEST(CompletionTest, ListExplicitObjectOverloads) { + Annotations Code(R"cpp( + struct S { + void foo1(int a); + void foo2(int a) const; + void foo2(this const S& self, float a); + void foo3(this const S& self, int a); + void foo4(this S& self, int a); + }; + + void S::foo1(int a) { + this->$c1^; + } + + void S::foo2(int a) const { + this->$c2^; + } + + void S::foo3(this const S& self, int a) { + self.$c3^; + } + + void S::foo4(this S& self, int a) { + self.$c4^; + } + + void test1(S s) { + s.$c5^; + } + + void test2(const S s) { + s.$c6^; + } + )cpp"); + + auto TU = TestTU::withCode(Code.code()); + TU.ExtraArgs = {"-std=c++23"}; + + auto Preamble = TU.preamble(); + ASSERT_TRUE(Preamble); + + CodeCompleteOptions Opts{}; + + MockFS FS; + auto Inputs = TU.inputs(FS); + + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c1"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo1"), signature("(int a)"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo4"), signature("(int a)"), + snippetSuffix("(${1:int a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c2"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c3"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c4"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo1"), signature("(int a)"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo4"), signature("(int a)"), + snippetSuffix("(${1:int a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c5"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo1"), signature("(int a)"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo4"), signature("(int a)"), + snippetSuffix("(${1:int a})")))); + } + { + auto Result = codeComplete(testPath(TU.Filename), Code.point("c6"), + Preamble.get(), Inputs, Opts); + EXPECT_THAT( + Result.Completions, + UnorderedElementsAre(AllOf(named("foo2"), signature("(int a) const"), + snippetSuffix("(${1:int a})")), + AllOf(named("foo2"), signature("(float a) const"), + snippetSuffix("(${1:float a})")), + AllOf(named("foo3"), signature("(int a) const"), + snippetSuffix("(${1:int a})")))); + } +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp index eb1bbec82b645..c6adead5a65f5 100644 --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -1435,6 +1435,14 @@ void ResultBuilder::AddResult(Result R, DeclContext *CurContext, AdjustResultPriorityForDecl(R); + // Account for explicit object parameter + const auto GetQualifiers = [&](const CXXMethodDecl *MethodDecl) { + if (MethodDecl->isExplicitObjectMemberFunction()) + return MethodDecl->getFunctionObjectParameterType().getQualifiers(); + else + return MethodDecl->getMethodQualifiers(); + }; + if (IsExplicitObjectMemberFunction && R.Kind == CodeCompletionResult::RK_Declaration && (isa(R.Declaration) || isa(R.Declaration))) { @@ -1447,7 +1455,7 @@ void ResultBuilder::AddResult(Result R, DeclContext *CurContext, if (HasObjectTypeQualifiers) if (const auto *Method = dyn_cast(R.Declaration)) if (Method->isInstance()) { - Qualifiers MethodQuals = Method->getMethodQualifiers(); + Qualifiers MethodQuals = GetQualifiers(Method); if (ObjectTypeQualifiers == MethodQuals) R.Priority += CCD_ObjectQualifierMatch; else if (ObjectTypeQualifiers - MethodQuals) { @@ -3426,42 +3434,57 @@ static void AddQualifierToCompletionString(CodeCompletionBuilder &Result, Result.AddTextChunk(Result.getAllocator().CopyString(PrintedNNS)); } -static void -AddFunctionTypeQualsToCompletionString(CodeCompletionBuilder &Result, - const FunctionDecl *Function) { - const auto *Proto = Function->getType()->getAs(); - if (!Proto || !Proto->getMethodQuals()) - return; - +static void AddFunctionTypeQuals(CodeCompletionBuilder &Result, + const Qualifiers Quals) { // FIXME: Add ref-qualifier! // Handle single qualifiers without copying - if (Proto->getMethodQuals().hasOnlyConst()) { + if (Quals.hasOnlyConst()) { Result.AddInformativeChunk(" const"); return; } - if (Proto->getMethodQuals().hasOnlyVolatile()) { + if (Quals.hasOnlyVolatile()) { Result.AddInformativeChunk(" volatile"); return; } - if (Proto->getMethodQuals().hasOnlyRestrict()) { + if (Quals.hasOnlyRestrict()) { Result.AddInformativeChunk(" restrict"); return; } // Handle multiple qualifiers. std::string QualsStr; - if (Proto->isConst()) + if (Quals.hasConst()) QualsStr += " const"; - if (Proto->isVolatile()) + if (Quals.hasVolatile()) QualsStr += " volatile"; - if (Proto->isRestrict()) + if (Quals.hasRestrict()) QualsStr += " restrict"; Result.AddInformativeChunk(Result.getAllocator().CopyString(QualsStr)); } +static void +AddFunctionTypeQualsToCompletionString(CodeCompletionBuilder &Result, + const FunctionDecl *Function) { + if (auto *CxxMethodDecl = llvm::dyn_cast_if_present(Function); + CxxMethodDecl && CxxMethodDecl->hasCXXExplicitFunctionObjectParameter()) { + // if explicit object method, infer quals from the object parameter + const auto Quals = CxxMethodDecl->getFunctionObjectParameterType(); + if (!Quals.hasQualifiers()) + return; + + AddFunctionTypeQuals(Result, Quals.getQualifiers()); + } else { + const auto *Proto = Function->getType()->getAs(); + if (!Proto || !Proto->getMethodQuals()) + return; + + AddFunctionTypeQuals(Result, Proto->getMethodQuals()); + } +} + static void AddFunctionExceptSpecToCompletionString(std::string &NameAndSignature, const FunctionDecl *Function) { diff --git a/clang/test/CodeCompletion/cpp23-explicit-object.cpp b/clang/test/CodeCompletion/cpp23-explicit-object.cpp index 08e5565f3d186..ea97237ecd24b 100644 --- a/clang/test/CodeCompletion/cpp23-explicit-object.cpp +++ b/clang/test/CodeCompletion/cpp23-explicit-object.cpp @@ -81,3 +81,73 @@ struct C { } }; + +struct S { + void foo1(int a); + void foo2(int a) const; + void foo2(this const S& self, float a); + void foo3(this const S& self, int a); + void foo4(this S& self, int a); +}; + +void S::foo1(int a) { + this->; +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:%(line-1):9 -std=c++23 %s | FileCheck -check-prefix=CHECK-CC7 %s +// CHECK-CC7: COMPLETION: foo1 : [#void#]foo1(<#int a#>) +// CHECK-CC7: COMPLETION: foo2 : [#void#]foo2(<#int a#>)[# const#] +// CHECK-CC7: COMPLETION: foo2 : [#void#]foo2(<#float a#>)[# const#] +// CHECK-CC7: COMPLETION: foo3 : [#void#]foo3(<#int a#>)[# const#] +// CHECK-CC7: COMPLETION: foo4 : [#void#]foo4(<#int a#>) +} + +void S::foo2(int a) const { + this->; +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:%(line-1):9 -std=c++23 %s | FileCheck -check-prefix=CHECK-CC8 %s +// CHECK-CC8: COMPLETION: foo2 : [#void#]foo2(<#int a#>)[# const#] +// CHECK-CC8: COMPLETION: foo2 : [#void#]foo2(<#float a#>)[# const#] +// CHECK-CC8: COMPLETION: foo3 : [#void#]foo3(<#int a#>)[# const#] +} + +void S::foo3(this const S& self, int a) { + self.; +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:%(line-1):8 -std=c++23 %s | FileCheck -check-prefix=CHECK-CC9 %s +// CHECK-CC9: COMPLETION: foo2 : [#void#]foo2(<#int a#>)[# const#] +// CHECK-CC9: COMPLETION: foo2 : [#void#]foo2(<#float a#>)[# const#] +// CHECK-CC9: COMPLETION: foo3 : [#void#]foo3(<#int a#>)[# const#] +} + +void S::foo4(this S& self, int a) { + self.; +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:%(line-1):8 -std=c++23 %s | FileCheck -check-prefix=CHECK-CC10 %s +// CHECK-CC10: COMPLETION: foo1 : [#void#]foo1(<#int a#>) +// CHECK-CC10: COMPLETION: foo2 : [#void#]foo2(<#int a#>)[# const#] +// CHECK-CC10: COMPLETION: foo2 : [#void#]foo2(<#float a#>)[# const#] +// CHECK-CC10: COMPLETION: foo3 : [#void#]foo3(<#int a#>)[# const#] +// CHECK-CC10: COMPLETION: foo4 : [#void#]foo4(<#int a#>) +} + +void test1(S s) { + s.; +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:%(line-1):5 -std=c++23 %s | FileCheck -check-prefix=CHECK-CC11 %s +// CHECK-CC11: COMPLETION: foo1 : [#void#]foo1(<#int a#>) +// CHECK-CC11: COMPLETION: foo2 : [#void#]foo2(<#int a#>)[# const#] +// CHECK-CC11: COMPLETION: foo2 : [#void#]foo2(<#float a#>)[# const#] +// CHECK-CC11: COMPLETION: foo3 : [#void#]foo3(<#int a#>)[# const#] +// CHECK-CC11: COMPLETION: foo4 : [#void#]foo4(<#int a#>) +} + +void test2(const S s) { + s.; +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:%(line-1):5 -std=c++23 %s | FileCheck -check-prefix=CHECK-CC12 %s +// CHECK-CC12: COMPLETION: foo2 : [#void#]foo2(<#int a#>)[# const#] +// CHECK-CC12: COMPLETION: foo2 : [#void#]foo2(<#float a#>)[# const#] +// CHECK-CC12: COMPLETION: foo3 : [#void#]foo3(<#int a#>)[# const#] +} + +void test3(S s) { + s.foo2(); +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:%(line-1):10 -std=c++23 %s | FileCheck -check-prefix=CHECK-CC13 %s +// CHECK-CC13: OVERLOAD: [#void#]foo2(<#int a#>) +// CHECK-CC13: OVERLOAD: [#void#]foo2(float a) +// TODO: foo2 should be OVERLOAD: [#void#]foo2(<#float a#>) +}