[clang] [clang-tools-extra] [clang][Sema] Unify getPrototypeLoc helpers in SemaCodeComplete and clangd (PR #143345)
Nathan Ridge via cfe-commits
cfe-commits at lists.llvm.org
Sat Jul 5 07:02:50 PDT 2025
https://github.com/HighCommander4 updated https://github.com/llvm/llvm-project/pull/143345
>From ccb46000da10ec57f0aec4f35d123f0bee03a151 Mon Sep 17 00:00:00 2001
From: Nathan Ridge <zeratul976 at hotmail.com>
Date: Mon, 9 Jun 2025 00:58:47 -0400
Subject: [PATCH] [clang][Sema] Unify getPrototypeLoc helpers in
SemaCodeComplete and clangd
HeuristicResolver houses the unified implementation.
The patch also includes dedicated tests for the new HeuristicResolver method.
Fixes https://github.com/llvm/llvm-project/issues/143240
---
clang-tools-extra/clangd/InlayHints.cpp | 51 +-----------
clang/include/clang/Sema/HeuristicResolver.h | 7 ++
clang/lib/Sema/HeuristicResolver.cpp | 57 +++++++++++++
clang/lib/Sema/SemaCodeComplete.cpp | 50 +-----------
.../unittests/Sema/HeuristicResolverTest.cpp | 80 +++++++++++++++++++
5 files changed, 147 insertions(+), 98 deletions(-)
diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 20a238612a7e4..197c62c40dcf0 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -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"
@@ -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())
@@ -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;
diff --git a/clang/include/clang/Sema/HeuristicResolver.h b/clang/include/clang/Sema/HeuristicResolver.h
index df60d3359c6a6..e193c0bc14cd9 100644
--- a/clang/include/clang/Sema/HeuristicResolver.h
+++ b/clang/include/clang/Sema/HeuristicResolver.h
@@ -20,6 +20,7 @@ class CXXBasePath;
class CXXDependentScopeMemberExpr;
class DeclarationName;
class DependentScopeDeclRefExpr;
+class FunctionProtoTypeLoc;
class NamedDecl;
class Type;
class UnresolvedUsingValueDecl;
@@ -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;
};
diff --git a/clang/lib/Sema/HeuristicResolver.cpp b/clang/lib/Sema/HeuristicResolver.cpp
index 0c67f1f2a3878..6874d30516f8f 100644
--- a/clang/lib/Sema/HeuristicResolver.cpp
+++ b/clang/lib/Sema/HeuristicResolver.cpp
@@ -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 {
@@ -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;
@@ -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(
@@ -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
diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp
index f9f7c192f19d2..84739d4cee802 100644
--- a/clang/lib/Sema/SemaCodeComplete.cpp
+++ b/clang/lib/Sema/SemaCodeComplete.cpp
@@ -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) {
@@ -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();
diff --git a/clang/unittests/Sema/HeuristicResolverTest.cpp b/clang/unittests/Sema/HeuristicResolverTest.cpp
index 3ed6bba790be3..ee434f7a1d43a 100644
--- a/clang/unittests/Sema/HeuristicResolverTest.cpp
+++ b/clang/unittests/Sema/HeuristicResolverTest.cpp
@@ -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
More information about the cfe-commits
mailing list