Skip to content

[clang][Sema] Unify getPrototypeLoc helpers in SemaCodeComplete and clangd #143345

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 2 additions & 49 deletions clang-tools-extra/clangd/InlayHints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/ADT/identity.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h"
Expand Down Expand Up @@ -339,53 +338,6 @@ QualType maybeDesugar(ASTContext &AST, QualType QT) {
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 *Fn) {
TypeLoc Target;
Expr *NakedFn = Fn->IgnoreParenCasts();
if (const auto *T = NakedFn->getType().getTypePtr()->getAs<TypedefType>()) {
Target = T->getDecl()->getTypeSourceInfo()->getTypeLoc();
} else if (const auto *DR = dyn_cast<DeclRefExpr>(NakedFn)) {
const auto *D = DR->getDecl();
if (const auto *const VD = dyn_cast<VarDecl>(D)) {
Target = VD->getTypeSourceInfo()->getTypeLoc();
}
}

if (!Target)
return {};

// Unwrap types that may be wrapping the function type
while (true) {
if (auto P = Target.getAs<PointerTypeLoc>()) {
Target = P.getPointeeLoc();
continue;
}
if (auto A = Target.getAs<AttributedTypeLoc>()) {
Target = A.getModifiedLoc();
continue;
}
if (auto P = Target.getAs<ParenTypeLoc>()) {
Target = P.getInnerLoc();
continue;
}
break;
}

if (auto F = Target.getAs<FunctionProtoTypeLoc>()) {
// In some edge cases the AST can contain a "trivial" FunctionProtoTypeLoc
// which has null parameters. Avoid these as they don't contain useful
// information.
if (llvm::all_of(F.getParams(), llvm::identity<ParmVarDecl *>()))
return F;
}

return {};
}

ArrayRef<const ParmVarDecl *>
maybeDropCxxExplicitObjectParameters(ArrayRef<const ParmVarDecl *> Params) {
if (!Params.empty() && Params.front()->isExplicitObjectParameter())
Expand Down Expand Up @@ -514,7 +466,8 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
Callee.Decl = FD;
else if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(CalleeDecls[0]))
Callee.Decl = FTD->getTemplatedDecl();
else if (FunctionProtoTypeLoc Loc = getPrototypeLoc(E->getCallee()))
else if (FunctionProtoTypeLoc Loc =
Resolver->getFunctionProtoTypeLoc(E->getCallee()))
Callee.Loc = Loc;
else
return true;
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/Sema/HeuristicResolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class CXXBasePath;
class CXXDependentScopeMemberExpr;
class DeclarationName;
class DependentScopeDeclRefExpr;
class FunctionProtoTypeLoc;
class NamedDecl;
class Type;
class UnresolvedUsingValueDecl;
Expand Down Expand Up @@ -93,6 +94,12 @@ class HeuristicResolver {
// during simplification, and the operation fails if no pointer type is found.
QualType simplifyType(QualType Type, const Expr *E, bool UnwrapPointer);

// Given an expression `Fn` representing the callee in a function call,
// 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.
FunctionProtoTypeLoc getFunctionProtoTypeLoc(const Expr *Fn) const;

private:
ASTContext &Ctx;
};
Expand Down
57 changes: 57 additions & 0 deletions clang/lib/Sema/HeuristicResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "clang/AST/ExprCXX.h"
#include "clang/AST/TemplateBase.h"
#include "clang/AST/Type.h"
#include "llvm/ADT/identity.h"

namespace clang {

Expand Down Expand Up @@ -50,6 +51,7 @@ class HeuristicResolverImpl {
llvm::function_ref<bool(const NamedDecl *ND)> Filter);
TagDecl *resolveTypeToTagDecl(QualType T);
QualType simplifyType(QualType Type, const Expr *E, bool UnwrapPointer);
FunctionProtoTypeLoc getFunctionProtoTypeLoc(const Expr *Fn);

private:
ASTContext &Ctx;
Expand Down Expand Up @@ -506,6 +508,56 @@ std::vector<const NamedDecl *> HeuristicResolverImpl::resolveDependentMember(
}
return {};
}

FunctionProtoTypeLoc
HeuristicResolverImpl::getFunctionProtoTypeLoc(const Expr *Fn) {
TypeLoc Target;
const Expr *NakedFn = Fn->IgnoreParenCasts();
if (const auto *T = NakedFn->getType().getTypePtr()->getAs<TypedefType>()) {
Target = T->getDecl()->getTypeSourceInfo()->getTypeLoc();
} else if (const auto *DR = dyn_cast<DeclRefExpr>(NakedFn)) {
const auto *D = DR->getDecl();
if (const auto *const VD = dyn_cast<VarDecl>(D)) {
Target = VD->getTypeSourceInfo()->getTypeLoc();
}
} else if (const auto *ME = dyn_cast<MemberExpr>(NakedFn)) {
const auto *MD = ME->getMemberDecl();
if (const auto *FD = dyn_cast<FieldDecl>(MD)) {
Target = FD->getTypeSourceInfo()->getTypeLoc();
}
}

if (!Target)
return {};

// Unwrap types that may be wrapping the function type
while (true) {
if (auto P = Target.getAs<PointerTypeLoc>()) {
Target = P.getPointeeLoc();
continue;
}
if (auto A = Target.getAs<AttributedTypeLoc>()) {
Target = A.getModifiedLoc();
continue;
}
if (auto P = Target.getAs<ParenTypeLoc>()) {
Target = P.getInnerLoc();
continue;
}
break;
}

if (auto F = Target.getAs<FunctionProtoTypeLoc>()) {
// In some edge cases the AST can contain a "trivial" FunctionProtoTypeLoc
// which has null parameters. Avoid these as they don't contain useful
// information.
if (llvm::all_of(F.getParams(), llvm::identity<ParmVarDecl *>()))
return F;
}

return {};
}

} // namespace

std::vector<const NamedDecl *> HeuristicResolver::resolveMemberExpr(
Expand Down Expand Up @@ -557,4 +609,9 @@ QualType HeuristicResolver::simplifyType(QualType Type, const Expr *E,
return HeuristicResolverImpl(Ctx).simplifyType(Type, E, UnwrapPointer);
}

FunctionProtoTypeLoc
HeuristicResolver::getFunctionProtoTypeLoc(const Expr *Fn) const {
return HeuristicResolverImpl(Ctx).getFunctionProtoTypeLoc(Fn);
}

} // namespace clang
50 changes: 1 addition & 49 deletions clang/lib/Sema/SemaCodeComplete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6283,54 +6283,6 @@ ProduceSignatureHelp(Sema &SemaRef, MutableArrayRef<ResultCandidate> Candidates,
return getParamType(SemaRef, Candidates, CurrentArg);
}

// 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.
static FunctionProtoTypeLoc GetPrototypeLoc(Expr *Fn) {
TypeLoc Target;

if (const auto *T = Fn->getType().getTypePtr()->getAs<TypedefType>()) {
Target = T->getDecl()->getTypeSourceInfo()->getTypeLoc();

} else if (const auto *DR = dyn_cast<DeclRefExpr>(Fn)) {
const auto *D = DR->getDecl();
if (const auto *const VD = dyn_cast<VarDecl>(D)) {
Target = VD->getTypeSourceInfo()->getTypeLoc();
}
} else if (const auto *ME = dyn_cast<MemberExpr>(Fn)) {
const auto *MD = ME->getMemberDecl();
if (const auto *FD = dyn_cast<FieldDecl>(MD)) {
Target = FD->getTypeSourceInfo()->getTypeLoc();
}
}

if (!Target)
return {};

// Unwrap types that may be wrapping the function type
while (true) {
if (auto P = Target.getAs<PointerTypeLoc>()) {
Target = P.getPointeeLoc();
continue;
}
if (auto A = Target.getAs<AttributedTypeLoc>()) {
Target = A.getModifiedLoc();
continue;
}
if (auto P = Target.getAs<ParenTypeLoc>()) {
Target = P.getInnerLoc();
continue;
}
break;
}

if (auto F = Target.getAs<FunctionProtoTypeLoc>()) {
return F;
}

return {};
}

QualType
SemaCodeCompletion::ProduceCallSignatureHelp(Expr *Fn, ArrayRef<Expr *> Args,
SourceLocation OpenParLoc) {
Expand Down Expand Up @@ -6419,7 +6371,7 @@ SemaCodeCompletion::ProduceCallSignatureHelp(Expr *Fn, ArrayRef<Expr *> Args,
// Lastly we check whether expression's type is function pointer or
// function.

FunctionProtoTypeLoc P = GetPrototypeLoc(NakedFn);
FunctionProtoTypeLoc P = Resolver.getFunctionProtoTypeLoc(NakedFn);
QualType T = NakedFn->getType();
if (!T->getPointeeType().isNull())
T = T->getPointeeType();
Expand Down
80 changes: 80 additions & 0 deletions clang/unittests/Sema/HeuristicResolverTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -766,5 +766,85 @@ TEST(HeuristicResolver, UsingValueDecl) {
cxxMethodDecl(hasName("waldo")).bind("output"));
}

// `arg` is a ParamVarDecl*, `Expected` is a string
MATCHER_P(ParamNameMatcher, Expected, "paramNameMatcher") {
EXPECT_TRUE(arg);
if (IdentifierInfo *Ident = arg->getDeclName().getAsIdentifierInfo()) {
return Ident->getName() == Expected;
}
return false;
}

// Helper function for testing HeuristicResolver::getProtoTypeLoc.
// Takes a matcher that selects a callee expression bound to the ID "input",
// calls getProtoTypeLoc() on it, and checks that the call found a
// FunctionProtoTypeLoc encoding the given parameter names.
template <typename InputMatcher, typename... ParameterNames>
void expectParameterNames(ASTContext &Ctx, const InputMatcher &IM,
ParameterNames... ExpectedParameterNames) {
auto InputMatches = match(IM, Ctx);
ASSERT_EQ(1u, InputMatches.size());
const auto *Input = InputMatches[0].template getNodeAs<Expr>("input");
ASSERT_TRUE(Input);

HeuristicResolver H(Ctx);
auto Loc = H.getFunctionProtoTypeLoc(Input);
ASSERT_TRUE(Loc);
EXPECT_THAT(Loc.getParams(),
ElementsAre(ParamNameMatcher(ExpectedParameterNames)...));
}

TEST(HeuristicResolver, ProtoTypeLoc) {
std::string Code = R"cpp(
void (*f1)(int param1);
void (__stdcall *f2)(int param2);
using f3_t = void(*)(int param3);
f3_t f3;
using f4_t = void(__stdcall *)(int param4);
f4_t f4;
struct S {
void (*f5)(int param5);
using f6_t = void(*)(int param6);
f6_t f6;
};
void bar() {
f1(42);
f2(42);
f3(42);
f4(42);
S s;
s.f5(42);
s.f6(42);
}
)cpp";
auto TU = tooling::buildASTFromCodeWithArgs(Code, {"-std=c++20"});
auto &Ctx = TU->getASTContext();
auto checkFreeFunction = [&](llvm::StringRef FunctionName,
llvm::StringRef ParamName) {
expectParameterNames(
Ctx,
callExpr(
callee(implicitCastExpr(hasSourceExpression(declRefExpr(
to(namedDecl(hasName(FunctionName))))))
.bind("input"))),
ParamName);
};
checkFreeFunction("f1", "param1");
checkFreeFunction("f2", "param2");
checkFreeFunction("f3", "param3");
checkFreeFunction("f4", "param4");
auto checkMemberFunction = [&](llvm::StringRef MemberName,
llvm::StringRef ParamName) {
expectParameterNames(
Ctx,
callExpr(callee(implicitCastExpr(hasSourceExpression(memberExpr(
member(hasName(MemberName)))))
.bind("input"))),
ParamName);
};
checkMemberFunction("f5", "param5");
checkMemberFunction("f6", "param6");
}

} // namespace
} // namespace clang
Loading