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 @@ -16,6 +16,7 @@ #include "index/SymbolID.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclObjC.h" +#include "clang/AST/Expr.h" #include "clang/AST/NestedNameSpecifier.h" #include "clang/AST/TypeLoc.h" #include "clang/Basic/SourceLocation.h" @@ -199,6 +200,16 @@ /// 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 the expanded form of a parameter pack +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,22 +16,26 @@ #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/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/raw_ostream.h" +#include #include #include @@ -665,5 +669,304 @@ } return false; } + +namespace { + +// Returns the template parameter pack type from an instantiated function +// template, if this is the case, nullptr otherwise. +const TemplateTypeParmType * +getPackTemplateParameter(const FunctionDecl *Callee) { + if (const auto *TemplateDecl = Callee->getPrimaryTemplate()) { + auto TemplateParams = TemplateDecl->getTemplateParameters()->asArray(); + if (TemplateParams.size() > 0) { + if (const auto *TTPD = + dyn_cast(TemplateParams.back())) { + const auto *TTPT = + TTPD->getTypeForDecl()->castAs(); + if (TTPT->isParameterPack()) { + return TTPT; + } + } + } + } + return nullptr; +} + +// Returns the template parameter pack type that this parameter was expanded +// from, 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->getPointeeType().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 FullyResolved, PartiallyResolved and + // NextTarget accordingly. + void handleCall(FunctionDecl *Callee, typename CallExpr::arg_range Args) { + std::cout << "Handling call to\n"; + Callee->dump(); + std::cout << "with arguments\n"; + for (const auto *Arg : Args) { + Arg->dump(); + } + auto OptPackLocation = findPack(Args); + if (OptPackLocation) { + size_t PackLocation = OptPackLocation.getValue(); + ArrayRef MatchingParams = + Callee->parameters().slice(PackLocation, Parameters.size()); + std::cout << "Pack starting at " << PackLocation + << " with matching parameters\n"; + for (const auto *Param : MatchingParams) { + Param->dump(); + } + // Check whether the function has a parameter pack as the last template + // parameter + if (const auto *TTPT = getPackTemplateParameter(Callee)) { + std::cout << "Need to recurse into\n"; + Callee->dump(); + TTPT->dump(); + // 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; + std::cout << "Resulting parameters: Head\n"; + for (const auto *Param : FI.Head) { + Param->dump(); + } + std::cout << "Pack\n"; + for (const auto *Param : FI.Pack) { + Param->dump(); + } + std::cout << "Tail\n"; + for (const auto *Param : FI.Tail) { + Param->dump(); + } + 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 *D) { + 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() == D->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)) { + std::cout << "Looking at\n"; + D->dump(); + // Split the parameters into head, pack and tail + auto IsExpandedPack = [&](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.rbegin(), Result.rbegin()); + // Recurse on pack parameters + size_t Depth = 0; + const FunctionDecl *CurrentFunction = D; + while (!Pack.empty() && CurrentFunction && Depth < MaxDepth) { + std::cout << "At depth " << Depth << " current state is\n"; + for (const auto *P : Result) { + if (P) { + P->dump(); + } else { + std::cout << "\n"; + } + } + // 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++; + } + // Fill in the remaining unresolved pack parameters + HeadIt = std::copy(Pack.begin(), Pack.end(), HeadIt); + assert(TailIt.base() == HeadIt); + std::cout << "At depth " << Depth << " current state is\n"; + for (const auto *P : Result) { + if (P) { + P->dump(); + } else { + std::cout << "\n"; + } + } + 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 @@ -392,19 +392,17 @@ 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); - NameVec ParameterNames = chooseParameterNames(Callee, ArgCount); + NameVec ParameterNames = chooseParameterNames(Params); // Exclude setters (i.e. functions with one argument whose name begins with // "set"), as their parameter name is also not likely to be interesting. if (isSetter(Callee, ParameterNames)) return; - for (size_t I = 0; I < ArgCount; ++I) { + for (size_t I = 0; I < Params.size(); ++I) { StringRef Name = ParameterNames[I]; bool NameHint = shouldHintName(Args[I], Name); bool ReferenceHint = shouldHintReference(Params[I]); @@ -507,23 +505,15 @@ 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)) { + ParameterNames.emplace_back(); + } else { + ParameterNames.emplace_back(getSimpleName(*P)); } } - assert(ParameterNames.size() == ArgCount); // Standard library functions often have parameter names that start // with underscores, which makes the hints noisy, so strip them out. @@ -537,26 +527,6 @@ 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(); - } - static StringRef getSimpleName(const NamedDecl &D) { if (IdentifierInfo *Ident = D.getDeclName().getAsIdentifierInfo()) { return Ident->getName(); @@ -565,17 +535,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,44 @@ )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($fwd[[args]])...); } + void baz() { + bar(42); + } + )cpp", + ExpectedHint{"&: ", "fwd"}); +} + +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 +224,19 @@ 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, NameMismatch) { // Prefer name from declaration. assertParameterHints(R"cpp( @@ -258,6 +309,276 @@ ExpectedHint{"param: ", "param"}); } +TEST(ParameterHints, VariadicForwardedConstructor) { + // Name hint for variadic parameter + // 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($fwd[[args]])...}; } + void baz() { + int b; + bar($param[[b]]); + } + )cpp", + ExpectedHint{"&: ", "fwd"}, + ExpectedHint{"a: ", "param"}); +} + +TEST(ParameterHints, VariadicPlainConstructor) { + // Name hint for variadic parameter + 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 + // 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($fwd[[args]])...}; } + void baz() { + int b; + bar($param[[b]]); + } + )cpp", + ExpectedHint{"&: ", "fwd"}, + ExpectedHint{"a: ", "param"}); +} + +TEST(ParameterHints, VariadicPlainNewConstructor) { + // Name hint for variadic parameter + 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 + // 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($fwd[[args]])...); } + void baz() { + int b; + bar($param[[b]]); + } + )cpp", + ExpectedHint{"&: ", "fwd"}, + 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, VariadicSplitTwolevel) { + // Name for variadic parameter + // 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); + template + void foo(int a, Args&&... args) { + return baz(std::forward($fwd1[[args]])...); + } + template + void bar(Args&&... args) { return foo(std::forward($fwd2[[args]])...); } + void bazz() { + bar($param1[[32]], $param2[[42]]); + } + )cpp", + ExpectedHint{"&: ", "fwd1"}, ExpectedHint{"&: ", "fwd2"}, + ExpectedHint{"a: ", "param1"}, + ExpectedHint{"b: ", "param2"}); +} + +TEST(ParameterHints, VariadicOverloaded) { + // Name for variadic parameter + // 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($fwd1[[args]])...); + } + template + void bar(Args&&... args) { return foo(std::forward($fwd2[[args]])...); } + void bazz() { + bar($param1[[32]], $param2[[42]], $param3[[52]]); + bar($param4[[1]], $param5[[2]], $param6[[3]], $param7[[4]]); + } + )cpp", + ExpectedHint{"&: ", "fwd1"}, ExpectedHint{"&: ", "fwd2"}, + ExpectedHint{"a: ", "param1"}, ExpectedHint{"b: ", "param2"}, + ExpectedHint{"c: ", "param3"}, ExpectedHint{"a: ", "param4"}, + ExpectedHint{"bb: ", "param5"}, ExpectedHint{"cc: ", "param6"}, + ExpectedHint{"dd: ", "param7"}); +} + +TEST(ParameterHints, VariadicRecursive) { + assertParameterHints( + R"cpp( + void foo(); + + template + void foo(Head head, Tail... tail) { + foo(tail...); + } + + int main() { + foo(1, 2, 3); + } + )cpp"); +} + +TEST(ParameterHints, VariadicTwoCalls) { + 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, DISABLED_VariadicInfinite) { + 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, VariadicEmplace) { + // Name for variadic parameter + // 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($fwd1[[args]])...}; + } + }; + template + struct container { + template + void emplace(Args&&... args) { + alloc a; + auto ptr = a.template allocate(); + a.construct(ptr, std::forward($fwd2[[args]])...); + } + }; + void foo() { + container c; + c.emplace($param1[[1]]); + c.emplace($param2[[2]], $param3[[3]]); + } + )cpp", + ExpectedHint{"&: ", "fwd1"}, ExpectedHint{"&: ", "fwd2"}, + 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($fwd[[args]])...); } + void baz() { + int a; + bar(a); + } + )cpp", + ExpectedHint{"&: ", "fwd"}); +} + +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(