[clang-tools-extra] d8716cd - [CodeCompletion] (mostly) fix completion in incomplete C++ ctor initializers.

Sam McCall via cfe-commits cfe-commits at lists.llvm.org
Wed Jan 12 23:25:41 PST 2022


Author: Sam McCall
Date: 2022-01-13T08:06:35+01:00
New Revision: d8716cd7d31c64a6aaa25d43569f1c00e553ab43

URL: https://github.com/llvm/llvm-project/commit/d8716cd7d31c64a6aaa25d43569f1c00e553ab43
DIFF: https://github.com/llvm/llvm-project/commit/d8716cd7d31c64a6aaa25d43569f1c00e553ab43.diff

LOG: [CodeCompletion] (mostly) fix completion in incomplete C++ ctor initializers.

C++ member function bodies (including ctor initializers) are first captured
into a buffer and then parsed after the class is complete. (This allows
members to be referenced even if declared later).

When the boundary of the function body cannot be established, its buffer is
discarded and late-parsing never happens (it would surely fail).
For code completion this is the wrong tradeoff: the point of the parse is to
generate completions as a side-effect.
Today, when the ctor body wasn't typed yet there are no init list completions.
With this patch we parse such an init-list if it contains the completion point.

There's one caveat: the parser has to decide where to resume parsing members
after a broken init list. Often the first clear recovery point is *after* the
next member, so that member is missing from completion/signature help etc. e.g.
  struct S {
    S() m  //<- completion here
    int maaa;
    int mbbb;
  }
Here "int maaa;" is treated as part of the init list, so "maaa" is not available
as a completion. Maybe in future indentation can be used to recognize that
this is a separate member, not part of the init list.

Differential Revision: https://reviews.llvm.org/D116294

Added: 
    

Modified: 
    clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
    clang/lib/Parse/ParseCXXInlineMethods.cpp
    clang/test/CodeCompletion/ctor-initializer.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
index 52dee0fdc0e2..e3bf5ebdb0a7 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -2006,6 +2006,36 @@ TEST(CompletionTest, ScopeOfClassFieldInConstructorInitializer) {
               UnorderedElementsAre(AllOf(Scope("ns::X::"), Named("x_"))));
 }
 
+// Like other class members, constructor init lists have to parse what's below,
+// after the completion point.
+// But recovering from an incomplete constructor init list is particularly
+// tricky because the bulk of the list is not surrounded by brackets.
+TEST(CompletionTest, ConstructorInitListIncomplete) {
+  auto Results = completions(
+      R"cpp(
+        namespace ns {
+          struct X {
+            X() : x^
+            int xyz_;
+          };
+        }
+      )cpp");
+  EXPECT_THAT(Results.Completions, ElementsAre(Named("xyz_")));
+
+  Results = completions(
+      R"cpp(
+        int foo();
+
+        namespace ns {
+          struct X {
+            X() : xyz_(fo^
+            int xyz_;
+          };
+        }
+      )cpp");
+  EXPECT_THAT(Results.Completions, ElementsAre(Named("foo")));
+}
+
 TEST(CompletionTest, CodeCompletionContext) {
   auto Results = completions(
       R"cpp(
@@ -2650,9 +2680,7 @@ TEST(SignatureHelpTest, InsideArgument) {
 TEST(SignatureHelpTest, ConstructorInitializeFields) {
   {
     const auto Results = signatures(R"cpp(
-      struct A {
-        A(int);
-      };
+      struct A { A(int); };
       struct B {
         B() : a_elem(^) {}
         A a_elem;
@@ -2662,6 +2690,31 @@ TEST(SignatureHelpTest, ConstructorInitializeFields) {
                 UnorderedElementsAre(Sig("A([[int]])"), Sig("A([[A &&]])"),
                                      Sig("A([[const A &]])")));
   }
+  {
+    const auto Results = signatures(R"cpp(
+      struct A { A(int); };
+      struct B {
+        B() : a_elem(^
+        A a_elem;
+      };
+    )cpp");
+    // FIXME: currently the parser skips over the decl of a_elem as part of the
+    // (broken) init list, so we don't get signatures for the first member.
+    EXPECT_THAT(Results.signatures, IsEmpty());
+  }
+  {
+    const auto Results = signatures(R"cpp(
+      struct A { A(int); };
+      struct B {
+        B() : a_elem(^
+        int dummy_elem;
+        A a_elem;
+      };
+    )cpp");
+    EXPECT_THAT(Results.signatures,
+                UnorderedElementsAre(Sig("A([[int]])"), Sig("A([[A &&]])"),
+                                     Sig("A([[const A &]])")));
+  }
   {
     const auto Results = signatures(R"cpp(
       struct A {

diff  --git a/clang/lib/Parse/ParseCXXInlineMethods.cpp b/clang/lib/Parse/ParseCXXInlineMethods.cpp
index 360e8e0e7f50..7330c2b7593d 100644
--- a/clang/lib/Parse/ParseCXXInlineMethods.cpp
+++ b/clang/lib/Parse/ParseCXXInlineMethods.cpp
@@ -140,8 +140,22 @@ NamedDecl *Parser::ParseCXXInlineMethodDef(
   // function body.
   if (ConsumeAndStoreFunctionPrologue(Toks)) {
     // We didn't find the left-brace we expected after the
-    // constructor initializer; we already printed an error, and it's likely
-    // impossible to recover, so don't try to parse this method later.
+    // constructor initializer.
+
+    // If we're code-completing and the completion point was in the broken
+    // initializer, we want to parse it even though that will fail.
+    if (PP.isCodeCompletionEnabled() &&
+        llvm::any_of(Toks, [](const Token &Tok) {
+          return Tok.is(tok::code_completion);
+        })) {
+      // If we gave up at the completion point, the initializer list was
+      // likely truncated, so don't eat more tokens. We'll hit some extra
+      // errors, but they should be ignored in code completion.
+      return FnD;
+    }
+
+    // We already printed an error, and it's likely impossible to recover,
+    // so don't try to parse this method later.
     // Skip over the rest of the decl and back to somewhere that looks
     // reasonable.
     SkipMalformedDecl();

diff  --git a/clang/test/CodeCompletion/ctor-initializer.cpp b/clang/test/CodeCompletion/ctor-initializer.cpp
index ead99f087ca8..97620db0b755 100644
--- a/clang/test/CodeCompletion/ctor-initializer.cpp
+++ b/clang/test/CodeCompletion/ctor-initializer.cpp
@@ -103,3 +103,23 @@ struct X : Y<T> {
 // RUN: %clang_cc1 -fsyntax-only -std=c++98 -code-completion-at=%s:100:9 %s -o - | FileCheck -check-prefix=CHECK-CC11 %s
 // RUN: %clang_cc1 -fsyntax-only -std=c++14 -code-completion-at=%s:100:9 %s -o - | FileCheck -check-prefix=CHECK-CC11 %s
 // CHECK-CC11: Pattern : Y<T>(<#Y<T>#>)
+
+// Test with incomplete init lists. (Relevant as parsing is *not* cut off).
+struct Incomplete1 {
+  Incomplete1() : mem
+
+  int member1;
+  int member2;
+};
+// RUN: not %clang_cc1 -fsyntax-only -std=c++14 -code-completion-at=%s:109:19 %s -o - | FileCheck -check-prefix=CHECK-CC12 %s
+// CHECK-CC12: COMPLETION: Pattern : member1(<#int#>)
+// CHECK-CC12: COMPLETION: Pattern : member2(<#int#>)
+
+struct Incomplete2 {
+  Incomplete2() : member2(
+
+  int member1;
+  int member2;
+};
+// RUN: not %clang_cc1 -fsyntax-only -std=c++14 -code-completion-at=%s:119:27 %s -o - | FileCheck -check-prefix=CHECK-CC13 %s
+// CHECK-CC13: PREFERRED-TYPE: int


        


More information about the cfe-commits mailing list