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/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index 1d2ef0f4f2319..7309ccfda4c47 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -2288,7 +2288,11 @@ class CXXMethodDecl : public FunctionDecl { const CXXRecordDecl *Decl); Qualifiers getMethodQualifiers() const { - return getType()->castAs()->getMethodQuals(); + if (isExplicitObjectMemberFunction()) { + return getFunctionObjectParameterType().getQualifiers(); + } else { + return getType()->castAs()->getMethodQuals(); + } } /// Retrieve the ref-qualifier associated with this method. diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp index eb1bbec82b645..ea7173d751551 100644 --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -413,6 +413,37 @@ class ResultBuilder { bool IsImpossibleToSatisfy(const NamedDecl *ND) const; //@} }; + +void AddFunctionTypeQuals(CodeCompletionBuilder &Result, + const Qualifiers Quals) { + // FIXME: Add ref-qualifier! + + // Handle single qualifiers without copying + if (Quals.hasOnlyConst()) { + Result.AddInformativeChunk(" const"); + return; + } + + if (Quals.hasOnlyVolatile()) { + Result.AddInformativeChunk(" volatile"); + return; + } + + if (Quals.hasOnlyRestrict()) { + Result.AddInformativeChunk(" restrict"); + return; + } + + // Handle multiple qualifiers. + std::string QualsStr; + if (Quals.hasConst()) + QualsStr += " const"; + if (Quals.hasVolatile()) + QualsStr += " volatile"; + if (Quals.hasRestrict()) + QualsStr += " restrict"; + Result.AddInformativeChunk(Result.getAllocator().CopyString(QualsStr)); +} } // namespace void PreferredTypeBuilder::enterReturn(Sema &S, SourceLocation Tok) { @@ -3426,40 +3457,37 @@ 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()) +// Sets the function qualifiers completion string by inspecting the explicit +// object +static void AddCXXExplicitObjectFunctionTypeQualsToCompletionString( + CodeCompletionBuilder &Result, const CXXMethodDecl *Function) { + assert(isa(Function) && + Function->hasCXXExplicitFunctionObjectParameter() && + "Valid only on CXX explicit object methods"); + + const auto Quals = Function->getFunctionObjectParameterType(); + if (!Quals.hasQualifiers()) return; - // FIXME: Add ref-qualifier! + AddFunctionTypeQuals(Result, Quals.getQualifiers()); +} - // Handle single qualifiers without copying - if (Proto->getMethodQuals().hasOnlyConst()) { - Result.AddInformativeChunk(" const"); - return; - } - if (Proto->getMethodQuals().hasOnlyVolatile()) { - Result.AddInformativeChunk(" volatile"); - return; - } +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 + AddCXXExplicitObjectFunctionTypeQualsToCompletionString(Result, + CxxMethodDecl); + } else { + const auto *Proto = Function->getType()->getAs(); + if (!Proto || !Proto->getMethodQuals()) + return; - if (Proto->getMethodQuals().hasOnlyRestrict()) { - Result.AddInformativeChunk(" restrict"); - return; + AddFunctionTypeQuals(Result, Proto->getMethodQuals()); } - - // Handle multiple qualifiers. - std::string QualsStr; - if (Proto->isConst()) - QualsStr += " const"; - if (Proto->isVolatile()) - QualsStr += " volatile"; - if (Proto->isRestrict()) - QualsStr += " restrict"; - Result.AddInformativeChunk(Result.getAllocator().CopyString(QualsStr)); } static void 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#>) +}