Skip to content

Commit

Permalink
[clangd][c++20] Drop first template argument in code completion in so…
Browse files Browse the repository at this point in the history
…me contexts.

In case of a top level context the first template argument of a concept
should be dropped. Currently the indexer doesn't support different
signatures for different contexts (for an index entry always the default
`Symbol` context is used). Thus we add a hack which checks if we are in
a top level context and have a concept and in that case removes the
first argment of the signature and snippet suffix. If there is only a
single argument, the signature and snippet suffix are completly
removed. The check for the first argument is done by simply looking for
the first comma which should be sufficient in most cases.

Additionally extend test environment to support adding artificial index
entries with signature and completion snippet suffix.

Differential Revision: https://reviews.llvm.org/D154450
  • Loading branch information
jensmassberg committed Jul 5, 2023
1 parent 7208fde commit 1af0e34
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 12 deletions.
34 changes: 31 additions & 3 deletions clang-tools-extra/clangd/CodeComplete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,15 @@ struct ScoredBundleGreater {
}
};

// Remove the first template argument from Signature.
// If Signature only contains a single argument an empty string is returned.
std::string removeFirstTemplateArg(llvm::StringRef Signature) {
auto Rest = Signature.split(",").second;
if (Rest.empty())
return "";
return ("<" + Rest.ltrim()).str();
}

// Assembles a code completion out of a bundle of >=1 completion candidates.
// Many of the expensive strings are only computed at this point, once we know
// the candidate bundle is going to be returned.
Expand All @@ -336,7 +345,7 @@ struct CodeCompletionBuilder {
EnableFunctionArgSnippets(Opts.EnableFunctionArgSnippets),
IsUsingDeclaration(IsUsingDeclaration), NextTokenKind(NextTokenKind) {
Completion.Deprecated = true; // cleared by any non-deprecated overload.
add(C, SemaCCS);
add(C, SemaCCS, ContextKind);
if (C.SemaResult) {
assert(ASTCtx);
Completion.Origin |= SymbolOrigin::AST;
Expand Down Expand Up @@ -443,21 +452,40 @@ struct CodeCompletionBuilder {
});
}

void add(const CompletionCandidate &C, CodeCompletionString *SemaCCS) {
void add(const CompletionCandidate &C, CodeCompletionString *SemaCCS,
CodeCompletionContext::Kind ContextKind) {
assert(bool(C.SemaResult) == bool(SemaCCS));
Bundled.emplace_back();
BundledEntry &S = Bundled.back();
bool IsConcept = false;
if (C.SemaResult) {
getSignature(*SemaCCS, &S.Signature, &S.SnippetSuffix, C.SemaResult->Kind,
C.SemaResult->CursorKind, &Completion.RequiredQualifier);
if (!C.SemaResult->FunctionCanBeCall)
S.SnippetSuffix.clear();
S.ReturnType = getReturnType(*SemaCCS);
if (C.SemaResult->Kind == CodeCompletionResult::RK_Declaration)
if (const auto *D = C.SemaResult->getDeclaration())
if (isa<ConceptDecl>(D))
IsConcept = true;
} else if (C.IndexResult) {
S.Signature = std::string(C.IndexResult->Signature);
S.SnippetSuffix = std::string(C.IndexResult->CompletionSnippetSuffix);
S.ReturnType = std::string(C.IndexResult->ReturnType);
if (C.IndexResult->SymInfo.Kind == index::SymbolKind::Concept)
IsConcept = true;
}

/// When a concept is used as a type-constraint (e.g. `Iterator auto x`),
/// and in some other contexts, its first type argument is not written.
/// Drop the parameter from the signature.
if (IsConcept && ContextKind == CodeCompletionContext::CCC_TopLevel) {
S.Signature = removeFirstTemplateArg(S.Signature);
// Dropping the first placeholder from the suffix will leave a $2
// with no $1.
S.SnippetSuffix = removeFirstTemplateArg(S.SnippetSuffix);
}

if (!Completion.Documentation) {
auto SetDoc = [&](llvm::StringRef Doc) {
if (!Doc.empty()) {
Expand Down Expand Up @@ -2020,7 +2048,7 @@ class CodeCompleteFlow {
Item, SemaCCS, AccessibleScopes, *Inserter, FileName,
CCContextKind, Opts, IsUsingDeclaration, NextTokenKind);
else
Builder->add(Item, SemaCCS);
Builder->add(Item, SemaCCS, CCContextKind);
}
return Builder->build();
}
Expand Down
46 changes: 37 additions & 9 deletions clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3961,26 +3961,54 @@ TEST(CompletionTest, Concepts) {
template<$tparam^A U>
int foo();
template<typename T>
int bar(T t) requires $expr^A<int>;
template<class T>
concept b = $other^A<T> && $other^sizeof(T) % 2 == 0 || $other^A<T> && sizeof(T) == 1;
concept b = $expr^A && $expr^sizeof(T) % 2 == 0 || $expr^A && sizeof(T) == 1;
$toplevel^A auto i = 19;
template<$toplevel^A auto i> void constrainedNTTP();
$other^A<T> auto i = 19;
// FIXME: The first parameter should be dropped in this case.
void abbreviated($expr^A auto x) {}
)cpp");
TestTU TU;
TU.Code = Code.code().str();
TU.ExtraArgs = {"-std=c++20"};

std::vector<Symbol> Syms = {conceptSym("same_as")};
auto Sym = conceptSym("same_as");
Sym.Signature = "<typename Tp, typename Up>";
Sym.CompletionSnippetSuffix = "<${1:typename Tp}, ${2:typename Up}>";
std::vector<Symbol> Syms = {Sym};
for (auto P : Code.points("tparam")) {
ASSERT_THAT(completions(TU, P, Syms).Completions,
AllOf(Contains(named("A")), Contains(named("same_as")),
Contains(named("class")), Contains(named("typename"))))
ASSERT_THAT(
completions(TU, P, Syms).Completions,
AllOf(Contains(AllOf(named("A"), signature(""), snippetSuffix(""))),
Contains(AllOf(named("same_as"), signature("<typename Up>"),
snippetSuffix("<${2:typename Up}>"))),
Contains(named("class")), Contains(named("typename"))))
<< "Completing template parameter at position " << P;
}

for (auto P : Code.points("other")) {
EXPECT_THAT(completions(TU, P, Syms).Completions,
AllOf(Contains(named("A")), Contains(named("same_as"))))
for (auto P : Code.points("toplevel")) {
EXPECT_THAT(
completions(TU, P, Syms).Completions,
AllOf(Contains(AllOf(named("A"), signature(""), snippetSuffix(""))),
Contains(AllOf(named("same_as"), signature("<typename Up>"),
snippetSuffix("<${2:typename Up}>")))))
<< "Completing 'requires' expression at position " << P;
}

for (auto P : Code.points("expr")) {
EXPECT_THAT(
completions(TU, P, Syms).Completions,
AllOf(Contains(AllOf(named("A"), signature("<class T>"),
snippetSuffix("<${1:class T}>"))),
Contains(AllOf(
named("same_as"), signature("<typename Tp, typename Up>"),
snippetSuffix("<${1:typename Tp}, ${2:typename Up}>")))))
<< "Completing 'requires' expression at position " << P;
}
}
Expand Down

0 comments on commit 1af0e34

Please sign in to comment.