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 @@ -460,6 +460,58 @@ return QT; } +// Given a callee expression `Fn`, if the call is through a function pointer, +// try to find the declaration of the corresponding function pointer type, +// so that we can recover argument names from it. +// FIXME: This function is mostly duplicated in SemaCodeComplete.cpp; unify. +static FunctionProtoTypeLoc getPrototypeLoc(Expr *Callee) { + TypeLoc Target; + Expr *Naked = Callee->IgnoreParenCasts(); + if (const auto *T = Naked->getType().getTypePtr()->getAs()) { + Target = T->getDecl()->getTypeSourceInfo()->getTypeLoc(); + } else if (const auto *DR = dyn_cast(Naked)) { + const auto *D = DR->getDecl(); + if (const auto *const VD = dyn_cast(D)) { + Target = VD->getTypeSourceInfo()->getTypeLoc(); + } + } + + if (!Target) + return {}; + + // Unwrap types that may be wrapping the function type + while (true) { + if (auto P = Target.getAs()) { + Target = P.getPointeeLoc(); + continue; + } + if (auto A = Target.getAs()) { + Target = A.getModifiedLoc(); + continue; + } + if (auto P = Target.getAs()) { + Target = P.getInnerLoc(); + continue; + } + break; + } + + if (auto F = Target.getAs()) { + return F; + } + + return {}; +} + +struct CallInfo { + // Only one of Callee or ProtoTypeLoc is set. + const FunctionDecl *Callee = nullptr; + FunctionProtoTypeLoc ProtoTypeLoc; + + // Args is always set. + llvm::ArrayRef Args; +}; + class InlayHintVisitor : public RecursiveASTVisitor { public: InlayHintVisitor(std::vector &Results, ParsedAST &AST, @@ -499,7 +551,12 @@ return true; } - processCall(E->getConstructor(), {E->getArgs(), E->getNumArgs()}); + CallInfo Call; + Call.Callee = E->getConstructor(); + if (!Call.Callee) + return true; + Call.Args = {E->getArgs(), E->getNumArgs()}; + processCall(Call); return true; } @@ -522,10 +579,17 @@ Callee = FD; else if (const auto *FTD = dyn_cast(CalleeDecls[0])) Callee = FTD->getTemplatedDecl(); - if (!Callee) - return true; - processCall(Callee, {E->getArgs(), E->getNumArgs()}); + CallInfo Call; + if (Callee) + Call.Callee = Callee; + else if (FunctionProtoTypeLoc ProtoTypeLoc = + getPrototypeLoc(E->getCallee())) + Call.ProtoTypeLoc = ProtoTypeLoc; + else + return true; + Call.Args = {E->getArgs(), E->getNumArgs()}; + processCall(Call); return true; } @@ -737,42 +801,51 @@ private: using NameVec = SmallVector; - void processCall(const FunctionDecl *Callee, - llvm::ArrayRef Args) { - if (!Cfg.InlayHints.Parameters || Args.size() == 0 || !Callee) + void processCall(CallInfo Call) { + assert(Call.Callee || Call.ProtoTypeLoc); + + if (!Cfg.InlayHints.Parameters || Call.Args.size() == 0) return; // The parameter name of a move or copy constructor is not very interesting. - if (auto *Ctor = dyn_cast(Callee)) - if (Ctor->isCopyOrMoveConstructor()) - return; + if (Call.Callee) + if (auto *Ctor = dyn_cast(Call.Callee)) + if (Ctor->isCopyOrMoveConstructor()) + return; + + auto Params = + Call.Callee ? Call.Callee->parameters() : Call.ProtoTypeLoc.getParams(); // Resolve parameter packs to their forwarded parameter - auto ForwardedParams = resolveForwardingParameters(Callee); + SmallVector ForwardedParams; + if (Call.Callee) + ForwardedParams = resolveForwardingParameters(Call.Callee); + else + ForwardedParams = {Params.begin(), Params.end()}; NameVec ParameterNames = chooseParameterNames(ForwardedParams); // Exclude setters (i.e. functions with one argument whose name begins with // "set"), and builtins like std::move/forward/... as their parameter name // is also not likely to be interesting. - if (isSetter(Callee, ParameterNames) || isSimpleBuiltin(Callee)) + if (Call.Callee && + (isSetter(Call.Callee, ParameterNames) || isSimpleBuiltin(Call.Callee))) return; - for (size_t I = 0; I < ParameterNames.size() && I < Args.size(); ++I) { + for (size_t I = 0; I < ParameterNames.size() && I < Call.Args.size(); ++I) { // Pack expansion expressions cause the 1:1 mapping between arguments and // parameters to break down, so we don't add further inlay hints if we // encounter one. - if (isa(Args[I])) { + if (isa(Call.Args[I])) { break; } StringRef Name = ParameterNames[I]; - bool NameHint = shouldHintName(Args[I], Name); - bool ReferenceHint = - shouldHintReference(Callee->getParamDecl(I), ForwardedParams[I]); + bool NameHint = shouldHintName(Call.Args[I], Name); + bool ReferenceHint = shouldHintReference(Params[I], ForwardedParams[I]); if (NameHint || ReferenceHint) { - addInlayHint(Args[I]->getSourceRange(), HintSide::Left, + addInlayHint(Call.Args[I]->getSourceRange(), HintSide::Left, InlayHintKind::Parameter, ReferenceHint ? "&" : "", NameHint ? Name : "", ": "); } 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 @@ -910,6 +910,26 @@ )cpp"); } +TEST(ParameterHints, FunctionPointer) { + assertParameterHints( + R"cpp( + void (*f1)(int param); + void (__stdcall *f2)(int param); + using f3_t = void(*)(int param); + f3_t f3; + using f4_t = void(__stdcall *)(int param); + f4_t f4; + void bar() { + f1($f1[[42]]); + f2($f2[[42]]); + f3($f3[[42]]); + f4($f4[[42]]); + } + )cpp", + ExpectedHint{"param: ", "f1"}, ExpectedHint{"param: ", "f2"}, + ExpectedHint{"param: ", "f3"}, ExpectedHint{"param: ", "f4"}); +} + TEST(ParameterHints, ArgMatchesParam) { assertParameterHints(R"cpp( void foo(int param);