[CodeCompletion] Signature help for template argument lists

Provide signature while typing template arguments: Foo< ^here >
Here the parameters are e.g. "typename x", and the result type is e.g.
"struct" (class template) or "int" (variable template) or "bool (std::string)"
(function template).

Multiple overloads are possible when a template name is used for several
overloaded function templates.

Fixes https://github.com/clangd/clangd/issues/299

Differential Revision: https://reviews.llvm.org/D116352
This commit is contained in:
Sam McCall 2021-12-29 04:16:47 +01:00
parent 3a33c0b1ce
commit cd45e8c7bc
12 changed files with 266 additions and 33 deletions

View File

@ -555,7 +555,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
}},
{"signatureHelpProvider",
llvm::json::Object{
{"triggerCharacters", {"(", ",", ")"}},
{"triggerCharacters", {"(", ",", ")", "<", ">"}},
}},
{"declarationProvider", true},
{"definitionProvider", true},

View File

@ -895,14 +895,12 @@ struct ScoredSignature {
// 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<FunctionProtoType>()) {
NumParams = Proto->getNumParams();
if (Proto->isVariadic())
++NumParams;
}
@ -1016,6 +1014,9 @@ public:
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 @@ public:
for (unsigned I = 0; I < NumCandidates; ++I) {
OverloadCandidate Candidate = Candidates[I];
auto *Func = Candidate.getFunction();
if (!Func || Func->getNumParams() <= CurrentArg)
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 *PVD = Func->getParamDecl(CurrentArg);
if (!PVD)
continue;
auto *Ident = PVD->getIdentifier();
auto *Ident = Param->getIdentifier();
if (!Ident)
continue;
auto Name = Ident->getName();

View File

@ -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": {

View File

@ -3453,6 +3453,25 @@ TEST(SignatureHelp, DocFormat) {
}
}
TEST(SignatureHelp, TemplateArguments) {
std::string Top = R"cpp(
template <typename T, int> bool foo(char);
template <int I, int> 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

View File

@ -3454,7 +3454,8 @@ private:
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 @@ private:
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,

View File

@ -1009,12 +1009,15 @@ public:
/// 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 @@ public:
/// 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 @@ public:
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 @@ public:
/// 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,

View File

@ -12549,6 +12549,8 @@ public:
ArrayRef<Expr *> ArgExprs,
IdentifierInfo *II,
SourceLocation OpenParLoc);
QualType ProduceTemplateArgumentSignatureHelp(
TemplateTy, ArrayRef<ParsedTemplateArgument>, 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

View File

@ -2454,8 +2454,8 @@ bool Parser::ParseUnqualifiedIdTemplateId(
// 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.

View File

@ -1222,7 +1222,6 @@ bool Parser::ParseGreaterThanInTemplateList(SourceLocation LAngleLoc,
return false;
}
/// Parses a template-id that after the template name has
/// already been parsed.
///
@ -1234,11 +1233,13 @@ bool Parser::ParseGreaterThanInTemplateList(SourceLocation LAngleLoc,
/// 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 @@ Parser::ParseTemplateIdAfterTemplateName(bool ConsumeLastToken,
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 @@ bool Parser::AnnotateTemplateIdToken(TemplateTy Template, TemplateNameKind TNK,
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 @@ ParsedTemplateArgument Parser::ParseTemplateArgument() {
/// 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);

View File

@ -506,11 +506,22 @@ CodeCompleteConsumer::OverloadCandidate::getFunctionType() const {
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<FunctionProtoType>(getFunctionType()))
return FPT->getNumParams();
return 0;
}
//===----------------------------------------------------------------------===//
// Code completion consumer implementation
//===----------------------------------------------------------------------===//

View File

@ -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"
@ -3757,6 +3758,78 @@ static void AddOverloadParameterChunks(ASTContext &Context,
}
}
static std::string
formatTemplateParameterPlaceholder(const NamedDecl *Param, bool &Optional,
const PrintingPolicy &Policy) {
if (const auto *Type = dyn_cast<TemplateTypeParmDecl>(Param)) {
Optional = Type->hasDefaultArgument();
} else if (const auto *NonType = dyn_cast<NonTypeTemplateParmDecl>(Param)) {
Optional = NonType->hasDefaultArgument();
} else if (const auto *Template = dyn_cast<TemplateTemplateParmDecl>(Param)) {
Optional = Template->hasDefaultArgument();
}
std::string Result;
llvm::raw_string_ostream OS(Result);
Param->print(OS, Policy);
return Result;
}
static std::string templateResultType(const TemplateDecl *TD,
const PrintingPolicy &Policy) {
if (const auto *CTD = dyn_cast<ClassTemplateDecl>(TD))
return CTD->getTemplatedDecl()->getKindName().str();
if (const auto *VTD = dyn_cast<VarTemplateDecl>(TD))
return VTD->getTemplatedDecl()->getType().getAsString(Policy);
if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(TD))
return FTD->getTemplatedDecl()->getReturnType().getAsString(Policy);
if (isa<TypeAliasTemplateDecl>(TD))
return "type";
if (isa<TemplateTemplateParmDecl>(TD))
return "class";
if (isa<ConceptDecl>(TD))
return "concept";
return "";
}
static CodeCompletionString *createTemplateSignatureString(
const TemplateDecl *TD, CodeCompletionBuilder &Builder, unsigned CurrentArg,
const PrintingPolicy &Policy) {
llvm::ArrayRef<NamedDecl *> Params = TD->getTemplateParameters()->asArray();
CodeCompletionBuilder OptionalBuilder(Builder.getAllocator(),
Builder.getCodeCompletionTUInfo());
std::string ResultType = templateResultType(TD, Policy);
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);
// For function templates, ResultType was the function's return type.
// Give some clue this is a function. (Don't show the possibly-bulky params).
if (isa<FunctionTemplateDecl>(TD))
Builder.AddInformativeChunk("()");
return Builder.TakeString();
}
CodeCompletionString *
CodeCompleteConsumer::OverloadCandidate::CreateSignatureString(
unsigned CurrentArg, Sema &S, CodeCompletionAllocator &Allocator,
@ -3770,6 +3843,11 @@ CodeCompleteConsumer::OverloadCandidate::CreateSignatureString(
// 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<FunctionProtoType>(getFunctionType());
@ -5843,6 +5921,7 @@ static QualType getParamType(Sema &SemaRef,
// 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<FunctionProtoType>(FType))
if (N < Proto->getNumParams()) {
@ -5860,8 +5939,7 @@ static QualType getParamType(Sema &SemaRef,
}
static QualType
ProduceSignatureHelp(Sema &SemaRef, Scope *S,
MutableArrayRef<ResultCandidate> Candidates,
ProduceSignatureHelp(Sema &SemaRef, MutableArrayRef<ResultCandidate> Candidates,
unsigned CurrentArg, SourceLocation OpenParLoc) {
if (Candidates.empty())
return QualType();
@ -5970,7 +6048,7 @@ QualType Sema::ProduceCallSignatureHelp(Scope *S, Expr *Fn,
}
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();
}
@ -6010,7 +6088,7 @@ QualType Sema::ProduceConstructorSignatureHelp(Scope *S, QualType Type,
SmallVector<ResultCandidate, 8> 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(
@ -6032,6 +6110,58 @@ QualType Sema::ProduceCtorInitMemberSignatureHelp(
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<TemplateTypeParmDecl>(Param); // constraints not checked
case ParsedTemplateArgument::NonType:
return llvm::isa<NonTypeTemplateParmDecl>(Param); // type not checked
case ParsedTemplateArgument::Template:
return llvm::isa<TemplateTemplateParmDecl>(Param); // signature not checked
}
}
QualType Sema::ProduceTemplateArgumentSignatureHelp(
TemplateTy ParsedTemplate, ArrayRef<ParsedTemplateArgument> Args,
SourceLocation LAngleLoc) {
if (!CodeCompleter || !ParsedTemplate)
return QualType();
SmallVector<ResultCandidate, 8> 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<TemplateDecl>(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())

View File

@ -0,0 +1,28 @@
template <int, char y> float overloaded(int);
template <class, int x> 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<int, <#char y#>>[#()#]
template <class T, T... args> int n = 0;
int val = n<int, 1, 2, 3>;
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:14:18 %s -o - | FileCheck -check-prefix=CHECK-CC3 %s
// CHECK-CC3: OVERLOAD: [#int#]n<class T, <#T ...args#>>
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:14:24 %s -o - | FileCheck -check-prefix=CHECK-CC4 %s
// CHECK-CC4: OVERLOAD: [#int#]n<class T, T ...args>
template <typename> struct Vector {};
template <typename Element, template <typename E> class Container = Vector>
struct Collection { Container<Element> 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<int, Vector> 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#>>