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 @@ -832,10 +832,26 @@ } return false; }); - if (FirstMatch == Args.end()) { - return llvm::None; + if (FirstMatch != Args.end()) { + const auto NumArgs = + static_cast(std::distance(Args.begin(), Args.end())); + const auto FirstMatchIdx = + static_cast(std::distance(Args.begin(), FirstMatch)); + // Now we found the first parameter, check whether the last one also fits + // This is necessary because we may be matching a function call inside an + // argument of the variadic function if we didn't successfully match the + // outer variadic call. + const auto LastMatchIdx = FirstMatchIdx + Parameters.size() - 1; + if (LastMatchIdx < NumArgs) { + if (const auto *RefArg = + unwrapArgument(*(Args.begin() + LastMatchIdx))) { + if (Parameters.back() == dyn_cast(RefArg->getDecl())) { + return FirstMatchIdx; + } + } + } } - return std::distance(Args.begin(), FirstMatch); + return llvm::None; } static FunctionDecl *getCalleeDeclOrUniqueOverload(CallExpr *E) { @@ -881,7 +897,7 @@ return E; } - // Maps std::forward(E) to E, nullptr otherwise + // Maps std::forward(E) to E static const Expr *unwrapForward(const Expr *E) { if (const auto *Call = dyn_cast(E)) { const auto Callee = Call->getBuiltinCallee(); @@ -892,10 +908,26 @@ return E; } + // Maps an argument implicitly calling a converting constructor to the + // constructor's argument + static const Expr *unwrapConstructorConversion(const Expr *E) { + if (const auto *CBTE = dyn_cast(E)) { + E = CBTE->getSubExpr(); + } + if (const auto *CCE = dyn_cast(E)) { + const auto *C = CCE->getConstructor(); + if (!C->isExplicit() && C->getNumParams() == 1) { + E = CCE->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 = unwrapConstructorConversion(E); E = unwrapForward(E); E = unwrapImplicitCast(E); return dyn_cast(E); 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 @@ -680,6 +680,71 @@ ExpectedHint{"C: ", "param3"}); } +TEST(ParameterHints, VariadicWithConstructorConversion) { + // variadic call that involves a conversion via a (potentially implicitly + // provided) constructor, here move-construction Foo&& -> Foo + assertParameterHints( + R"cpp( + struct Foo {}; + void foo(Foo s, int x); + + template + void bar(_Args ...__args) { + foo(__args...); + } + + void foo() { + bar($param1[[Foo{}]], $param2[[3]]); + } + )cpp", + ExpectedHint{"s: ", "param1"}, ExpectedHint{"x: ", "param2"}); +} + +TEST(ParameterHints, VariadicWithOperatorConversion) { + // variadic call that involves a conversion via an operator, here Foo -> Bar + assertParameterHints( + R"cpp( + struct Bar {}; + struct Foo { + operator Bar(); + }; + void foo(Bar s, int x); + + template + void bar(_Args ...__args) { + foo(__args...); + } + + void foo() { + bar($param1[[Foo{}]], $param2[[3]]); + } + )cpp" /*, ExpectedHint{"s: ", "param1"}, ExpectedHint{"x: ", "param2"}*/); +} + +TEST(ParameterHints, VariadicWithFunctionCall) { + // variadic call that involves an explicit transformation via a function, here + // we don't output an inlay hint, since it's not clear what transform does. + assertParameterHints( + R"cpp( + struct Bar {}; + struct Foo { + operator Bar(); + }; + template + T transform(T); + void foo(Bar s, int x); + + template + void bar(_Args ...__args) { + foo(transform(__args)...); + } + + void foo() { + bar(Foo{}, 3); + } + )cpp"); +} + TEST(ParameterHints, VariadicReferenceHint) { assertParameterHints(R"cpp( void foo(int&);