Skip to content

Commit 55a7931

Browse files
committed
[clang][clangd] Improve signature help for variadic functions.
This covers both C-style variadic functions and template variadic w/ parameter packs. Previously we would return no signatures when working with template variadic functions once activeParameter reached the position of the parameter pack (except when it was the only param, then we'd still show it when no arguments were given). With this commit, we now show signathure help correctly. Additionally, this commit fixes the activeParameter value in LSP output of clangd in the presence of variadic functions (both kinds). LSP does not allow the activeParamter to be higher than the number of parameters in the active signature. With "..." or parameter pack being just one argument, for all but first argument passed to "..." we'd report incorrect activeParameter value. Clients such as VSCode would then treat it as 0, as suggested in the spec) and highlight the wrong parameter. In the future, we should add support for per-signature activeParamter value, which exists in LSP since 3.16.0. This is not part of this commit. Differential Revision: https://reviews.llvm.org/D111318
1 parent b2ad420 commit 55a7931

File tree

6 files changed

+186
-3
lines changed

6 files changed

+186
-3
lines changed

clang-tools-extra/clangd/CodeComplete.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,28 @@ struct ScoredSignature {
872872
SignatureQualitySignals Quality;
873873
};
874874

875+
// Returns the index of the parameter matching argument number "Arg.
876+
// This is usually just "Arg", except for variadic functions/templates, where
877+
// "Arg" might be higher than the number of parameters. When that happens, we
878+
// assume the last parameter is variadic and assume all further args are
879+
// part of it.
880+
int paramIndexForArg(const CodeCompleteConsumer::OverloadCandidate &Candidate,
881+
int Arg) {
882+
int NumParams = 0;
883+
if (const auto *F = Candidate.getFunction()) {
884+
NumParams = F->getNumParams();
885+
if (F->isVariadic())
886+
++NumParams;
887+
} else if (auto *T = Candidate.getFunctionType()) {
888+
if (auto *Proto = T->getAs<FunctionProtoType>()) {
889+
NumParams = Proto->getNumParams();
890+
if (Proto->isVariadic())
891+
++NumParams;
892+
}
893+
}
894+
return std::min(Arg, std::max(NumParams - 1, 0));
895+
}
896+
875897
class SignatureHelpCollector final : public CodeCompleteConsumer {
876898
public:
877899
SignatureHelpCollector(const clang::CodeCompleteOptions &CodeCompleteOpts,
@@ -902,7 +924,9 @@ class SignatureHelpCollector final : public CodeCompleteConsumer {
902924
SigHelp.activeSignature = 0;
903925
assert(CurrentArg <= (unsigned)std::numeric_limits<int>::max() &&
904926
"too many arguments");
927+
905928
SigHelp.activeParameter = static_cast<int>(CurrentArg);
929+
906930
for (unsigned I = 0; I < NumCandidates; ++I) {
907931
OverloadCandidate Candidate = Candidates[I];
908932
// We want to avoid showing instantiated signatures, because they may be
@@ -912,6 +936,14 @@ class SignatureHelpCollector final : public CodeCompleteConsumer {
912936
if (auto *Pattern = Func->getTemplateInstantiationPattern())
913937
Candidate = OverloadCandidate(Pattern);
914938
}
939+
if (static_cast<int>(I) == SigHelp.activeSignature) {
940+
// The activeParameter in LSP relates to the activeSignature. There is
941+
// another, per-signature field, but we currently do not use it and not
942+
// all clients might support it.
943+
// FIXME: Add support for per-signature activeParameter field.
944+
SigHelp.activeParameter =
945+
paramIndexForArg(Candidate, SigHelp.activeParameter);
946+
}
915947

916948
const auto *CCS = Candidate.CreateSignatureString(
917949
CurrentArg, S, *Allocator, CCTUInfo, true);

clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2622,6 +2622,104 @@ TEST(SignatureHelpTest, ConstructorInitializeFields) {
26222622
}
26232623
}
26242624

2625+
TEST(SignatureHelpTest, Variadic) {
2626+
const std::string Header = R"cpp(
2627+
void fun(int x, ...) {}
2628+
void test() {)cpp";
2629+
const std::string ExpectedSig = "fun([[int x]], [[...]]) -> void";
2630+
2631+
{
2632+
const auto Result = signatures(Header + "fun(^);}");
2633+
EXPECT_EQ(0, Result.activeParameter);
2634+
EXPECT_THAT(Result.signatures, UnorderedElementsAre(Sig(ExpectedSig)));
2635+
}
2636+
{
2637+
const auto Result = signatures(Header + "fun(1, ^);}");
2638+
EXPECT_EQ(1, Result.activeParameter);
2639+
EXPECT_THAT(Result.signatures, UnorderedElementsAre(Sig(ExpectedSig)));
2640+
}
2641+
{
2642+
const auto Result = signatures(Header + "fun(1, 2, ^);}");
2643+
EXPECT_EQ(1, Result.activeParameter);
2644+
EXPECT_THAT(Result.signatures, UnorderedElementsAre(Sig(ExpectedSig)));
2645+
}
2646+
}
2647+
2648+
TEST(SignatureHelpTest, VariadicTemplate) {
2649+
const std::string Header = R"cpp(
2650+
template<typename T, typename ...Args>
2651+
void fun(T t, Args ...args) {}
2652+
void test() {)cpp";
2653+
const std::string ExpectedSig = "fun([[T t]], [[Args args...]]) -> void";
2654+
2655+
{
2656+
const auto Result = signatures(Header + "fun(^);}");
2657+
EXPECT_EQ(0, Result.activeParameter);
2658+
EXPECT_THAT(Result.signatures, UnorderedElementsAre(Sig(ExpectedSig)));
2659+
}
2660+
{
2661+
const auto Result = signatures(Header + "fun(1, ^);}");
2662+
EXPECT_EQ(1, Result.activeParameter);
2663+
EXPECT_THAT(Result.signatures, UnorderedElementsAre(Sig(ExpectedSig)));
2664+
}
2665+
{
2666+
const auto Result = signatures(Header + "fun(1, 2, ^);}");
2667+
EXPECT_EQ(1, Result.activeParameter);
2668+
EXPECT_THAT(Result.signatures, UnorderedElementsAre(Sig(ExpectedSig)));
2669+
}
2670+
}
2671+
2672+
TEST(SignatureHelpTest, VariadicMethod) {
2673+
const std::string Header = R"cpp(
2674+
class C {
2675+
template<typename T, typename ...Args>
2676+
void fun(T t, Args ...args) {}
2677+
};
2678+
void test() {C c; )cpp";
2679+
const std::string ExpectedSig = "fun([[T t]], [[Args args...]]) -> void";
2680+
2681+
{
2682+
const auto Result = signatures(Header + "c.fun(^);}");
2683+
EXPECT_EQ(0, Result.activeParameter);
2684+
EXPECT_THAT(Result.signatures, UnorderedElementsAre(Sig(ExpectedSig)));
2685+
}
2686+
{
2687+
const auto Result = signatures(Header + "c.fun(1, ^);}");
2688+
EXPECT_EQ(1, Result.activeParameter);
2689+
EXPECT_THAT(Result.signatures, UnorderedElementsAre(Sig(ExpectedSig)));
2690+
}
2691+
{
2692+
const auto Result = signatures(Header + "c.fun(1, 2, ^);}");
2693+
EXPECT_EQ(1, Result.activeParameter);
2694+
EXPECT_THAT(Result.signatures, UnorderedElementsAre(Sig(ExpectedSig)));
2695+
}
2696+
}
2697+
2698+
TEST(SignatureHelpTest, VariadicType) {
2699+
const std::string Header = R"cpp(
2700+
void fun(int x, ...) {}
2701+
auto get_fun() { return fun; }
2702+
void test() {
2703+
)cpp";
2704+
const std::string ExpectedSig = "([[int]], [[...]]) -> void";
2705+
2706+
{
2707+
const auto Result = signatures(Header + "get_fun()(^);}");
2708+
EXPECT_EQ(0, Result.activeParameter);
2709+
EXPECT_THAT(Result.signatures, UnorderedElementsAre(Sig(ExpectedSig)));
2710+
}
2711+
{
2712+
const auto Result = signatures(Header + "get_fun()(1, ^);}");
2713+
EXPECT_EQ(1, Result.activeParameter);
2714+
EXPECT_THAT(Result.signatures, UnorderedElementsAre(Sig(ExpectedSig)));
2715+
}
2716+
{
2717+
const auto Result = signatures(Header + "get_fun()(1, 2, ^);}");
2718+
EXPECT_EQ(1, Result.activeParameter);
2719+
EXPECT_THAT(Result.signatures, UnorderedElementsAre(Sig(ExpectedSig)));
2720+
}
2721+
}
2722+
26252723
TEST(CompletionTest, IncludedCompletionKinds) {
26262724
Annotations Test(R"cpp(#include "^)cpp");
26272725
auto TU = TestTU::withCode(Test.code());

clang/include/clang/Sema/Overload.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,6 +1204,20 @@ class Sema;
12041204
return Info;
12051205
}
12061206

1207+
// Returns false if signature help is relevant despite number of arguments
1208+
// exceeding parameters. Specifically, it returns false when
1209+
// PartialOverloading is true and one of the following:
1210+
// * Function is variadic
1211+
// * Function is template variadic
1212+
// * Function is an instantiation of template variadic function
1213+
// The last case may seem strange. The idea is that if we added one more
1214+
// argument, we'd end up with a function similar to Function. Since, in the
1215+
// context of signature help and/or code completion, we do not know what the
1216+
// type of the next argument (that the user is typing) will be, this is as
1217+
// good candidate as we can get, despite the fact that it takes one less
1218+
// parameter.
1219+
bool shouldEnforceArgLimit(bool PartialOverloading, FunctionDecl *Function);
1220+
12071221
} // namespace clang
12081222

12091223
#endif // LLVM_CLANG_SEMA_OVERLOAD_H

clang/lib/Sema/SemaCodeComplete.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5818,7 +5818,8 @@ static void mergeCandidatesWithResults(
58185818
if (Candidate.Function) {
58195819
if (Candidate.Function->isDeleted())
58205820
continue;
5821-
if (!Candidate.Function->isVariadic() &&
5821+
if (shouldEnforceArgLimit(/*PartialOverloading=*/true,
5822+
Candidate.Function) &&
58225823
Candidate.Function->getNumParams() <= ArgSize &&
58235824
// Having zero args is annoying, normally we don't surface a function
58245825
// with 2 params, if you already have 2 params, because you are

clang/lib/Sema/SemaOverload.cpp

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6456,7 +6456,8 @@ void Sema::AddOverloadCandidate(
64566456
// parameters is viable only if it has an ellipsis in its parameter
64576457
// list (8.3.5).
64586458
if (TooManyArguments(NumParams, Args.size(), PartialOverloading) &&
6459-
!Proto->isVariadic()) {
6459+
!Proto->isVariadic() &&
6460+
shouldEnforceArgLimit(PartialOverloading, Function)) {
64606461
Candidate.Viable = false;
64616462
Candidate.FailureKind = ovl_fail_too_many_arguments;
64626463
return;
@@ -6946,7 +6947,8 @@ Sema::AddMethodCandidate(CXXMethodDecl *Method, DeclAccessPair FoundDecl,
69466947
// parameters is viable only if it has an ellipsis in its parameter
69476948
// list (8.3.5).
69486949
if (TooManyArguments(NumParams, Args.size(), PartialOverloading) &&
6949-
!Proto->isVariadic()) {
6950+
!Proto->isVariadic() &&
6951+
shouldEnforceArgLimit(PartialOverloading, Method)) {
69506952
Candidate.Viable = false;
69516953
Candidate.FailureKind = ovl_fail_too_many_arguments;
69526954
return;
@@ -15242,3 +15244,21 @@ ExprResult Sema::FixOverloadedFunctionReference(ExprResult E,
1524215244
FunctionDecl *Fn) {
1524315245
return FixOverloadedFunctionReference(E.get(), Found, Fn);
1524415246
}
15247+
15248+
bool clang::shouldEnforceArgLimit(bool PartialOverloading,
15249+
FunctionDecl *Function) {
15250+
if (!PartialOverloading || !Function)
15251+
return true;
15252+
if (Function->isVariadic())
15253+
return false;
15254+
if (const auto *Proto =
15255+
dyn_cast<FunctionProtoType>(Function->getFunctionType()))
15256+
if (Proto->isTemplateVariadic())
15257+
return false;
15258+
if (auto *Pattern = Function->getTemplateInstantiationPattern())
15259+
if (const auto *Proto =
15260+
dyn_cast<FunctionProtoType>(Pattern->getFunctionType()))
15261+
if (Proto->isTemplateVariadic())
15262+
return false;
15263+
return true;
15264+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
template <typename T, typename... Args>
2+
void fun(T x, Args... args) {}
3+
4+
void f() {
5+
fun(1, 2, 3, 4);
6+
// The results are quite awkward here, but it's the best we can do for now.
7+
// Tools, including clangd, can unexpand "args" when showing this to the user.
8+
// The important thing is that we provide OVERLOAD signature in all those cases.
9+
//
10+
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:5:7 %s -o - | FileCheck --check-prefix=CHECK-1 %s
11+
// CHECK-1: OVERLOAD: [#void#]fun(<#T x#>, Args args...)
12+
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:5:10 %s -o - | FileCheck --check-prefix=CHECK-2 %s
13+
// CHECK-2: OVERLOAD: [#void#]fun(int x)
14+
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:5:13 %s -o - | FileCheck --check-prefix=CHECK-3 %s
15+
// CHECK-3: OVERLOAD: [#void#]fun(int x, int args)
16+
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:5:16 %s -o - | FileCheck --check-prefix=CHECK-4 %s
17+
// CHECK-4: OVERLOAD: [#void#]fun(int x, int args, int args)
18+
}

0 commit comments

Comments
 (0)