Skip to content

[clang][CodeComplete] skip explicit obj param in SignatureHelp #146649

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

MythreyaK
Copy link
Contributor

@MythreyaK MythreyaK commented Jul 2, 2025

Fixes clangd/clangd#2284.

Seems like the code segment here is similar to the one that I updated in the previous PR #146258. It seems like the code is dissimilar enough in both the function bodies that it probably isn't possible to cleanly move it into a single place to prevent repetition.

My current understanding is that this line checks that the given overload exists, but not that other overloads do not, right? How do I ensure that this is the only overload?

CHECK-CC2: OVERLOAD: [#void#]foo(int arg)

Please let me know if any other improvements can be made!

@llvmbot llvmbot added clang Clang issues not falling into any other category clang-tools-extra clangd clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jul 2, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 2, 2025

@llvm/pr-subscribers-clang-tools-extra
@llvm/pr-subscribers-clangd

@llvm/pr-subscribers-clang

Author: Mythreya Kuricheti (MythreyaK)

Changes

Fixes clangd/clangd#2284.

Seems like the code segment here is similar to the one that I updated in the previous PR #146258. It seems like the code is dissimilar enough in both the function bodies that it probably isn't possible to cleanly move it into a single place to prevent repetition.

Added 2 tests. Please let me know if any other improvements can be made!


Full diff: https://github.com/llvm/llvm-project/pull/146649.diff

3 Files Affected:

  • (modified) clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp (+28)
  • (modified) clang/lib/Sema/SemaCodeComplete.cpp (+10-1)
  • (modified) clang/test/CodeCompletion/skip-explicit-object-parameter.cpp (+18-6)
diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
index b7c64c7a06745..b18d712ee9aef 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -3266,6 +3266,34 @@ TEST(SignatureHelpTest, VariadicType) {
   }
 }
 
+TEST(SignatureHelpTest, SkipExplicitObjectParameter) {
+  Annotations Code(R"cpp(
+    struct A {
+      void foo(this auto&& self, int arg); 
+    };
+    int main() {
+      A a {};
+      a.foo(^);
+    }
+  )cpp");
+
+  auto TU = TestTU::withCode(Code.code());
+  TU.ExtraArgs = {"-std=c++23"};
+
+  MockFS FS;
+  auto Inputs = TU.inputs(FS);
+
+  auto Preamble = TU.preamble();
+  ASSERT_TRUE(Preamble);
+
+  const auto Result = signatureHelp(testPath(TU.Filename), Code.point(),
+                                    *Preamble, Inputs, MarkupKind::PlainText);
+
+  EXPECT_EQ(1, Result.signatures.size());
+
+  EXPECT_THAT(Result.signatures[0], AllOf(sig("foo([[int arg]]) -> void")));
+}
+
 TEST(CompletionTest, IncludedCompletionKinds) {
   Annotations Test(R"cpp(#include "^)cpp");
   auto TU = TestTU::withCode(Test.code());
diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp
index b5d4a94da83df..97fefd3c93897 100644
--- a/clang/lib/Sema/SemaCodeComplete.cpp
+++ b/clang/lib/Sema/SemaCodeComplete.cpp
@@ -4019,7 +4019,9 @@ static void AddOverloadParameterChunks(
       Function ? Function->getNumParams() : Prototype->getNumParams();
 
   for (unsigned P = Start; P != NumParams; ++P) {
-    if (Function && Function->getParamDecl(P)->hasDefaultArg() && !InOptional) {
+    const ParmVarDecl *Param = Function->getParamDecl(P);
+
+    if (Function && Param->hasDefaultArg() && !InOptional) {
       // When we see an optional default argument, put that argument and
       // the remaining default arguments into a new, optional string.
       CodeCompletionBuilder Opt(Result.getAllocator(),
@@ -4034,6 +4036,13 @@ static void AddOverloadParameterChunks(
       return;
     }
 
+    // C++23 introduces an explicit object parameter, a.k.a. "deducing this"
+    // Skip it for autocomplete and treat the next parameter as the first
+    // parameter
+    if (FirstParameter && Param->isExplicitObjectParameter()) {
+      continue;
+    }
+
     if (FirstParameter)
       FirstParameter = false;
     else
diff --git a/clang/test/CodeCompletion/skip-explicit-object-parameter.cpp b/clang/test/CodeCompletion/skip-explicit-object-parameter.cpp
index 55c16bb126fee..0eb71dce95849 100644
--- a/clang/test/CodeCompletion/skip-explicit-object-parameter.cpp
+++ b/clang/test/CodeCompletion/skip-explicit-object-parameter.cpp
@@ -6,9 +6,21 @@ int main() {
   A a {};
   a.
 }
-// RUN: %clang_cc1 -cc1 -fsyntax-only -code-completion-at=%s:%(line-2):5 -std=c++23 %s | FileCheck %s
-// CHECK: COMPLETION: A : A::
-// CHECK-NEXT: COMPLETION: foo : [#void#]foo(<#int arg#>)
-// CHECK-NEXT: COMPLETION: operator= : [#A &#]operator=(<#const A &#>)
-// CHECK-NEXT: COMPLETION: operator= : [#A &#]operator=(<#A &&#>)
-// CHECK-NEXT: COMPLETION: ~A : [#void#]~A()
+// RUN: %clang_cc1 -cc1 -fsyntax-only -code-completion-at=%s:%(line-2):5 -std=c++23 %s | FileCheck -check-prefix=CHECK-CC1 %s
+// CHECK-CC1: COMPLETION: A : A::
+// CHECK-NEXT-CC1: COMPLETION: foo : [#void#]foo(<#int arg#>)
+// CHECK-NEXT-CC1: COMPLETION: operator= : [#A &#]operator=(<#const A &#>)
+// CHECK-NEXT-CC1: COMPLETION: operator= : [#A &#]operator=(<#A &&#>)
+// CHECK-NEXT-CC1: COMPLETION: ~A : [#void#]~A()
+
+struct B {
+  template <typename T>
+  void foo(this T&& self, int arg);
+};
+
+int main2() {
+  B b {};
+  b.foo();
+}
+// RUN: %clang_cc1 -cc1 -fsyntax-only -code-completion-at=%s:%(line-2):9 -std=c++23 %s | FileCheck -check-prefix=CHECK-CC2 %s
+// CHECK-CC2: OVERLOAD: [#void#]foo(int arg)

@MythreyaK MythreyaK force-pushed the mythreyak/skip-expl-obj-param-signature-help branch from 592ba1a to 7f8581f Compare July 2, 2025 09:03
Copy link
Contributor

@zwuis zwuis left a comment

Choose a reason for hiding this comment

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

Thank you for the fix!

// CHECK-NEXT: COMPLETION: operator= : [#A &#]operator=(<#const A &#>)
// CHECK-NEXT: COMPLETION: operator= : [#A &#]operator=(<#A &&#>)
// CHECK-NEXT: COMPLETION: ~A : [#void#]~A()
// RUN: %clang_cc1 -cc1 -fsyntax-only -code-completion-at=%s:%(line-2):5 -std=c++23 %s | FileCheck -check-prefix=CHECK-CC1 %s
Copy link
Contributor

Choose a reason for hiding this comment

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

-cc1 can be removed.

@zwuis zwuis requested a review from HighCommander4 July 2, 2025 10:44
@cor3ntin
Copy link
Contributor

cor3ntin commented Jul 2, 2025

How does completion handle things like this?

struct S {
    void f(this S, int);
};

int main() {
    (&S::f)(S{}, 0);
}

@MythreyaK
Copy link
Contributor Author

How does completion handle things like this?

@cor3ntin Oh I totally forgot about this! Thanks! I'll check it in the evening.

@MythreyaK
Copy link
Contributor Author

Seems to be working as expected. I'll add a test case for this as well.

image
image

@MythreyaK
Copy link
Contributor Author

MythreyaK commented Jul 3, 2025

The completion for (&A::foo2) is just (A, int), not foo(A, int). I tried to add a test case, like in this godbolt example,

struct A {
    void foo1(this A self, int arg) {}
    template <typename T>
    void foo2(this T&& self, int arg, float arg2) {}
};

int main() {
    A a {};
    (&A::foo1)(a, 1);
    (&A::foo2<A&>)(a, 1, 3.4); // possible?
    // (&A::foo2<A&>)(a, 1 /* , 3.4*/); // causes a compiler crash
    return 0;
}

but causes a crash (example). So I haven't added those cases, but left a TODO. Should I leave a TODO: PR 146649 instead?

b.foo();
}
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:%(line-2):9 -std=c++23 %s | FileCheck -check-prefix=CHECK-CC2 %s
// CHECK-CC2: OVERLOAD: [#void#]foo(int arg)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

My current understanding is that this line checks that the given overload exists, but not that other overloads do not. Do I need to ensure that this is the only overload?

@cor3ntin
Copy link
Contributor

cor3ntin commented Jul 4, 2025

but causes a crash (example). So I haven't added those cases, but left a TODO. Should I leave a TODO: PR 146649 instead?

I opened 147046

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category clang-tools-extra clangd
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Signature help popup should not contain the explicit object parameter with "Deducing this"
4 participants