diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -555,7 +555,7 @@ }}, {"signatureHelpProvider", llvm::json::Object{ - {"triggerCharacters", {"(", ",", ")"}}, + {"triggerCharacters", {"(", ",", ")", "<", ">"}}, }}, {"declarationProvider", true}, {"definitionProvider", true}, 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 @@ -895,14 +895,12 @@ // part of it. int paramIndexForArg(const CodeCompleteConsumer::OverloadCandidate &Candidate, int Arg) { - int NumParams = 0; + int NumParams = Candidate.getNumParams(); if (const auto *F = Candidate.getFunction()) { - NumParams = F->getNumParams(); if (F->isVariadic()) ++NumParams; } else if (auto *T = Candidate.getFunctionType()) { if (auto *Proto = T->getAs()) { - NumParams = Proto->getNumParams(); if (Proto->isVariadic()) ++NumParams; } @@ -1016,6 +1014,9 @@ return R.Quality.Kind != OC::CK_Function; case OC::CK_FunctionTemplate: return false; + case OC::CK_Template: + assert(false && "Never see templates and other overloads mixed"); + return false; } llvm_unreachable("Unknown overload candidate type."); } @@ -1168,13 +1169,18 @@ for (unsigned I = 0; I < NumCandidates; ++I) { OverloadCandidate Candidate = Candidates[I]; - auto *Func = Candidate.getFunction(); - if (!Func || Func->getNumParams() <= CurrentArg) - continue; - auto *PVD = Func->getParamDecl(CurrentArg); - if (!PVD) + NamedDecl *Param = nullptr; + if (auto *Func = Candidate.getFunction()) { + if (CurrentArg < Func->getNumParams()) + Param = Func->getParamDecl(CurrentArg); + } else if (auto *Template = Candidate.getTemplate()) { + if (CurrentArg < Template->getTemplateParameters()->size()) + Param = Template->getTemplateParameters()->getParam(CurrentArg); + } + + if (!Param) continue; - auto *Ident = PVD->getIdentifier(); + auto *Ident = Param->getIdentifier(); if (!Ident) continue; auto Name = Ident->getName(); diff --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test --- a/clang-tools-extra/clangd/test/initialize-params.test +++ b/clang-tools-extra/clangd/test/initialize-params.test @@ -108,7 +108,9 @@ # CHECK-NEXT: "triggerCharacters": [ # CHECK-NEXT: "(", # CHECK-NEXT: ",", -# CHECK-NEXT: ")" +# CHECK-NEXT: ")", +# CHECK-NEXT: "<", +# CHECK-NEXT: ">" # CHECK-NEXT: ] # CHECK-NEXT: }, # CHECK-NEXT: "textDocumentSync": { 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 @@ -3453,6 +3453,25 @@ } } +TEST(SignatureHelp, TemplateArguments) { + std::string Top = R"cpp( + template bool foo(char); + template bool foo(float); + )cpp"; + + auto First = signatures(Top + "bool x = foo<^"); + EXPECT_THAT( + First.signatures, + UnorderedElementsAre(Sig("foo<[[typename T]], [[int]]>() -> bool"), + Sig("foo<[[int I]], [[int]]>() -> bool"))); + EXPECT_EQ(First.activeParameter, 0); + + auto Second = signatures(Top + "bool x = foo<1, ^"); + EXPECT_THAT(Second.signatures, + ElementsAre(Sig("foo<[[int I]], [[int]]>() -> bool"))); + EXPECT_EQ(Second.activeParameter, 1); +} + } // namespace } // namespace clangd } // namespace clang diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -3454,7 +3454,8 @@ bool ParseTemplateIdAfterTemplateName(bool ConsumeLastToken, SourceLocation &LAngleLoc, TemplateArgList &TemplateArgs, - SourceLocation &RAngleLoc); + SourceLocation &RAngleLoc, + TemplateTy NameHint = nullptr); bool AnnotateTemplateIdToken(TemplateTy Template, TemplateNameKind TNK, CXXScopeSpec &SS, @@ -3464,7 +3465,8 @@ bool TypeConstraint = false); void AnnotateTemplateIdTokenAsType(CXXScopeSpec &SS, bool IsClassName = false); - bool ParseTemplateArgumentList(TemplateArgList &TemplateArgs); + bool ParseTemplateArgumentList(TemplateArgList &TemplateArgs, + TemplateTy Template, SourceLocation OpenLoc); ParsedTemplateArgument ParseTemplateTemplateArgument(); ParsedTemplateArgument ParseTemplateArgument(); Decl *ParseExplicitInstantiation(DeclaratorContext Context, diff --git a/clang/include/clang/Sema/CodeCompleteConsumer.h b/clang/include/clang/Sema/CodeCompleteConsumer.h --- a/clang/include/clang/Sema/CodeCompleteConsumer.h +++ b/clang/include/clang/Sema/CodeCompleteConsumer.h @@ -1009,12 +1009,15 @@ /// The candidate is a function declaration. CK_Function, - /// The candidate is a function template. + /// The candidate is a function template, arguments are being completed. CK_FunctionTemplate, /// The "candidate" is actually a variable, expression, or block /// for which we only have a function prototype. - CK_FunctionType + CK_FunctionType, + + /// The candidate is a template, template arguments are being completed. + CK_Template, }; private: @@ -1033,6 +1036,10 @@ /// The function type that describes the entity being called, /// when Kind == CK_FunctionType. const FunctionType *Type; + + /// The template overload candidate, available when + /// Kind == CK_Template. + const TemplateDecl *Template; }; public: @@ -1045,6 +1052,9 @@ OverloadCandidate(const FunctionType *Type) : Kind(CK_FunctionType), Type(Type) {} + OverloadCandidate(const TemplateDecl *Template) + : Kind(CK_Template), Template(Template) {} + /// Determine the kind of overload candidate. CandidateKind getKind() const { return Kind; } @@ -1062,6 +1072,13 @@ /// function is stored. const FunctionType *getFunctionType() const; + const TemplateDecl *getTemplate() const { + assert(getKind() == CK_Template && "Not a template"); + return Template; + } + + unsigned getNumParams() const; + /// Create a new code-completion string that describes the function /// signature of this overload candidate. CodeCompletionString *CreateSignatureString(unsigned CurrentArg, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -12546,6 +12546,8 @@ ArrayRef ArgExprs, IdentifierInfo *II, SourceLocation OpenParLoc); + QualType ProduceTemplateArgumentSignatureHelp( + TemplateTy, ArrayRef, SourceLocation LAngleLoc); void CodeCompleteInitializer(Scope *S, Decl *D); /// Trigger code completion for a record of \p BaseType. \p InitExprs are /// expressions in the initializer list seen so far and \p D is the current diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp --- a/clang/lib/Parse/ParseExprCXX.cpp +++ b/clang/lib/Parse/ParseExprCXX.cpp @@ -2454,8 +2454,8 @@ // Parse the enclosed template argument list. SourceLocation LAngleLoc, RAngleLoc; TemplateArgList TemplateArgs; - if (ParseTemplateIdAfterTemplateName(true, LAngleLoc, TemplateArgs, - RAngleLoc)) + if (ParseTemplateIdAfterTemplateName(true, LAngleLoc, TemplateArgs, RAngleLoc, + Template)) return true; // If this is a non-template, we already issued a diagnostic. diff --git a/clang/lib/Parse/ParseTemplate.cpp b/clang/lib/Parse/ParseTemplate.cpp --- a/clang/lib/Parse/ParseTemplate.cpp +++ b/clang/lib/Parse/ParseTemplate.cpp @@ -1222,7 +1222,6 @@ return false; } - /// Parses a template-id that after the template name has /// already been parsed. /// @@ -1234,11 +1233,13 @@ /// token that forms the template-id. Otherwise, we will leave the /// last token in the stream (e.g., so that it can be replaced with an /// annotation token). -bool -Parser::ParseTemplateIdAfterTemplateName(bool ConsumeLastToken, - SourceLocation &LAngleLoc, - TemplateArgList &TemplateArgs, - SourceLocation &RAngleLoc) { +/// +/// \param NameHint is not required, and merely affects code completion. +bool Parser::ParseTemplateIdAfterTemplateName(bool ConsumeLastToken, + SourceLocation &LAngleLoc, + TemplateArgList &TemplateArgs, + SourceLocation &RAngleLoc, + TemplateTy Template) { assert(Tok.is(tok::less) && "Must have already parsed the template-name"); // Consume the '<'. @@ -1251,7 +1252,7 @@ if (!Tok.isOneOf(tok::greater, tok::greatergreater, tok::greatergreatergreater, tok::greaterequal, tok::greatergreaterequal)) - Invalid = ParseTemplateArgumentList(TemplateArgs); + Invalid = ParseTemplateArgumentList(TemplateArgs, Template, LAngleLoc); if (Invalid) { // Try to find the closing '>'. @@ -1332,8 +1333,8 @@ TemplateArgList TemplateArgs; bool ArgsInvalid = false; if (!TypeConstraint || Tok.is(tok::less)) { - ArgsInvalid = ParseTemplateIdAfterTemplateName(false, LAngleLoc, - TemplateArgs, RAngleLoc); + ArgsInvalid = ParseTemplateIdAfterTemplateName( + false, LAngleLoc, TemplateArgs, RAngleLoc, Template); // If we couldn't recover from invalid arguments, don't form an annotation // token -- we don't know how much to annotate. // FIXME: This can lead to duplicate diagnostics if we retry parsing this @@ -1585,19 +1586,34 @@ /// template-argument-list: [C++ 14.2] /// template-argument /// template-argument-list ',' template-argument -bool -Parser::ParseTemplateArgumentList(TemplateArgList &TemplateArgs) { +/// +/// \param Template is only used for code completion, and may be null. +bool Parser::ParseTemplateArgumentList(TemplateArgList &TemplateArgs, + TemplateTy Template, + SourceLocation OpenLoc) { ColonProtectionRAIIObject ColonProtection(*this, false); + auto RunSignatureHelp = [&] { + if (!Template) + return QualType(); + CalledSignatureHelp = true; + return Actions.ProduceTemplateArgumentSignatureHelp(Template, TemplateArgs, + OpenLoc); + }; + do { + PreferredType.enterFunctionArgument(Tok.getLocation(), RunSignatureHelp); ParsedTemplateArgument Arg = ParseTemplateArgument(); SourceLocation EllipsisLoc; if (TryConsumeToken(tok::ellipsis, EllipsisLoc)) Arg = Actions.ActOnPackExpansion(Arg, EllipsisLoc); - if (Arg.isInvalid()) + if (Arg.isInvalid()) { + if (PP.isCodeCompletionReached() && !CalledSignatureHelp) + RunSignatureHelp(); return true; + } // Save this template argument. TemplateArgs.push_back(Arg); diff --git a/clang/lib/Sema/CodeCompleteConsumer.cpp b/clang/lib/Sema/CodeCompleteConsumer.cpp --- a/clang/lib/Sema/CodeCompleteConsumer.cpp +++ b/clang/lib/Sema/CodeCompleteConsumer.cpp @@ -506,11 +506,22 @@ case CK_FunctionType: return Type; + + case CK_Template: + return nullptr; } llvm_unreachable("Invalid CandidateKind!"); } +unsigned CodeCompleteConsumer::OverloadCandidate::getNumParams() const { + if (Kind == CK_Template) + return Template->getTemplateParameters()->size(); + if (const auto *FPT = dyn_cast_or_null(getFunctionType())) + return FPT->getNumParams(); + return 0; +} + //===----------------------------------------------------------------------===// // Code completion consumer implementation //===----------------------------------------------------------------------===// diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -36,6 +36,7 @@ #include "clang/Sema/Lookup.h" #include "clang/Sema/Overload.h" #include "clang/Sema/ParsedAttr.h" +#include "clang/Sema/ParsedTemplate.h" #include "clang/Sema/Scope.h" #include "clang/Sema/ScopeInfo.h" #include "clang/Sema/Sema.h" @@ -3758,6 +3759,86 @@ } } +static std::string +formatTemplateParameterPlaceholder(const NamedDecl *Param, bool &Optional, + const PrintingPolicy &Policy) { + if (const auto *Type = dyn_cast(Param)) { + Optional = Type->hasDefaultArgument(); + } else if (const auto *NonType = dyn_cast(Param)) { + Optional = NonType->hasDefaultArgument(); + } else if (const auto *Template = dyn_cast(Param)) { + Optional = Template->hasDefaultArgument(); + } + std::string Result; + llvm::raw_string_ostream OS(Result); + Param->print(OS, Policy); + return Result; +} + +// A string describing the result of a template. +// +// If the template is for a function `bool foo(int)`, then returns +// `bool` and sets Suffix to `(int)`. +static std::string templateResultType(const TemplateDecl *TD, + const PrintingPolicy &Policy, + std::string &Suffix) { + if (const auto *CTD = dyn_cast(TD)) + return CTD->getTemplatedDecl()->getKindName().str(); + if (const auto *VTD = dyn_cast(TD)) + return VTD->getTemplatedDecl()->getType().getAsString(Policy); + if (const auto *FTD = dyn_cast(TD)) { + // For now, indicate this is a function, but don't attempt to list args. + // It's fiddly and potentially bulky/distracting. + Suffix = "()"; + return FTD->getTemplatedDecl()->getReturnType().getAsString(Policy); + } + if (isa(TD)) + return "type"; + if (isa(TD)) + return "class"; + if (isa(TD)) + return "concept"; + return ""; +} + +static CodeCompletionString *createTemplateSignatureString( + const TemplateDecl *TD, CodeCompletionBuilder &Builder, unsigned CurrentArg, + const PrintingPolicy &Policy) { + llvm::ArrayRef Params = TD->getTemplateParameters()->asArray(); + CodeCompletionBuilder OptionalBuilder(Builder.getAllocator(), + Builder.getCodeCompletionTUInfo()); + std::string ArgsSuffix; + std::string ResultType = templateResultType(TD, Policy, ArgsSuffix); + if (!ResultType.empty()) + Builder.AddResultTypeChunk(Builder.getAllocator().CopyString(ResultType)); + Builder.AddTextChunk( + Builder.getAllocator().CopyString(TD->getNameAsString())); + Builder.AddChunk(CodeCompletionString::CK_LeftAngle); + // Initially we're writing into the main string. Once we see an optional arg + // (with default), we're writing into the nested optional chunk. + CodeCompletionBuilder *Current = &Builder; + for (unsigned I = 0; I < Params.size(); ++I) { + bool Optional = false; + std::string Placeholder = + formatTemplateParameterPlaceholder(Params[I], Optional, Policy); + if (Optional) + Current = &OptionalBuilder; + if (I > 0) + Current->AddChunk(CodeCompletionString::CK_Comma); + Current->AddChunk(I == CurrentArg + ? CodeCompletionString::CK_CurrentParameter + : CodeCompletionString::CK_Placeholder, + Current->getAllocator().CopyString(Placeholder)); + } + // Add the optional chunk to the main string if we ever used it. + if (Current == &OptionalBuilder) + Builder.AddOptionalChunk(OptionalBuilder.TakeString()); + Builder.AddChunk(CodeCompletionString::CK_RightAngle); + if (!ArgsSuffix.empty()) + Builder.AddInformativeChunk(Builder.getAllocator().CopyString(ArgsSuffix)); + return Builder.TakeString(); +} + CodeCompletionString * CodeCompleteConsumer::OverloadCandidate::CreateSignatureString( unsigned CurrentArg, Sema &S, CodeCompletionAllocator &Allocator, @@ -3771,6 +3852,11 @@ // FIXME: Set priority, availability appropriately. CodeCompletionBuilder Result(Allocator, CCTUInfo, 1, CXAvailability_Available); + + if (getKind() == CK_Template) + return createTemplateSignatureString(getTemplate(), Result, CurrentArg, + Policy); + FunctionDecl *FDecl = getFunction(); const FunctionProtoType *Proto = dyn_cast(getFunctionType()); @@ -5844,6 +5930,7 @@ // overload candidates. QualType ParamType; for (auto &Candidate : Candidates) { + // FIXME: handle non-type-template-parameters by merging with D116326 if (const auto *FType = Candidate.getFunctionType()) if (const auto *Proto = dyn_cast(FType)) if (N < Proto->getNumParams()) { @@ -5861,8 +5948,7 @@ } static QualType -ProduceSignatureHelp(Sema &SemaRef, Scope *S, - MutableArrayRef Candidates, +ProduceSignatureHelp(Sema &SemaRef, MutableArrayRef Candidates, unsigned CurrentArg, SourceLocation OpenParLoc) { if (Candidates.empty()) return QualType(); @@ -5971,7 +6057,7 @@ } mergeCandidatesWithResults(*this, Results, CandidateSet, Loc, Args.size()); QualType ParamType = - ProduceSignatureHelp(*this, S, Results, Args.size(), OpenParLoc); + ProduceSignatureHelp(*this, Results, Args.size(), OpenParLoc); return !CandidateSet.empty() ? ParamType : QualType(); } @@ -6011,7 +6097,7 @@ SmallVector Results; mergeCandidatesWithResults(*this, Results, CandidateSet, Loc, Args.size()); - return ProduceSignatureHelp(*this, S, Results, Args.size(), OpenParLoc); + return ProduceSignatureHelp(*this, Results, Args.size(), OpenParLoc); } QualType Sema::ProduceCtorInitMemberSignatureHelp( @@ -6033,6 +6119,58 @@ return QualType(); } +static bool argMatchesTemplateParams(const ParsedTemplateArgument &Arg, + unsigned Index, + const TemplateParameterList &Params) { + const NamedDecl *Param; + if (Index < Params.size()) + Param = Params.getParam(Index); + else if (Params.hasParameterPack()) + Param = Params.asArray().back(); + else + return false; // too many args + + switch (Arg.getKind()) { + case ParsedTemplateArgument::Type: + return llvm::isa(Param); // constraints not checked + case ParsedTemplateArgument::NonType: + return llvm::isa(Param); // type not checked + case ParsedTemplateArgument::Template: + return llvm::isa(Param); // signature not checked + } +} + +QualType Sema::ProduceTemplateArgumentSignatureHelp( + TemplateTy ParsedTemplate, ArrayRef Args, + SourceLocation LAngleLoc) { + if (!CodeCompleter || !ParsedTemplate) + return QualType(); + + SmallVector Results; + auto Consider = [&](const TemplateDecl *TD) { + // Only add if the existing args are compatible with the template. + bool Matches = true; + for (unsigned I = 0; I < Args.size(); ++I) { + if (!argMatchesTemplateParams(Args[I], I, *TD->getTemplateParameters())) { + Matches = false; + break; + } + } + if (Matches) + Results.emplace_back(TD); + }; + + TemplateName Template = ParsedTemplate.get(); + if (const auto *TD = Template.getAsTemplateDecl()) { + Consider(TD); + } else if (const auto *OTS = Template.getAsOverloadedTemplate()) { + for (const NamedDecl *ND : *OTS) + if (const auto *TD = llvm::dyn_cast(ND)) + Consider(TD); + } + return ProduceSignatureHelp(*this, Results, Args.size(), LAngleLoc); +} + static QualType getDesignatedType(QualType BaseType, const Designation &Desig) { for (unsigned I = 0; I < Desig.getNumDesignators(); ++I) { if (BaseType.isNull()) diff --git a/clang/test/CodeCompletion/template-signature.cpp b/clang/test/CodeCompletion/template-signature.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeCompletion/template-signature.cpp @@ -0,0 +1,28 @@ +template float overloaded(int); +template bool overloaded(char); + +auto m = overloaded<1, 2>(0); +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:4:21 %s -o - | FileCheck -check-prefix=CHECK-CC1 %s +// CHECK-CC1: OPENING_PAREN_LOC: {{.*}}4:20 +// CHECK-CC1-DAG: OVERLOAD: [#float#]overloaded<<#int#>, char y>[#()#] +// CHECK-CC1-DAG: OVERLOAD: [#bool#]overloaded<<#class#>, int x>[#()#] +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:4:24 %s -o - | FileCheck -check-prefix=CHECK-CC2 %s +// CHECK-CC2-NOT: OVERLOAD: {{.*}}int x +// CHECK-CC2: OVERLOAD: [#float#]overloaded>[#()#] + +template int n = 0; +int val = n; +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:14:18 %s -o - | FileCheck -check-prefix=CHECK-CC3 %s +// CHECK-CC3: OVERLOAD: [#int#]n> +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:14:24 %s -o - | FileCheck -check-prefix=CHECK-CC4 %s +// CHECK-CC4: OVERLOAD: [#int#]n + +template struct Vector {}; +template class Container = Vector> +struct Collection { Container container; }; +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:22:31 %s -o - | FileCheck -check-prefix=CHECK-CC5 %s +// CHECK-CC5: OVERLOAD: [#class#]Container<<#typename E#>> +Collection collection; +// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:25:12 %s -o - | FileCheck -check-prefix=CHECK-CC6 %s +// CHECK-CC6: OVERLOAD: [#struct#]Collection<<#typename Element#>> +