[clangd] Don't produce snippets when completion location is followed by parenthesis

Summary:
Prevent a second pair of parenthesis from being added when there already is one
right after cursor.

Related issue and more context: https://github.com/clangd/clangd/issues/387

Reviewers: sammccall

Reviewed By: sammccall

Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, usaxena95, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D81380
This commit is contained in:
Kirill Bobyrev 2020-06-09 13:58:46 +02:00
parent 1ce831912c
commit 348364bffd
No known key found for this signature in database
GPG Key ID: 2307C055C8384FA0
2 changed files with 76 additions and 7 deletions

View File

@ -45,10 +45,12 @@
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/TokenKinds.h"
#include "clang/Format/Format.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Lex/ExternalPreprocessorSource.h"
#include "clang/Lex/Lexer.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "clang/Sema/CodeCompleteConsumer.h"
@ -255,10 +257,11 @@ struct CodeCompletionBuilder {
const IncludeInserter &Includes,
llvm::StringRef FileName,
CodeCompletionContext::Kind ContextKind,
const CodeCompleteOptions &Opts, bool GenerateSnippets)
const CodeCompleteOptions &Opts,
bool IsUsingDeclaration, tok::TokenKind NextTokenKind)
: ASTCtx(ASTCtx), ExtractDocumentation(Opts.IncludeComments),
EnableFunctionArgSnippets(Opts.EnableFunctionArgSnippets),
GenerateSnippets(GenerateSnippets) {
IsUsingDeclaration(IsUsingDeclaration), NextTokenKind(NextTokenKind) {
add(C, SemaCCS);
if (C.SemaResult) {
assert(ASTCtx);
@ -429,7 +432,13 @@ private:
}
std::string summarizeSnippet() const {
if (!GenerateSnippets)
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)
@ -488,8 +497,10 @@ private:
llvm::SmallVector<BundledEntry, 1> Bundled;
bool ExtractDocumentation;
bool EnableFunctionArgSnippets;
/// When false, no snippets are generated argument lists.
bool GenerateSnippets;
// No snippets will be generated for using declarations and when the function
// arguments are already present.
bool IsUsingDeclaration;
tok::TokenKind NextTokenKind;
};
// Determine the symbol ID for a Sema code completion result, if possible.
@ -1223,6 +1234,10 @@ class CodeCompleteFlow {
CompletionRecorder *Recorder = nullptr;
CodeCompletionContext::Kind CCContextKind = CodeCompletionContext::CCC_Other;
bool IsUsingDeclaration = false;
// The snippets will not be generated if the token following completion
// location is an opening parenthesis (tok::l_paren) because this would add
// extra parenthesis.
tok::TokenKind NextTokenKind = tok::eof;
// Counters for logging.
int NSema = 0, NIndex = 0, NSemaAndIndex = 0, NIdent = 0;
bool Incomplete = false; // Would more be available with a higher limit?
@ -1277,6 +1292,11 @@ public:
auto Style = getFormatStyleForFile(
SemaCCInput.FileName, SemaCCInput.ParseInput.Contents,
SemaCCInput.ParseInput.FSProvider->getFileSystem().get());
const auto NextToken = Lexer::findNextToken(
Recorder->CCSema->getPreprocessor().getCodeCompletionLoc(),
Recorder->CCSema->getSourceManager(), Recorder->CCSema->LangOpts);
if (NextToken)
NextTokenKind = NextToken->getKind();
// If preprocessor was run, inclusions from preprocessor callback should
// already be added to Includes.
Inserter.emplace(
@ -1694,8 +1714,7 @@ private:
if (!Builder)
Builder.emplace(Recorder ? &Recorder->CCSema->getASTContext() : nullptr,
Item, SemaCCS, QueryScopes, *Inserter, FileName,
CCContextKind, Opts,
/*GenerateSnippets=*/!IsUsingDeclaration);
CCContextKind, Opts, IsUsingDeclaration, NextTokenKind);
else
Builder->add(Item, SemaCCS);
}

View File

@ -2875,6 +2875,56 @@ TEST(AllowImplicitCompletion, All) {
}
}
TEST(CompletionTest, FunctionArgsExist) {
clangd::CodeCompleteOptions Opts;
Opts.EnableSnippets = true;
std::string Context = R"cpp(
int foo(int A);
int bar();
struct Object {
Object(int B) {}
};
template <typename T>
struct Container {
Container(int Size) {}
};
)cpp";
EXPECT_THAT(completions(Context + "int y = fo^", {}, Opts).Completions,
UnorderedElementsAre(
AllOf(Labeled("foo(int A)"), SnippetSuffix("(${1:int A})"))));
EXPECT_THAT(
completions(Context + "int y = fo^(42)", {}, Opts).Completions,
UnorderedElementsAre(AllOf(Labeled("foo(int A)"), SnippetSuffix(""))));
// FIXME(kirillbobyrev): No snippet should be produced here.
EXPECT_THAT(completions(Context + "int y = fo^o(42)", {}, Opts).Completions,
UnorderedElementsAre(
AllOf(Labeled("foo(int A)"), SnippetSuffix("(${1:int A})"))));
EXPECT_THAT(
completions(Context + "int y = ba^", {}, Opts).Completions,
UnorderedElementsAre(AllOf(Labeled("bar()"), SnippetSuffix("()"))));
EXPECT_THAT(completions(Context + "int y = ba^()", {}, Opts).Completions,
UnorderedElementsAre(AllOf(Labeled("bar()"), SnippetSuffix(""))));
EXPECT_THAT(
completions(Context + "Object o = Obj^", {}, Opts).Completions,
Contains(AllOf(Labeled("Object(int B)"), SnippetSuffix("(${1:int B})"),
Kind(CompletionItemKind::Constructor))));
EXPECT_THAT(completions(Context + "Object o = Obj^()", {}, Opts).Completions,
Contains(AllOf(Labeled("Object(int B)"), SnippetSuffix(""),
Kind(CompletionItemKind::Constructor))));
EXPECT_THAT(
completions(Context + "Container c = Cont^", {}, Opts).Completions,
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(""),
Kind(CompletionItemKind::Constructor))));
}
} // namespace
} // namespace clangd
} // namespace clang