diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp --- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp +++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp @@ -114,6 +114,7 @@ } unsigned SnippetArg = 0; bool HadObjCArguments = false; + bool HadInformativeArguments = false; for (const auto &Chunk : CCS) { // Informative qualifier chunks only clutter completion results, skip // them. @@ -129,10 +130,15 @@ // 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++). + // Objective-C methods expressions may have multiple typed-text chunks, + // so we must treat them carefully. For Objective-C methods, all + // typed-text and informative chunks will end in ':' (unless there are + // no arguments, in which case we can safely treat them as C++). + // + // Completing a method declaration itself (not a method expression) is + // similar except that we use the `RequiredQualifiers` to store the + // prefix of our snippet (Objective-C doesn't have C++ style + // qualifiers). if (!llvm::StringRef(Chunk.Text).endswith(":")) { // Treat as C++. if (RequiredQualifiers) *RequiredQualifiers = std::move(*Signature); @@ -147,6 +153,11 @@ // methods. if (!HadObjCArguments) { HadObjCArguments = true; + if (!HadInformativeArguments) { + if (RequiredQualifiers) + *RequiredQualifiers = std::move(*Signature); + Snippet->clear(); + } Signature->clear(); } else { // Subsequent argument, considered part of snippet/signature. *Signature += Chunk.Text; @@ -173,6 +184,7 @@ *Snippet += '}'; break; case CodeCompletionString::CK_Informative: + HadInformativeArguments = true; // For example, the word "const" for a const method, or the name of // the base class for methods that are part of the base class. *Signature += Chunk.Text; diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -2783,6 +2783,61 @@ EXPECT_THAT(C, ElementsAre(SnippetSuffix("${1:(unsigned int)}"))); } +TEST(CompletionTest, ObjectiveCMethodDeclaration) { + auto Results = completions(R"objc( + @interface Foo + - (int)valueForCharacter:(char)c secondArgument:(id)object; + @end + @implementation Foo + valueFor^ + @end + )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(Qualifier("- (int)"))); + EXPECT_THAT(C, ElementsAre(Signature("(char)c secondArgument:(id)object"))); +} + +TEST(CompletionTest, ObjectiveCMethodDeclarationPrefixTyped) { + auto Results = completions(R"objc( + @interface Foo + - (int)valueForCharacter:(char)c; + @end + @implementation Foo + - (int)valueFor^ + @end + )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(Signature("(char)c"))); +} + +TEST(CompletionTest, ObjectiveCMethodDeclarationFromMiddle) { + auto Results = completions(R"objc( + @interface Foo + - (int)valueForCharacter:(char)c secondArgument:(id)object; + @end + @implementation Foo + - (int)valueForCharacter:(char)c second^ + @end + )objc", + /*IndexSymbols=*/{}, + /*Opts=*/{}, "Foo.m"); + + auto C = Results.Completions; + EXPECT_THAT(C, ElementsAre(Named("secondArgument:"))); + EXPECT_THAT(C, ElementsAre(Kind(CompletionItemKind::Method))); + EXPECT_THAT(C, ElementsAre(Signature("(id)object"))); +} + TEST(CompletionTest, CursorInSnippets) { clangd::CodeCompleteOptions Options; Options.EnableSnippets = true;