[clangd] Drop template argument lists from completions followed by <

Now, given `template <typename T> foo() {}` when user types `fo^<int>()` the
completion snippet will not contain `<int>()`.

Also, when the next token is opening parenthesis (`(`) and completion snippet
contains template argument list, it is still emitted.

This patch complements D81380.

Related issue: https://github.com/clangd/clangd/issues/387

Reviewed By: kadircet

Differential Revision: https://reviews.llvm.org/D89870
This commit is contained in:
Kirill Bobyrev 2021-02-18 13:06:01 +01:00
parent a89035d750
commit 19db870a0d
No known key found for this signature in database
GPG Key ID: 2307C055C8384FA0
2 changed files with 45 additions and 8 deletions

View File

@ -452,18 +452,52 @@ private:
std::string summarizeSnippet() const {
if (IsUsingDeclaration)
return "";
// Suppress function argument snippets if args are already present.
if ((Completion.Kind == CompletionItemKind::Function ||
Completion.Kind == CompletionItemKind::Method ||
Completion.Kind == CompletionItemKind::Constructor) &&
NextTokenKind == tok::l_paren)
return "";
auto *Snippet = onlyValue<&BundledEntry::SnippetSuffix>();
if (!Snippet)
// All bundles are function calls.
// FIXME(ibiryukov): sometimes add template arguments to a snippet, e.g.
// we need to complete 'forward<$1>($0)'.
return "($0)";
// Suppress function argument snippets cursor is followed by left
// parenthesis (and potentially arguments) or if there are potentially
// template arguments. There are cases where it would be wrong (e.g. next
// '<' token is a comparison rather than template argument list start) but
// it is less common and suppressing snippet provides better UX.
if (Completion.Kind == CompletionItemKind::Function ||
Completion.Kind == CompletionItemKind::Method ||
Completion.Kind == CompletionItemKind::Constructor) {
// If there is a potential template argument list, drop snippet and just
// complete symbol name. Ideally, this could generate an edit that would
// paste function arguments after template argument list but it would be
// complicated. Example:
//
// fu^<int> -> function<int>
if (NextTokenKind == tok::less && Snippet->front() == '<')
return "";
// Potentially followed by argument list.
if (NextTokenKind == tok::l_paren) {
// If snippet contains template arguments we will emit them and drop
// function arguments. Example:
//
// fu^(42) -> function<int>(42);
if (Snippet->front() == '<') {
// Find matching '>'. Snippet->find('>') will not work in cases like
// template <typename T=std::vector<int>>. Hence, iterate through
// the snippet until the angle bracket balance reaches zero.
int Balance = 0;
size_t I = 0;
do {
if (Snippet->at(I) == '>')
--Balance;
else if (Snippet->at(I) == '<')
++Balance;
++I;
} while (Balance > 0);
return Snippet->substr(0, I);
}
return "";
}
}
if (EnableFunctionArgSnippets)
return *Snippet;

View File

@ -3114,10 +3114,13 @@ TEST(CompletionTest, FunctionArgsExist) {
Contains(AllOf(Labeled("Container<typename T>(int Size)"),
SnippetSuffix("<${1:typename T}>(${2:int Size})"),
Kind(CompletionItemKind::Constructor))));
// FIXME(kirillbobyrev): It would be nice to still produce the template
// snippet part: in this case it should be "<${1:typename T}>".
EXPECT_THAT(
completions(Context + "Container c = Cont^()", {}, Opts).Completions,
Contains(AllOf(Labeled("Container<typename T>(int Size)"),
SnippetSuffix("<${1:typename T}>"),
Kind(CompletionItemKind::Constructor))));
EXPECT_THAT(
completions(Context + "Container c = Cont^<int>()", {}, Opts).Completions,
Contains(AllOf(Labeled("Container<typename T>(int Size)"),
SnippetSuffix(""),
Kind(CompletionItemKind::Constructor))));