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,17 @@ } }; +// Remove the first template argument from Signature. +// If Signature only contains a single argument an empty string is returned. +std::string RemoveFirstTemplateArg(const std::string &Signature) { + if (Signature.empty() || Signature.front() != '<' || Signature.back() != '>') + return Signature; + std::size_t FirstComma = Signature.find(", "); + if (FirstComma == std::string::npos) + return ""; + return "<" + Signature.substr(FirstComma + 2); +} + // 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 +347,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 +454,36 @@ }); } - 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; } + + // Hack to remove first template argument in some special cases. + if (IsConcept && ContextKind == CodeCompletionContext::CCC_TopLevel) { + S.Signature = RemoveFirstTemplateArg(S.Signature); + S.SnippetSuffix = RemoveFirstTemplateArg(S.SnippetSuffix); + } + if (!Completion.Documentation) { auto SetDoc = [&](llvm::StringRef Doc) { if (!Doc.empty()) { @@ -2020,7 +2046,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,48 @@ template<$tparam^A U> int foo(); + template + int bar() requires $other^A; + template - concept b = $other^A && $other^sizeof(T) % 2 == 0 || $other^A && sizeof(T) == 1; + concept b = $other^A && $other^sizeof(T) % 2 == 0 || $other^A && sizeof(T) == 1; - $other^A auto i = 19; + $toplevel^A auto i = 19; )cpp"); TestTU TU; TU.Code = Code.code().str(); TU.ExtraArgs = {"-std=c++20"}; - std::vector Syms = {conceptSym("same_as")}; + std::vector Syms = { + conceptSym("same_as", "", + "<${1:typename Tp}, ${2:typename Up}>")}; 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("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("other")) { - EXPECT_THAT(completions(TU, P, Syms).Completions, - AllOf(Contains(named("A")), Contains(named("same_as")))) + 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; } } diff --git a/clang-tools-extra/clangd/unittests/TestIndex.h b/clang-tools-extra/clangd/unittests/TestIndex.h --- a/clang-tools-extra/clangd/unittests/TestIndex.h +++ b/clang-tools-extra/clangd/unittests/TestIndex.h @@ -20,7 +20,8 @@ // Helpers to produce fake index symbols with proper SymbolID. // USRFormat is a regex replacement string for the unqualified part of the USR. Symbol sym(llvm::StringRef QName, index::SymbolKind Kind, - llvm::StringRef USRFormat); + llvm::StringRef USRFormat, llvm::StringRef Signature = "", + llvm::StringRef CompletionSnippetSuffix = ""); // Creats a function symbol assuming no function arg. Symbol func(llvm::StringRef Name); // Creates a class symbol. @@ -34,7 +35,8 @@ // Creates a namespace symbol. Symbol ns(llvm::StringRef Name); // Create a C++20 concept symbol. -Symbol conceptSym(llvm::StringRef Name); +Symbol conceptSym(llvm::StringRef Name, llvm::StringRef Signature = "", + llvm::StringRef CompletionSnippetSuffix = ""); // Create an Objective-C symbol. Symbol objcSym(llvm::StringRef Name, index::SymbolKind Kind, diff --git a/clang-tools-extra/clangd/unittests/TestIndex.cpp b/clang-tools-extra/clangd/unittests/TestIndex.cpp --- a/clang-tools-extra/clangd/unittests/TestIndex.cpp +++ b/clang-tools-extra/clangd/unittests/TestIndex.cpp @@ -38,7 +38,8 @@ // Helpers to produce fake index symbols for memIndex() or completions(). // USRFormat is a regex replacement string for the unqualified part of the USR. Symbol sym(llvm::StringRef QName, index::SymbolKind Kind, - llvm::StringRef USRFormat) { + llvm::StringRef USRFormat, llvm::StringRef Signature, + llvm::StringRef CompletionSnippetSuffix) { Symbol Sym; std::string USR = "c:"; // We synthesize a few simple cases of USRs by hand! size_t Pos = QName.rfind("::"); @@ -55,6 +56,8 @@ Sym.SymInfo.Kind = Kind; Sym.Flags |= Symbol::IndexedForCodeCompletion; Sym.Origin = SymbolOrigin::Static; + Sym.Signature = Signature; + Sym.CompletionSnippetSuffix = CompletionSnippetSuffix; return Sym; } @@ -82,8 +85,10 @@ return sym(Name, index::SymbolKind::Namespace, "@N@\\0"); } -Symbol conceptSym(llvm::StringRef Name) { - return sym(Name, index::SymbolKind::Concept, "@CT@\\0"); +Symbol conceptSym(llvm::StringRef Name, llvm::StringRef Signature, + llvm::StringRef CompletionSnippetSuffix) { + return sym(Name, index::SymbolKind::Concept, "@CT@\\0", Signature, + CompletionSnippetSuffix); } Symbol objcSym(llvm::StringRef Name, index::SymbolKind Kind,