Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4473,6 +4473,65 @@ TEST(CompletionTest, SkipExplicitObjectParameter) {
snippetSuffix(""))));
}
}

TEST(CompletionTest, MemberAccessInExplicitObjMemfn) {
Annotations Code(R"cpp(
struct A {
int member {};
int memberFnA(int a);
int memberFnA(this A&, float a);

void foo(this A& self) {
// Should not offer any members here, since
// it needs to be referenced through `self`.
mem$c1^;
// should offer all results
self.mem$c2^;

[&]() {
// should not offer any results
mem$c3^;
}();
}
};
)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, ElementsAre());
}
{
auto Result = codeComplete(testPath(TU.Filename), Code.point("c2"),
Preamble.get(), Inputs, Opts);

EXPECT_THAT(
Result.Completions,
UnorderedElementsAre(named("member"),
AllOf(named("memberFnA"), signature("(int a)"),
snippetSuffix("(${1:int a})")),
AllOf(named("memberFnA"), signature("(float a)"),
snippetSuffix("(${1:float a})"))));
}
{
auto Result = codeComplete(testPath(TU.Filename), Code.point("c3"),
Preamble.get(), Inputs, Opts);

EXPECT_THAT(Result.Completions, ElementsAre());
}
}
} // namespace
} // namespace clangd
} // namespace clang
33 changes: 28 additions & 5 deletions clang/lib/Sema/SemaCodeComplete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ class ResultBuilder {
/// Whether the \p ObjectTypeQualifiers field is active.
bool HasObjectTypeQualifiers;

// Whether the member function is using an explicit object parameter
bool IsExplicitObjectMemberFunction;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to rename this, wasn't sure of a good name, especially since filters use the IsXxx format.


/// The selector that we prefer.
Selector PreferredSelector;

Expand All @@ -218,8 +221,8 @@ class ResultBuilder {
LookupFilter Filter = nullptr)
: SemaRef(SemaRef), Allocator(Allocator), CCTUInfo(CCTUInfo),
Filter(Filter), AllowNestedNameSpecifiers(false),
HasObjectTypeQualifiers(false), CompletionContext(CompletionContext),
ObjCImplementation(nullptr) {
HasObjectTypeQualifiers(false), IsExplicitObjectMemberFunction(false),
CompletionContext(CompletionContext), ObjCImplementation(nullptr) {
// If this is an Objective-C instance method definition, dig out the
// corresponding implementation.
switch (CompletionContext.getKind()) {
Expand Down Expand Up @@ -275,6 +278,10 @@ class ResultBuilder {
HasObjectTypeQualifiers = true;
}

void setExplicitObjectMemberFn(bool IsExplicitObjectFn) {
IsExplicitObjectMemberFunction = IsExplicitObjectFn;
}

/// Set the preferred selector.
///
/// When an Objective-C method declaration result is added, and that
Expand Down Expand Up @@ -1428,6 +1435,15 @@ void ResultBuilder::AddResult(Result R, DeclContext *CurContext,

AdjustResultPriorityForDecl(R);

if (IsExplicitObjectMemberFunction &&
R.Kind == CodeCompletionResult::RK_Declaration &&
(isa<CXXMethodDecl>(R.Declaration) || isa<FieldDecl>(R.Declaration))) {
// If result is a member in the context of an explicit-object member
// function, drop it because it must be accessed through the object
// parameter
return;
}

if (HasObjectTypeQualifiers)
if (const auto *Method = dyn_cast<CXXMethodDecl>(R.Declaration))
if (Method->isInstance()) {
Expand Down Expand Up @@ -4636,12 +4652,19 @@ void SemaCodeCompletion::CodeCompleteOrdinaryName(
break;
}

// If we are in a C++ non-static member function, check the qualifiers on
// the member function to filter/prioritize the results list.
auto ThisType = SemaRef.getCurrentThisType();
if (!ThisType.isNull())
if (ThisType.isNull()) {
// check if function scope is an explicit object function
if (auto *MethodDecl = llvm::dyn_cast_if_present<CXXMethodDecl>(
SemaRef.getCurFunctionDecl()))
Results.setExplicitObjectMemberFn(
MethodDecl->isExplicitObjectMemberFunction());
} else {
// If we are in a C++ non-static member function, check the qualifiers on
// the member function to filter/prioritize the results list.
Results.setObjectTypeQualifiers(ThisType->getPointeeType().getQualifiers(),
VK_LValue);
}

CodeCompletionDeclConsumer Consumer(Results, SemaRef.CurContext);
SemaRef.LookupVisibleDecls(S, SemaRef.LookupOrdinaryName, Consumer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,42 @@ int func3() {

int func4() {
// TODO (&A::foo)(
(&A::bar)(
(&A::bar)()
}
Comment on lines +45 to 46
Copy link
Contributor Author

@MythreyaK MythreyaK Aug 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to add this paren, otherwise test fails

clang/test/CodeCompletion/cpp23-explicit-object.cpp:46:1: error: expected expression
   46 | }
      | ^
1 error generated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed this file from skip-explicit-object-parameter.cpp to cpp23-explicit-object.cpp and added the test here, instead of creating a new file.

// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:%(line-2):13 -std=c++23 %s | FileCheck -check-prefix=CHECK-CC5 %s
// CHECK-CC5: OVERLOAD: [#void#](<#A#>, int)

struct C {
int member {};
int memberFnA(int a);
int memberFnA(this C&, float a);

void foo(this C& self) {
// Should not offer any members here, since
// it needs to be referenced through `self`.
mem
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:%(line-1):8 -std=c++23 %s | FileCheck --allow-empty %s
// CHECK-NOT: COMPLETION: member : [#int#]member
// CHECK-NOT: COMPLETION: memberFnA : [#int#]memberFnA(<#int a#>)
// CHECK-NOT: COMPLETION: memberFnA : [#int#]memberFnA(<#float a#>)
}
void bar(this C& self) {
// should offer all results
self.mem
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:%(line-1):13 -std=c++23 %s | FileCheck -check-prefix=CHECK-CC6 %s
// CHECK-CC6: COMPLETION: member : [#int#]member
// CHECK-CC6: COMPLETION: memberFnA : [#int#]memberFnA(<#int a#>)
// CHECK-CC6: COMPLETION: memberFnA : [#int#]memberFnA(<#float a#>)
}
void baz(this C& self) {
[&]() {
// Should not offer any results
mem
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:%(line-1):10 -std=c++23 %s | FileCheck --allow-empty %s
// CHECK-NOT: COMPLETION: member : [#int#]member
// CHECK-NOT: COMPLETION: memberFnA : [#int#]memberFnA(<#int a#>)
// CHECK-NOT: COMPLETION: memberFnA : [#int#]memberFnA(<#float a#>)
}();
}
};