Author: Jens Massberg Date: 2023-07-05T12:04:24+02:00 New Revision: 1af0e34477a3b4a28a1c251e527c9f75f5cf69e1
URL: https://github.com/llvm/llvm-project/commit/1af0e34477a3b4a28a1c251e527c9f75f5cf69e1 DIFF: https://github.com/llvm/llvm-project/commit/1af0e34477a3b4a28a1c251e527c9f75f5cf69e1.diff LOG: [clangd][c++20] Drop first template argument in code completion in some 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 Added: Modified: clang-tools-extra/clangd/CodeComplete.cpp clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp Removed: ################################################################################ diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index 70f2634aa7763e..68e12b2fb00167 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -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. @@ -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; @@ -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()) { @@ -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(); } diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 653408b57d927c..dd6ee442244716 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -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; } } _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits