This revision was automatically updated to reflect the committed changes.
Closed by commit rCTE346836: [clangd] Improve code completion for ObjC methods 
(authored by sammccall, committed by ).

Changed prior to commit:
  https://reviews.llvm.org/D53934?vs=173737&id=173997#toc

Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D53934

Files:
  clangd/CodeCompletionStrings.cpp
  unittests/clangd/CodeCompleteTests.cpp
  unittests/clangd/CodeCompletionStringsTests.cpp

Index: unittests/clangd/CodeCompleteTests.cpp
===================================================================
--- unittests/clangd/CodeCompleteTests.cpp
+++ unittests/clangd/CodeCompleteTests.cpp
@@ -67,6 +67,7 @@
 }
 MATCHER_P(SnippetSuffix, Text, "") { return arg.SnippetSuffix == Text; }
 MATCHER_P(Origin, OriginSet, "") { return arg.Origin == OriginSet; }
+MATCHER_P(Signature, S, "") { return arg.Signature == S; }
 
 // Shorthand for Contains(Named(Name)).
 Matcher<const std::vector<CodeCompletion> &> Has(std::string Name) {
@@ -105,15 +106,16 @@
 
 CodeCompleteResult completions(ClangdServer &Server, StringRef Text,
                                std::vector<Symbol> IndexSymbols = {},
-                               clangd::CodeCompleteOptions Opts = {}) {
+                               clangd::CodeCompleteOptions Opts = {},
+                               PathRef FilePath = "foo.cpp") {
   std::unique_ptr<SymbolIndex> OverrideIndex;
   if (!IndexSymbols.empty()) {
     assert(!Opts.Index && "both Index and IndexSymbols given!");
     OverrideIndex = memIndex(std::move(IndexSymbols));
     Opts.Index = OverrideIndex.get();
   }
 
-  auto File = testPath("foo.cpp");
+  auto File = testPath(FilePath);
   Annotations Test(Text);
   runAddDocument(Server, File, Test.code());
   auto CompletionList =
@@ -125,12 +127,14 @@
 // If IndexSymbols is non-empty, an index will be built and passed to opts.
 CodeCompleteResult completions(StringRef Text,
                                std::vector<Symbol> IndexSymbols = {},
-                               clangd::CodeCompleteOptions Opts = {}) {
+                               clangd::CodeCompleteOptions Opts = {},
+                               PathRef FilePath = "foo.cpp") {
   MockFSProvider FS;
   MockCompilationDatabase CDB;
   IgnoreDiagnostics DiagConsumer;
   ClangdServer Server(CDB, FS, DiagConsumer, ClangdServer::optsForTest());
-  return completions(Server, Text, std::move(IndexSymbols), std::move(Opts));
+  return completions(Server, Text, std::move(IndexSymbols), std::move(Opts),
+                     FilePath);
 }
 
 std::string replace(StringRef Haystack, StringRef Needle, StringRef Repl) {
@@ -2204,6 +2208,84 @@
                              {cls("naber"), cls("nx::naber")}, Opts);
   EXPECT_THAT(Results.Completions, UnorderedElementsAre());
 }
+
+TEST(CompletionTest, ObjectiveCMethodNoArguments) {
+  auto Results = completions(R"objc(
+      @interface Foo
+      @property(nonatomic, setter=setXToIgnoreComplete:) int value;
+      @end
+      Foo *foo = [Foo new]; int y = [foo v^]
+    )objc",
+    /*IndexSymbols=*/{},
+    /*Opts=*/{},
+    "Foo.m");
+
+  auto C = Results.Completions;
+  EXPECT_THAT(C, ElementsAre(Named("value")));
+  EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method)));
+  EXPECT_THAT(C, ElementsAre(ReturnType("int")));
+  EXPECT_THAT(C, ElementsAre(Signature("")));
+  EXPECT_THAT(C, ElementsAre(SnippetSuffix("")));
+}
+
+TEST(CompletionTest, ObjectiveCMethodOneArgument) {
+  auto Results = completions(R"objc(
+      @interface Foo
+      - (int)valueForCharacter:(char)c;
+      @end
+      Foo *foo = [Foo new]; int y = [foo v^]
+    )objc",
+    /*IndexSymbols=*/{},
+    /*Opts=*/{},
+    "Foo.m");
+
+  auto C = Results.Completions;
+  EXPECT_THAT(C, ElementsAre(Named("valueForCharacter:")));
+  EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method)));
+  EXPECT_THAT(C, ElementsAre(ReturnType("int")));
+  EXPECT_THAT(C, ElementsAre(Signature("(char)")));
+  EXPECT_THAT(C, ElementsAre(SnippetSuffix("${1:(char)}")));
+}
+
+TEST(CompletionTest, ObjectiveCMethodTwoArgumentsFromBeginning) {
+  auto Results = completions(R"objc(
+      @interface Foo
+      + (id)fooWithValue:(int)value fooey:(unsigned int)fooey;
+      @end
+      id val = [Foo foo^]
+    )objc",
+    /*IndexSymbols=*/{},
+    /*Opts=*/{},
+    "Foo.m");
+
+  auto C = Results.Completions;
+  EXPECT_THAT(C, ElementsAre(Named("fooWithValue:")));
+  EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method)));
+  EXPECT_THAT(C, ElementsAre(ReturnType("id")));
+  EXPECT_THAT(C, ElementsAre(Signature("(int) fooey:(unsigned int)")));
+  EXPECT_THAT(C,
+      ElementsAre(SnippetSuffix("${1:(int)} fooey:${2:(unsigned int)}")));
+}
+
+TEST(CompletionTest, ObjectiveCMethodTwoArgumentsFromMiddle) {
+  auto Results = completions(R"objc(
+      @interface Foo
+      + (id)fooWithValue:(int)value fooey:(unsigned int)fooey;
+      @end
+      id val = [Foo fooWithValue:10 f^]
+    )objc",
+    /*IndexSymbols=*/{},
+    /*Opts=*/{},
+    "Foo.m");
+
+  auto C = Results.Completions;
+  EXPECT_THAT(C, ElementsAre(Named("fooey:")));
+  EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method)));
+  EXPECT_THAT(C, ElementsAre(ReturnType("id")));
+  EXPECT_THAT(C, ElementsAre(Signature("(unsigned int)")));
+  EXPECT_THAT(C, ElementsAre(SnippetSuffix("${1:(unsigned int)}")));
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: unittests/clangd/CodeCompletionStringsTests.cpp
===================================================================
--- unittests/clangd/CodeCompletionStringsTests.cpp
+++ unittests/clangd/CodeCompletionStringsTests.cpp
@@ -109,6 +109,53 @@
   EXPECT_EQ(Snippet, "");
 }
 
+TEST_F(CompletionStringTest, ObjectiveCMethodNoArguments) {
+  Builder.AddResultTypeChunk("void");
+  Builder.AddTypedTextChunk("methodName");
+
+  auto *CCS = Builder.TakeString();
+  computeSignature(*CCS);
+  EXPECT_EQ(Signature, "");
+  EXPECT_EQ(Snippet, "");
+}
+
+TEST_F(CompletionStringTest, ObjectiveCMethodOneArgument) {
+  Builder.AddResultTypeChunk("void");
+  Builder.AddTypedTextChunk("methodWithArg:");
+  Builder.AddPlaceholderChunk("(type)");
+
+  auto *CCS = Builder.TakeString();
+  computeSignature(*CCS);
+  EXPECT_EQ(Signature, "(type)");
+  EXPECT_EQ(Snippet, "${1:(type)}");
+}
+
+TEST_F(CompletionStringTest, ObjectiveCMethodTwoArgumentsFromBeginning) {
+  Builder.AddResultTypeChunk("int");
+  Builder.AddTypedTextChunk("withFoo:");
+  Builder.AddPlaceholderChunk("(type)");
+  Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace);
+  Builder.AddTypedTextChunk("bar:");
+  Builder.AddPlaceholderChunk("(type2)");
+
+  auto *CCS = Builder.TakeString();
+  computeSignature(*CCS);
+  EXPECT_EQ(Signature, "(type) bar:(type2)");
+  EXPECT_EQ(Snippet, "${1:(type)} bar:${2:(type2)}");
+}
+
+TEST_F(CompletionStringTest, ObjectiveCMethodTwoArgumentsFromMiddle) {
+  Builder.AddResultTypeChunk("int");
+  Builder.AddInformativeChunk("withFoo:");
+  Builder.AddTypedTextChunk("bar:");
+  Builder.AddPlaceholderChunk("(type2)");
+
+  auto *CCS = Builder.TakeString();
+  computeSignature(*CCS);
+  EXPECT_EQ(Signature, "(type2)");
+  EXPECT_EQ(Snippet, "${1:(type2)}");
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: clangd/CodeCompletionStrings.cpp
===================================================================
--- clangd/CodeCompletionStrings.cpp
+++ clangd/CodeCompletionStrings.cpp
@@ -76,6 +76,7 @@
 void getSignature(const CodeCompletionString &CCS, std::string *Signature,
                   std::string *Snippet, std::string *RequiredQualifiers) {
   unsigned ArgCount = 0;
+  bool HadObjCArguments = false;
   for (const auto &Chunk : CCS) {
     // Informative qualifier chunks only clutter completion results, skip
     // them.
@@ -85,13 +86,36 @@
     switch (Chunk.Kind) {
     case CodeCompletionString::CK_TypedText:
       // The typed-text chunk is the actual name. We don't record this chunk.
-      // In general our string looks like <qualifiers><name><signature>.
-      // So once we see the name, any text we recorded so far should be
-      // reclassified as qualifiers.
-      if (RequiredQualifiers)
-        *RequiredQualifiers = std::move(*Signature);
-      Signature->clear();
-      Snippet->clear();
+      // C++:
+      //   In general our string looks like <qualifiers><name><signature>.
+      //   So once we see the name, any text we recorded so far should be
+      //   reclassified as qualifiers.
+      //
+      // Objective-C:
+      //   Objective-C methods may have multiple typed-text chunks, so we must
+      //   treat them carefully. For Objective-C methods, all typed-text chunks
+      //   will end in ':' (unless there are no arguments, in which case we
+      //   can safely treat them as C++).
+      if (!StringRef(Chunk.Text).endswith(":")) {  // Treat as C++.
+        if (RequiredQualifiers)
+          *RequiredQualifiers = std::move(*Signature);
+        Signature->clear();
+        Snippet->clear();
+      } else {  // Objective-C method with args.
+        // If this is the first TypedText to the Objective-C method, discard any
+        // text that we've previously seen (such as previous parameter selector,
+        // which will be marked as Informative text).
+        //
+        // TODO: Make previous parameters part of the signature for Objective-C
+        // methods.
+        if (!HadObjCArguments) {
+          HadObjCArguments = true;
+          Signature->clear();
+        } else {  // Subsequent argument, considered part of snippet/signature.
+          *Signature += Chunk.Text;
+          *Snippet += Chunk.Text;
+        }
+      }
       break;
     case CodeCompletionString::CK_Text:
       *Signature += Chunk.Text;
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to