diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -316,6 +316,15 @@ } }; +// 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. @@ -336,7 +345,7 @@ 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; @@ -443,21 +452,40 @@ }); } - 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(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()) { @@ -2020,7 +2048,7 @@ Item, SemaCCS, AccessibleScopes, *Inserter, FileName, CCContextKind, Opts, IsUsingDeclaration, NextTokenKind); else - Builder->add(Item, SemaCCS); + Builder->add(Item, SemaCCS, CCContextKind); } return Builder->build(); } 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 @@ -3961,26 +3961,51 @@ template<$tparam^A U> int foo(); + template + int bar(T t) requires $expr^A; + template - concept b = $other^A && $other^sizeof(T) % 2 == 0 || $other^A && sizeof(T) == 1; + concept b = $expr^A && $expr^sizeof(T) % 2 == 0 || $expr^A && sizeof(T) == 1; + + $toplevel^A auto i = 19; - $other^A auto i = 19; + template<$toplevel^A auto i> void constrainedNTTP(); )cpp"); TestTU TU; TU.Code = Code.code().str(); TU.ExtraArgs = {"-std=c++20"}; - std::vector Syms = {conceptSym("same_as")}; + auto Sym = conceptSym("same_as"); + Sym.Signature = ""; + Sym.CompletionSnippetSuffix = "<${1:typename Tp}, ${2:typename Up}>"; + std::vector 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(""), + 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(""), + 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(""), + snippetSuffix("<${1:class T}>"))), + Contains(AllOf( + named("same_as"), signature(""), + snippetSuffix("<${1:typename Tp}, ${2:typename Up}>"))))) << "Completing 'requires' expression at position " << P; } }