Skip to content

Commit ccb4600

Browse files
[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 #143240
1 parent 1fec092 commit ccb4600

File tree

5 files changed

+147
-98
lines changed

5 files changed

+147
-98
lines changed

clang-tools-extra/clangd/InlayHints.cpp

Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
#include "llvm/ADT/StringExtras.h"
3434
#include "llvm/ADT/StringRef.h"
3535
#include "llvm/ADT/Twine.h"
36-
#include "llvm/ADT/identity.h"
3736
#include "llvm/Support/Casting.h"
3837
#include "llvm/Support/ErrorHandling.h"
3938
#include "llvm/Support/FormatVariadic.h"
@@ -339,53 +338,6 @@ QualType maybeDesugar(ASTContext &AST, QualType QT) {
339338
return QT;
340339
}
341340

342-
// Given a callee expression `Fn`, if the call is through a function pointer,
343-
// try to find the declaration of the corresponding function pointer type,
344-
// so that we can recover argument names from it.
345-
// FIXME: This function is mostly duplicated in SemaCodeComplete.cpp; unify.
346-
static FunctionProtoTypeLoc getPrototypeLoc(Expr *Fn) {
347-
TypeLoc Target;
348-
Expr *NakedFn = Fn->IgnoreParenCasts();
349-
if (const auto *T = NakedFn->getType().getTypePtr()->getAs<TypedefType>()) {
350-
Target = T->getDecl()->getTypeSourceInfo()->getTypeLoc();
351-
} else if (const auto *DR = dyn_cast<DeclRefExpr>(NakedFn)) {
352-
const auto *D = DR->getDecl();
353-
if (const auto *const VD = dyn_cast<VarDecl>(D)) {
354-
Target = VD->getTypeSourceInfo()->getTypeLoc();
355-
}
356-
}
357-
358-
if (!Target)
359-
return {};
360-
361-
// Unwrap types that may be wrapping the function type
362-
while (true) {
363-
if (auto P = Target.getAs<PointerTypeLoc>()) {
364-
Target = P.getPointeeLoc();
365-
continue;
366-
}
367-
if (auto A = Target.getAs<AttributedTypeLoc>()) {
368-
Target = A.getModifiedLoc();
369-
continue;
370-
}
371-
if (auto P = Target.getAs<ParenTypeLoc>()) {
372-
Target = P.getInnerLoc();
373-
continue;
374-
}
375-
break;
376-
}
377-
378-
if (auto F = Target.getAs<FunctionProtoTypeLoc>()) {
379-
// In some edge cases the AST can contain a "trivial" FunctionProtoTypeLoc
380-
// which has null parameters. Avoid these as they don't contain useful
381-
// information.
382-
if (llvm::all_of(F.getParams(), llvm::identity<ParmVarDecl *>()))
383-
return F;
384-
}
385-
386-
return {};
387-
}
388-
389341
ArrayRef<const ParmVarDecl *>
390342
maybeDropCxxExplicitObjectParameters(ArrayRef<const ParmVarDecl *> Params) {
391343
if (!Params.empty() && Params.front()->isExplicitObjectParameter())
@@ -514,7 +466,8 @@ class InlayHintVisitor : public RecursiveASTVisitor<InlayHintVisitor> {
514466
Callee.Decl = FD;
515467
else if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(CalleeDecls[0]))
516468
Callee.Decl = FTD->getTemplatedDecl();
517-
else if (FunctionProtoTypeLoc Loc = getPrototypeLoc(E->getCallee()))
469+
else if (FunctionProtoTypeLoc Loc =
470+
Resolver->getFunctionProtoTypeLoc(E->getCallee()))
518471
Callee.Loc = Loc;
519472
else
520473
return true;

clang/include/clang/Sema/HeuristicResolver.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class CXXBasePath;
2020
class CXXDependentScopeMemberExpr;
2121
class DeclarationName;
2222
class DependentScopeDeclRefExpr;
23+
class FunctionProtoTypeLoc;
2324
class NamedDecl;
2425
class Type;
2526
class UnresolvedUsingValueDecl;
@@ -93,6 +94,12 @@ class HeuristicResolver {
9394
// during simplification, and the operation fails if no pointer type is found.
9495
QualType simplifyType(QualType Type, const Expr *E, bool UnwrapPointer);
9596

97+
// Given an expression `Fn` representing the callee in a function call,
98+
// if the call is through a function pointer, try to find the declaration of
99+
// the corresponding function pointer type, so that we can recover argument
100+
// names from it.
101+
FunctionProtoTypeLoc getFunctionProtoTypeLoc(const Expr *Fn) const;
102+
96103
private:
97104
ASTContext &Ctx;
98105
};

clang/lib/Sema/HeuristicResolver.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "clang/AST/ExprCXX.h"
1414
#include "clang/AST/TemplateBase.h"
1515
#include "clang/AST/Type.h"
16+
#include "llvm/ADT/identity.h"
1617

1718
namespace clang {
1819

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

5456
private:
5557
ASTContext &Ctx;
@@ -506,6 +508,56 @@ std::vector<const NamedDecl *> HeuristicResolverImpl::resolveDependentMember(
506508
}
507509
return {};
508510
}
511+
512+
FunctionProtoTypeLoc
513+
HeuristicResolverImpl::getFunctionProtoTypeLoc(const Expr *Fn) {
514+
TypeLoc Target;
515+
const Expr *NakedFn = Fn->IgnoreParenCasts();
516+
if (const auto *T = NakedFn->getType().getTypePtr()->getAs<TypedefType>()) {
517+
Target = T->getDecl()->getTypeSourceInfo()->getTypeLoc();
518+
} else if (const auto *DR = dyn_cast<DeclRefExpr>(NakedFn)) {
519+
const auto *D = DR->getDecl();
520+
if (const auto *const VD = dyn_cast<VarDecl>(D)) {
521+
Target = VD->getTypeSourceInfo()->getTypeLoc();
522+
}
523+
} else if (const auto *ME = dyn_cast<MemberExpr>(NakedFn)) {
524+
const auto *MD = ME->getMemberDecl();
525+
if (const auto *FD = dyn_cast<FieldDecl>(MD)) {
526+
Target = FD->getTypeSourceInfo()->getTypeLoc();
527+
}
528+
}
529+
530+
if (!Target)
531+
return {};
532+
533+
// Unwrap types that may be wrapping the function type
534+
while (true) {
535+
if (auto P = Target.getAs<PointerTypeLoc>()) {
536+
Target = P.getPointeeLoc();
537+
continue;
538+
}
539+
if (auto A = Target.getAs<AttributedTypeLoc>()) {
540+
Target = A.getModifiedLoc();
541+
continue;
542+
}
543+
if (auto P = Target.getAs<ParenTypeLoc>()) {
544+
Target = P.getInnerLoc();
545+
continue;
546+
}
547+
break;
548+
}
549+
550+
if (auto F = Target.getAs<FunctionProtoTypeLoc>()) {
551+
// In some edge cases the AST can contain a "trivial" FunctionProtoTypeLoc
552+
// which has null parameters. Avoid these as they don't contain useful
553+
// information.
554+
if (llvm::all_of(F.getParams(), llvm::identity<ParmVarDecl *>()))
555+
return F;
556+
}
557+
558+
return {};
559+
}
560+
509561
} // namespace
510562

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

612+
FunctionProtoTypeLoc
613+
HeuristicResolver::getFunctionProtoTypeLoc(const Expr *Fn) const {
614+
return HeuristicResolverImpl(Ctx).getFunctionProtoTypeLoc(Fn);
615+
}
616+
560617
} // namespace clang

clang/lib/Sema/SemaCodeComplete.cpp

Lines changed: 1 addition & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6283,54 +6283,6 @@ ProduceSignatureHelp(Sema &SemaRef, MutableArrayRef<ResultCandidate> Candidates,
62836283
return getParamType(SemaRef, Candidates, CurrentArg);
62846284
}
62856285

6286-
// Given a callee expression `Fn`, if the call is through a function pointer,
6287-
// try to find the declaration of the corresponding function pointer type,
6288-
// so that we can recover argument names from it.
6289-
static FunctionProtoTypeLoc GetPrototypeLoc(Expr *Fn) {
6290-
TypeLoc Target;
6291-
6292-
if (const auto *T = Fn->getType().getTypePtr()->getAs<TypedefType>()) {
6293-
Target = T->getDecl()->getTypeSourceInfo()->getTypeLoc();
6294-
6295-
} else if (const auto *DR = dyn_cast<DeclRefExpr>(Fn)) {
6296-
const auto *D = DR->getDecl();
6297-
if (const auto *const VD = dyn_cast<VarDecl>(D)) {
6298-
Target = VD->getTypeSourceInfo()->getTypeLoc();
6299-
}
6300-
} else if (const auto *ME = dyn_cast<MemberExpr>(Fn)) {
6301-
const auto *MD = ME->getMemberDecl();
6302-
if (const auto *FD = dyn_cast<FieldDecl>(MD)) {
6303-
Target = FD->getTypeSourceInfo()->getTypeLoc();
6304-
}
6305-
}
6306-
6307-
if (!Target)
6308-
return {};
6309-
6310-
// Unwrap types that may be wrapping the function type
6311-
while (true) {
6312-
if (auto P = Target.getAs<PointerTypeLoc>()) {
6313-
Target = P.getPointeeLoc();
6314-
continue;
6315-
}
6316-
if (auto A = Target.getAs<AttributedTypeLoc>()) {
6317-
Target = A.getModifiedLoc();
6318-
continue;
6319-
}
6320-
if (auto P = Target.getAs<ParenTypeLoc>()) {
6321-
Target = P.getInnerLoc();
6322-
continue;
6323-
}
6324-
break;
6325-
}
6326-
6327-
if (auto F = Target.getAs<FunctionProtoTypeLoc>()) {
6328-
return F;
6329-
}
6330-
6331-
return {};
6332-
}
6333-
63346286
QualType
63356287
SemaCodeCompletion::ProduceCallSignatureHelp(Expr *Fn, ArrayRef<Expr *> Args,
63366288
SourceLocation OpenParLoc) {
@@ -6419,7 +6371,7 @@ SemaCodeCompletion::ProduceCallSignatureHelp(Expr *Fn, ArrayRef<Expr *> Args,
64196371
// Lastly we check whether expression's type is function pointer or
64206372
// function.
64216373

6422-
FunctionProtoTypeLoc P = GetPrototypeLoc(NakedFn);
6374+
FunctionProtoTypeLoc P = Resolver.getFunctionProtoTypeLoc(NakedFn);
64236375
QualType T = NakedFn->getType();
64246376
if (!T->getPointeeType().isNull())
64256377
T = T->getPointeeType();

clang/unittests/Sema/HeuristicResolverTest.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,5 +766,85 @@ TEST(HeuristicResolver, UsingValueDecl) {
766766
cxxMethodDecl(hasName("waldo")).bind("output"));
767767
}
768768

769+
// `arg` is a ParamVarDecl*, `Expected` is a string
770+
MATCHER_P(ParamNameMatcher, Expected, "paramNameMatcher") {
771+
EXPECT_TRUE(arg);
772+
if (IdentifierInfo *Ident = arg->getDeclName().getAsIdentifierInfo()) {
773+
return Ident->getName() == Expected;
774+
}
775+
return false;
776+
}
777+
778+
// Helper function for testing HeuristicResolver::getProtoTypeLoc.
779+
// Takes a matcher that selects a callee expression bound to the ID "input",
780+
// calls getProtoTypeLoc() on it, and checks that the call found a
781+
// FunctionProtoTypeLoc encoding the given parameter names.
782+
template <typename InputMatcher, typename... ParameterNames>
783+
void expectParameterNames(ASTContext &Ctx, const InputMatcher &IM,
784+
ParameterNames... ExpectedParameterNames) {
785+
auto InputMatches = match(IM, Ctx);
786+
ASSERT_EQ(1u, InputMatches.size());
787+
const auto *Input = InputMatches[0].template getNodeAs<Expr>("input");
788+
ASSERT_TRUE(Input);
789+
790+
HeuristicResolver H(Ctx);
791+
auto Loc = H.getFunctionProtoTypeLoc(Input);
792+
ASSERT_TRUE(Loc);
793+
EXPECT_THAT(Loc.getParams(),
794+
ElementsAre(ParamNameMatcher(ExpectedParameterNames)...));
795+
}
796+
797+
TEST(HeuristicResolver, ProtoTypeLoc) {
798+
std::string Code = R"cpp(
799+
void (*f1)(int param1);
800+
void (__stdcall *f2)(int param2);
801+
using f3_t = void(*)(int param3);
802+
f3_t f3;
803+
using f4_t = void(__stdcall *)(int param4);
804+
f4_t f4;
805+
struct S {
806+
void (*f5)(int param5);
807+
using f6_t = void(*)(int param6);
808+
f6_t f6;
809+
};
810+
void bar() {
811+
f1(42);
812+
f2(42);
813+
f3(42);
814+
f4(42);
815+
S s;
816+
s.f5(42);
817+
s.f6(42);
818+
}
819+
)cpp";
820+
auto TU = tooling::buildASTFromCodeWithArgs(Code, {"-std=c++20"});
821+
auto &Ctx = TU->getASTContext();
822+
auto checkFreeFunction = [&](llvm::StringRef FunctionName,
823+
llvm::StringRef ParamName) {
824+
expectParameterNames(
825+
Ctx,
826+
callExpr(
827+
callee(implicitCastExpr(hasSourceExpression(declRefExpr(
828+
to(namedDecl(hasName(FunctionName))))))
829+
.bind("input"))),
830+
ParamName);
831+
};
832+
checkFreeFunction("f1", "param1");
833+
checkFreeFunction("f2", "param2");
834+
checkFreeFunction("f3", "param3");
835+
checkFreeFunction("f4", "param4");
836+
auto checkMemberFunction = [&](llvm::StringRef MemberName,
837+
llvm::StringRef ParamName) {
838+
expectParameterNames(
839+
Ctx,
840+
callExpr(callee(implicitCastExpr(hasSourceExpression(memberExpr(
841+
member(hasName(MemberName)))))
842+
.bind("input"))),
843+
ParamName);
844+
};
845+
checkMemberFunction("f5", "param5");
846+
checkMemberFunction("f6", "param6");
847+
}
848+
769849
} // namespace
770850
} // namespace clang

0 commit comments

Comments
 (0)