diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -199,6 +199,18 @@ /// reasonable. bool isDeeplyNested(const Decl *D, unsigned MaxDepth = 10); +/// Recursively resolves the parameters of a FunctionDecl that forwards its +/// parameters to another function via variadic template parameters. This can +/// for example be used to retrieve the constructor parameter ParmVarDecl for a +/// make_unique or emplace_back call. +llvm::SmallVector +resolveForwardingParameters(const FunctionDecl *D, unsigned MaxDepth = 10); + +/// Checks whether D is instantiated from a function parameter pack +/// whose type is a bare type parameter pack (e.g. `Args...`), or a +/// reference to one (e.g. `Args&...` or `Args&&...`). +bool isExpandedParameterPack(const ParmVarDecl *D); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -16,19 +16,23 @@ #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/DeclarationName.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/NestedNameSpecifier.h" #include "clang/AST/PrettyPrinter.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Stmt.h" #include "clang/AST/TemplateBase.h" #include "clang/AST/TypeLoc.h" +#include "clang/Basic/Builtins.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Specifiers.h" #include "clang/Index/USRGeneration.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/None.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/raw_ostream.h" @@ -667,5 +671,284 @@ } return false; } + +namespace { + +// Checks if the template parameter declaration is a type parameter pack +bool isTemplateTypeParameterPack(NamedDecl *D) { + if (const auto *TTPD = dyn_cast(D)) { + const auto *TTPT = TTPD->getTypeForDecl()->castAs(); + return TTPT->isParameterPack(); + } + return false; +} + +// Returns the template parameter pack type from an instantiated function +// template, if it exists, nullptr otherwise. +const TemplateTypeParmType * +getPackTemplateParameter(const FunctionDecl *Callee) { + if (const auto *TemplateDecl = Callee->getPrimaryTemplate()) { + auto TemplateParams = TemplateDecl->getTemplateParameters()->asArray(); + // find the template parameter pack from the back + const auto It = std::find_if(TemplateParams.rbegin(), TemplateParams.rend(), + isTemplateTypeParameterPack); + if (It != TemplateParams.rend()) { + const auto *TTPD = dyn_cast(*It); + return TTPD->getTypeForDecl()->castAs(); + } + } + return nullptr; +} + +// Returns the template parameter pack type that this parameter was expanded +// from (if in the Args... or Args&... or Args&&... form), if this is the case, +// nullptr otherwise. +const TemplateTypeParmType *getPackTemplateParameter(const ParmVarDecl *Param) { + const auto *PlainType = Param->getType().getTypePtr(); + if (auto *RT = dyn_cast(PlainType)) + PlainType = RT->getPointeeTypeAsWritten().getTypePtr(); + if (const auto *SubstType = dyn_cast(PlainType)) { + const auto *ReplacedParameter = SubstType->getReplacedParameter(); + if (ReplacedParameter->isParameterPack()) { + return dyn_cast( + ReplacedParameter->getCanonicalTypeUnqualified()->getTypePtr()); + } + } + return nullptr; +} + +class ForwardingCallVisitor + : public RecursiveASTVisitor { +public: + ForwardingCallVisitor(ArrayRef Parameters) + : Parameters{Parameters}, PackType{getPackTemplateParameter( + Parameters.front())} {} + + bool VisitCallExpr(CallExpr *E) { + auto *Callee = getCalleeDeclOrUniqueOverload(E); + if (Callee) { + handleCall(Callee, E->arguments()); + } + return !Info.hasValue(); + } + + bool VisitCXXConstructExpr(CXXConstructExpr *E) { + auto *Callee = E->getConstructor(); + if (Callee) { + handleCall(Callee, E->arguments()); + } + return !Info.hasValue(); + } + + // The expanded parameter pack to be resolved + ArrayRef Parameters; + // The type of the parameter pack + const TemplateTypeParmType *PackType; + + struct ForwardingInfo { + // If the parameters were resolved to another FunctionDecl, these are its + // first non-variadic parameters (i.e. the first entries of the parameter + // pack that are passed as arguments bound to a non-pack parameter.) + ArrayRef Head; + // If the parameters were resolved to another FunctionDecl, these are its + // variadic parameters (i.e. the entries of the parameter pack that are + // passed as arguments bound to a pack parameter.) + ArrayRef Pack; + // If the parameters were resolved to another FunctionDecl, these are its + // last non-variadic parameters (i.e. the last entries of the parameter pack + // that are passed as arguments bound to a non-pack parameter.) + ArrayRef Tail; + // If the parameters were resolved to another forwarding FunctionDecl, this + // is it. + Optional PackTarget; + }; + + // The output of this visitor + Optional Info; + +private: + // inspects the given callee with the given args to check whether it + // contains Parameters, and sets Info accordingly. + void handleCall(FunctionDecl *Callee, typename CallExpr::arg_range Args) { + if (std::any_of(Args.begin(), Args.end(), [](const Expr *E) { + return dyn_cast(E) != nullptr; + })) { + return; + } + auto OptPackLocation = findPack(Args); + if (OptPackLocation) { + size_t PackLocation = OptPackLocation.getValue(); + ArrayRef MatchingParams = + Callee->parameters().slice(PackLocation, Parameters.size()); + // Check whether the function has a parameter pack as the last template + // parameter + if (const auto *TTPT = getPackTemplateParameter(Callee)) { + // In this case: Separate the parameters into head, pack and tail + auto IsExpandedPack = [&](const ParmVarDecl *P) { + return getPackTemplateParameter(P) == TTPT; + }; + ForwardingInfo FI; + FI.Head = MatchingParams.take_until(IsExpandedPack); + FI.Pack = MatchingParams.drop_front(FI.Head.size()) + .take_while(IsExpandedPack); + FI.Tail = MatchingParams.drop_front(FI.Head.size() + FI.Pack.size()); + FI.PackTarget = Callee; + Info = FI; + return; + } + // Default case: assume all parameters were fully resolved + ForwardingInfo FI; + FI.Head = MatchingParams; + Info = FI; + } + } + + // Returns the beginning of the expanded pack represented by Parameters + // in the given arguments, if it is there. + llvm::Optional findPack(typename CallExpr::arg_range Args) { + // find the argument directly referring to the first parameter + auto FirstMatch = std::find_if(Args.begin(), Args.end(), [&](Expr *Arg) { + const auto *RefArg = unwrapArgument(Arg); + if (RefArg) { + if (Parameters.front() == dyn_cast(RefArg->getDecl())) { + return true; + } + } + return false; + }); + if (FirstMatch == Args.end()) { + return llvm::None; + } + return std::distance(Args.begin(), FirstMatch); + } + + static FunctionDecl *getCalleeDeclOrUniqueOverload(CallExpr *E) { + Decl *CalleeDecl = E->getCalleeDecl(); + auto *Callee = dyn_cast_or_null(CalleeDecl); + if (!Callee) { + if (auto *Lookup = dyn_cast(E->getCallee())) { + Callee = resolveOverload(Lookup, E); + } + } + // Ignore the callee if the number of arguments is wrong (deal with va_args) + if (Callee->getNumParams() == E->getNumArgs()) + return Callee; + return nullptr; + } + + static FunctionDecl *resolveOverload(UnresolvedLookupExpr *Lookup, + CallExpr *E) { + FunctionDecl *MatchingDecl = nullptr; + if (!Lookup->requiresADL()) { + // Check whether there is a single overload with this number of + // parameters + for (auto *Candidate : Lookup->decls()) { + if (auto *FuncCandidate = dyn_cast_or_null(Candidate)) { + if (FuncCandidate->getNumParams() == E->getNumArgs()) { + if (MatchingDecl) { + // there are multiple candidates - abort + return nullptr; + } + MatchingDecl = FuncCandidate; + } + } + } + } + return MatchingDecl; + } + + // Removes any implicit cast expressions around the given expression. + static const Expr *unwrapImplicitCast(const Expr *E) { + while (const auto *Cast = dyn_cast(E)) { + E = Cast->getSubExpr(); + } + return E; + } + + // Maps std::forward(E) to E, nullptr otherwise + static const Expr *unwrapForward(const Expr *E) { + if (const auto *Call = dyn_cast(E)) { + const auto Callee = Call->getBuiltinCallee(); + if (Callee == Builtin::BIforward) { + return Call->getArg(0); + } + } + return E; + } + + // Maps std::forward(DeclRefExpr) to DeclRefExpr, removing any intermediate + // implicit casts, nullptr otherwise + static const DeclRefExpr *unwrapArgument(const Expr *E) { + E = unwrapImplicitCast(E); + E = unwrapForward(E); + E = unwrapImplicitCast(E); + return dyn_cast(E); + } +}; + +} // namespace + +SmallVector +resolveForwardingParameters(const FunctionDecl *D, unsigned MaxDepth) { + auto Parameters = D->parameters(); + // If the function has a template parameter pack + if (const auto *TTPT = getPackTemplateParameter(D)) { + // Split the parameters into head, pack and tail + auto IsExpandedPack = [TTPT](const ParmVarDecl *P) { + return getPackTemplateParameter(P) == TTPT; + }; + ArrayRef Head = Parameters.take_until(IsExpandedPack); + ArrayRef Pack = + Parameters.drop_front(Head.size()).take_while(IsExpandedPack); + ArrayRef Tail = + Parameters.drop_front(Head.size() + Pack.size()); + SmallVector Result(Parameters.size()); + // Fill in non-pack parameters + auto HeadIt = std::copy(Head.begin(), Head.end(), Result.begin()); + auto TailIt = std::copy(Tail.rbegin(), Tail.rend(), Result.rbegin()); + // Recurse on pack parameters + size_t Depth = 0; + const FunctionDecl *CurrentFunction = D; + llvm::SmallSet SeenTemplates; + if (const auto *Template = D->getPrimaryTemplate()) { + SeenTemplates.insert(Template); + } + while (!Pack.empty() && CurrentFunction && Depth < MaxDepth) { + // Find call expressions involving the pack + ForwardingCallVisitor V{Pack}; + V.TraverseStmt(CurrentFunction->getBody()); + if (!V.Info) { + break; + } + // If we found something: Fill in non-pack parameters + auto Info = V.Info.getValue(); + HeadIt = std::copy(Info.Head.begin(), Info.Head.end(), HeadIt); + TailIt = std::copy(Info.Tail.rbegin(), Info.Tail.rend(), TailIt); + // Prepare next recursion level + Pack = Info.Pack; + CurrentFunction = Info.PackTarget.getValueOr(nullptr); + Depth++; + // If we are recursing into a previously encountered function: Abort + if (CurrentFunction) { + if (const auto *Template = CurrentFunction->getPrimaryTemplate()) { + bool NewFunction = SeenTemplates.insert(Template).second; + if (!NewFunction) { + return {Parameters.begin(), Parameters.end()}; + } + } + } + } + // Fill in the remaining unresolved pack parameters + HeadIt = std::copy(Pack.begin(), Pack.end(), HeadIt); + assert(TailIt.base() == HeadIt); + return Result; + } + return {Parameters.begin(), Parameters.end()}; +} + +bool isExpandedParameterPack(const ParmVarDecl *D) { + return getPackTemplateParameter(D) != nullptr; +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp --- a/clang-tools-extra/clangd/InlayHints.cpp +++ b/clang-tools-extra/clangd/InlayHints.cpp @@ -379,7 +379,7 @@ // assume that if this location does not come from a macro definition, then // the entire argument list likely appears in the main file and can be hinted. void processCall(SourceLocation Anchor, const FunctionDecl *Callee, - llvm::ArrayRef Args) { + llvm::ArrayRef Args) { if (!Cfg.InlayHints.Parameters || Args.size() == 0 || !Callee) return; @@ -393,12 +393,13 @@ if (Ctor->isCopyOrMoveConstructor()) return; - // Don't show hints for variadic parameters. - size_t FixedParamCount = getFixedParamCount(Callee); - size_t ArgCount = std::min(FixedParamCount, Args.size()); - auto Params = Callee->parameters(); + // Resolve parameter packs to their forwarded parameter + auto Params = resolveForwardingParameters(Callee); + // We are only interested in expanded arguments with corresponding + // parameters. + size_t ParamHintCount = std::min(Params.size(), getExpandedArgCount(Args)); - NameVec ParameterNames = chooseParameterNames(Callee, ArgCount); + NameVec ParameterNames = chooseParameterNames(Params); // Exclude setters (i.e. functions with one argument whose name begins with // "set"), and builtins like std::move/forward/... as their parameter name @@ -406,7 +407,7 @@ if (isSetter(Callee, ParameterNames) || isSimpleBuiltin(Callee)) return; - for (size_t I = 0; I < ArgCount; ++I) { + for (size_t I = 0; I < ParamHintCount; ++I) { StringRef Name = ParameterNames[I]; bool NameHint = shouldHintName(Args[I], Name); bool ReferenceHint = shouldHintReference(Params[I]); @@ -474,10 +475,12 @@ } bool shouldHintReference(const ParmVarDecl *Param) { - // If the parameter is a non-const reference type, print an inlay hint + // If the parameter is of non-const l-value reference type not originating + // from an unresolved expanded pack, print an inlay hint auto Type = Param->getType(); return Type->isLValueReferenceType() && - !Type.getNonReferenceType().isConstQualified(); + !Type.getNonReferenceType().isConstQualified() && + !isExpandedParameterPack(Param); } // Checks if "E" is spelled in the main file and preceded by a C-style comment @@ -524,23 +527,31 @@ return {}; } - NameVec chooseParameterNames(const FunctionDecl *Callee, size_t ArgCount) { - // The current strategy here is to use all the parameter names from the - // canonical declaration, unless they're all empty, in which case we - // use all the parameter names from the definition (in present in the - // translation unit). - // We could try a bit harder, e.g.: - // - try all re-declarations, not just canonical + definition - // - fall back arg-by-arg rather than wholesale - - NameVec ParameterNames = getParameterNamesForDecl(Callee, ArgCount); - - if (llvm::all_of(ParameterNames, std::mem_fn(&StringRef::empty))) { - if (const FunctionDecl *Def = Callee->getDefinition()) { - ParameterNames = getParameterNamesForDecl(Def, ArgCount); + NameVec chooseParameterNames(SmallVector Parameters) { + NameVec ParameterNames; + for (const auto *P : Parameters) { + if (isExpandedParameterPack(P)) { + // unresolved parameter packs should not be hinted + ParameterNames.emplace_back(); + } else { + auto SimpleName = getSimpleName(*P); + // If the parameter is unnamed in the declaration: + // attempt to get its name from the definition + if (SimpleName.empty()) { + if (auto *Callee = dyn_cast(P->getDeclContext())) { + if (auto *Def = Callee->getDefinition()) { + auto I = std::distance( + Callee->param_begin(), + std::find(Callee->param_begin(), Callee->param_end(), P)); + if (I < Callee->getNumParams()) { + SimpleName = getSimpleName(*Def->getParamDecl(I)); + } + } + } + } + ParameterNames.emplace_back(SimpleName); } } - assert(ParameterNames.size() == ArgCount); // Standard library functions often have parameter names that start // with underscores, which makes the hints noisy, so strip them out. @@ -554,24 +565,13 @@ Name = Name.ltrim('_'); } - // Return the number of fixed parameters Function has, that is, not counting - // parameters that are variadic (instantiated from a parameter pack) or - // C-style varargs. - static size_t getFixedParamCount(const FunctionDecl *Function) { - if (FunctionTemplateDecl *Template = Function->getPrimaryTemplate()) { - FunctionDecl *F = Template->getTemplatedDecl(); - size_t Result = 0; - for (ParmVarDecl *Parm : F->parameters()) { - if (Parm->isParameterPack()) { - break; - } - ++Result; - } - return Result; - } - // C-style varargs don't need special handling, they're already - // not included in getNumParams(). - return Function->getNumParams(); + // Return the length of the prefix of arguments that are not unexpanded pack + // expansion expressions. + static size_t getExpandedArgCount(llvm::ArrayRef Args) { + return std::distance( + Args.begin(), std::find_if(Args.begin(), Args.end(), [](const Expr *E) { + return dyn_cast(E) != nullptr; + })); } static StringRef getSimpleName(const NamedDecl &D) { @@ -582,17 +582,6 @@ return StringRef(); } - NameVec getParameterNamesForDecl(const FunctionDecl *Function, - size_t ArgCount) { - NameVec Result; - for (size_t I = 0; I < ArgCount; ++I) { - const ParmVarDecl *Parm = Function->getParamDecl(I); - assert(Parm); - Result.emplace_back(getSimpleName(*Parm)); - } - return Result; - } - // We pass HintSide rather than SourceLocation because we want to ensure // it is in the same file as the common file range. void addInlayHint(SourceRange R, HintSide Side, InlayHintKind Kind, diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp --- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp +++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp @@ -174,6 +174,43 @@ )cpp"); } +TEST(ParameterHints, NoNameVariadicDeclaration) { + // No hint for anonymous variadic parameter + assertParameterHints(R"cpp( + template + void foo(Args&& ...); + void bar() { + foo(42); + } + )cpp"); +} + +TEST(ParameterHints, NoNameVariadicForwarded) { + // No hint for anonymous variadic parameter + // This prototype of std::forward is sufficient for clang to recognize it + assertParameterHints(R"cpp( + namespace std { template T&& forward(T&); } + void foo(int); + template + void bar(Args&&... args) { return foo(std::forward(args)...); } + void baz() { + bar(42); + } + )cpp"); +} + +TEST(ParameterHints, NoNameVariadicPlain) { + // No hint for anonymous variadic parameter + assertParameterHints(R"cpp( + void foo(int); + template + void bar(Args&&... args) { return foo(args...); } + void baz() { + bar(42); + } + )cpp"); +} + TEST(ParameterHints, NameInDefinition) { // Parameter name picked up from definition if necessary. assertParameterHints(R"cpp( @@ -186,6 +223,36 @@ ExpectedHint{"param: ", "param"}); } +TEST(ParameterHints, NamePartiallyInDefinition) { + // Parameter name picked up from definition if necessary. + assertParameterHints(R"cpp( + void foo(int, int b); + void bar() { + foo($param1[[42]], $param2[[42]]); + } + void foo(int a, int) {}; + )cpp", + ExpectedHint{"a: ", "param1"}, + ExpectedHint{"b: ", "param2"}); +} + +TEST(ParameterHints, NameInDefinitionVariadic) { + // Parameter name picked up from definition in a resolved forwarded parameter. + assertParameterHints(R"cpp( + void foo(int, int); + template + void bar(Args... args) { + foo(args...); + } + void baz() { + bar($param1[[42]], $param2[[42]]); + } + void foo(int a, int b) {}; + )cpp", + ExpectedHint{"a: ", "param1"}, + ExpectedHint{"b: ", "param2"}); +} + TEST(ParameterHints, NameMismatch) { // Prefer name from declaration. assertParameterHints(R"cpp( @@ -258,6 +325,349 @@ ExpectedHint{"param: ", "param"}); } +TEST(ParameterHints, VariadicForwardedConstructor) { + // Name hint for variadic parameter using std::forward in a constructor call + // This prototype of std::forward is sufficient for clang to recognize it + assertParameterHints(R"cpp( + namespace std { template T&& forward(T&); } + struct S { S(int a); }; + template + T bar(Args&&... args) { return T{std::forward(args)...}; } + void baz() { + int b; + bar($param[[b]]); + } + )cpp", + ExpectedHint{"a: ", "param"}); +} + +TEST(ParameterHints, VariadicPlainConstructor) { + // Name hint for variadic parameter in a constructor call + assertParameterHints(R"cpp( + struct S { S(int a); }; + template + T bar(Args&&... args) { return T{args...}; } + void baz() { + int b; + bar($param[[b]]); + } + )cpp", + ExpectedHint{"a: ", "param"}); +} + +TEST(ParameterHints, VariadicForwardedNewConstructor) { + // Name hint for variadic parameter using std::forward in a new expression + // This prototype of std::forward is sufficient for clang to recognize it + assertParameterHints(R"cpp( + namespace std { template T&& forward(T&); } + struct S { S(int a); }; + template + T* bar(Args&&... args) { return new T{std::forward(args)...}; } + void baz() { + int b; + bar($param[[b]]); + } + )cpp", + ExpectedHint{"a: ", "param"}); +} + +TEST(ParameterHints, VariadicPlainNewConstructor) { + // Name hint for variadic parameter in a new expression + assertParameterHints(R"cpp( + struct S { S(int a); }; + template + T* bar(Args&&... args) { return new T{args...}; } + void baz() { + int b; + bar($param[[b]]); + } + )cpp", + ExpectedHint{"a: ", "param"}); +} + +TEST(ParameterHints, VariadicForwarded) { + // Name for variadic parameter using std::forward + // This prototype of std::forward is sufficient for clang to recognize it + assertParameterHints(R"cpp( + namespace std { template T&& forward(T&); } + void foo(int a); + template + void bar(Args&&... args) { return foo(std::forward(args)...); } + void baz() { + int b; + bar($param[[b]]); + } + )cpp", + ExpectedHint{"a: ", "param"}); +} + +TEST(ParameterHints, VariadicPlain) { + // Name hint for variadic parameter + assertParameterHints(R"cpp( + void foo(int a); + template + void bar(Args&&... args) { return foo(args...); } + void baz() { + bar($param[[42]]); + } + )cpp", + ExpectedHint{"a: ", "param"}); +} + +TEST(ParameterHints, VariadicPlainWithPackFirst) { + // Name hint for variadic parameter when the parameter pack is not the last + // template parameter + assertParameterHints(R"cpp( + void foo(int a); + template + void bar(Arg, Args&&... args) { return foo(args...); } + void baz() { + bar(1, $param[[42]]); + } + )cpp", + ExpectedHint{"a: ", "param"}); +} + +TEST(ParameterHints, VariadicSplitTwolevel) { + // Name for variadic parameter that involves both head and tail parameters to + // deal with. + // This prototype of std::forward is sufficient for clang to recognize it + assertParameterHints(R"cpp( + namespace std { template T&& forward(T&); } + void baz(int, int b, double); + template + void foo(int a, Args&&... args) { + return baz(1, std::forward(args)..., 1.0); + } + template + void bar(Args&&... args) { return foo(std::forward(args)...); } + void bazz() { + bar($param1[[32]], $param2[[42]]); + } + )cpp", + ExpectedHint{"a: ", "param1"}, + ExpectedHint{"b: ", "param2"}); +} + +TEST(ParameterHints, VariadicOverloaded) { + // Name for variadic parameter for an overloaded function with unique number + // of parameters. + // This prototype of std::forward is sufficient for clang to recognize it + assertParameterHints( + R"cpp( + namespace std { template T&& forward(T&); } + void baz(int b, int c); + void baz(int bb, int cc, int dd); + template + void foo(int a, Args&&... args) { + return baz(std::forward(args)...); + } + template + void bar(Args&&... args) { return foo(std::forward(args)...); } + void bazz() { + bar($param1[[32]], $param2[[42]], $param3[[52]]); + bar($param4[[1]], $param5[[2]], $param6[[3]], $param7[[4]]); + } + )cpp", + ExpectedHint{"a: ", "param1"}, ExpectedHint{"b: ", "param2"}, + ExpectedHint{"c: ", "param3"}, ExpectedHint{"a: ", "param4"}, + ExpectedHint{"bb: ", "param5"}, ExpectedHint{"cc: ", "param6"}, + ExpectedHint{"dd: ", "param7"}); +} + +TEST(ParameterHints, VariadicRecursive) { + // make_tuple-like recursive variadic call + assertParameterHints( + R"cpp( + void foo(); + + template + void foo(Head head, Tail... tail) { + foo(tail...); + } + + template + void bar(Args... args) { + foo(args...); + } + + int main() { + bar(1, 2, 3); + } + )cpp"); +} + +TEST(ParameterHints, VariadicVarargs) { + // variadic call involving varargs (to make sure we don't crash) + assertParameterHints(R"cpp( + void foo(int fixed, ...); + template + void bar(Args&&... args) { + foo(args...); + } + + void baz() { + bar($fixed[[41]], 42, 43); + } + )cpp"); +} + +TEST(ParameterHints, VariadicTwolevelUnresolved) { + // the same setting as VariadicVarargs, only with parameter pack + assertParameterHints(R"cpp( + template + void foo(int fixed, Args&& ... args); + template + void bar(Args&&... args) { + foo(args...); + } + + void baz() { + bar($fixed[[41]], 42, 43); + } + )cpp", + ExpectedHint{"fixed: ", "fixed"}); +} + +TEST(ParameterHints, VariadicTwoCalls) { + // only the first call using the parameter pack should be picked up + assertParameterHints( + R"cpp( + void f1(int a, int b); + void f2(int c, int d); + + bool cond; + + template + void foo(Args... args) { + if (cond) { + f1(args...); + } else { + f2(args...); + } + } + + int main() { + foo($param1[[1]], $param2[[2]]); + } + )cpp", + ExpectedHint{"a: ", "param1"}, ExpectedHint{"b: ", "param2"}); +} + +TEST(ParameterHints, VariadicInfinite) { + // infinite recursion should not break clangd + assertParameterHints( + R"cpp( + template + void foo(Args...); + + template + void bar(Args... args) { + foo(args...); + } + + template + void foo(Args... args) { + bar(args...); + } + + int main() { + foo(1, 2); + } + )cpp"); +} + +TEST(ParameterHints, VariadicDuplicatePack) { + // edge cases with multiple adjacent packs should work + assertParameterHints( + R"cpp( + void foo(int a, int b, int c, int); + + template + void bar(int, Args... args, int d) { + foo(args..., d); + } + + template + void baz(Args... args, Args... args2) { + bar(1, args..., args2...); + } + + int main() { + baz($p1[[1]], $p2[[2]], $p3[[3]], $p4[[4]]); + } + )cpp", + ExpectedHint{"a: ", "p1"}, ExpectedHint{"b: ", "p2"}, + ExpectedHint{"c: ", "p3"}, ExpectedHint{"d: ", "p4"}); +} + +TEST(ParameterHints, VariadicEmplace) { + // emplace-like calls should forward constructor parameters + // This prototype of std::forward is sufficient for clang to recognize it + assertParameterHints( + R"cpp( + namespace std { template T&& forward(T&); } + using size_t = decltype(sizeof(0)); + void *operator new(size_t, void *); + struct S { + S(int A); + S(int B, int C); + }; + struct alloc { + template + T* allocate(); + template + void construct(T* ptr, Args&&... args) { + ::new ((void*)ptr) T{std::forward(args)...}; + } + }; + template + struct container { + template + void emplace(Args&&... args) { + alloc a; + auto ptr = a.template allocate(); + a.construct(ptr, std::forward(args)...); + } + }; + void foo() { + container c; + c.emplace($param1[[1]]); + c.emplace($param2[[2]], $param3[[3]]); + } + )cpp", + ExpectedHint{"A: ", "param1"}, ExpectedHint{"B: ", "param2"}, + ExpectedHint{"C: ", "param3"}); +} + +TEST(ParameterHints, MatchingNameVariadicForwarded) { + // No name hint for variadic parameter with matching name + // This prototype of std::forward is sufficient for clang to recognize it + assertParameterHints(R"cpp( + namespace std { template T&& forward(T&); } + void foo(int a); + template + void bar(Args&&... args) { return foo(std::forward(args)...); } + void baz() { + int a; + bar(a); + } + )cpp"); +} + +TEST(ParameterHints, MatchingNameVariadicPlain) { + // No name hint for variadic parameter with matching name + assertParameterHints(R"cpp( + void foo(int a); + template + void bar(Args&&... args) { return foo(args...); } + void baz() { + int a; + bar(a); + } + )cpp"); +} + TEST(ParameterHints, Operator) { // No hint for operator call with operator syntax. assertParameterHints(R"cpp( @@ -470,7 +880,7 @@ assertParameterHints(R"cpp( void foo(int fixed, ...); - void bar() { + void bar() { foo($fixed[[41]], 42, 43); } )cpp",