[clang][Sema] Unify getPrototypeLoc helpers in SemaCodeComplete and clangd (#143345)
HeuristicResolver houses the unified implementation. Fixes https://github.com/llvm/llvm-project/issues/143240
This commit is contained in:
parent
0a067dc107
commit
e64289baa0
@ -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 @@ public:
|
||||
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;
|
||||
|
@ -20,6 +20,7 @@ class CXXBasePath;
|
||||
class CXXDependentScopeMemberExpr;
|
||||
class DeclarationName;
|
||||
class DependentScopeDeclRefExpr;
|
||||
class FunctionProtoTypeLoc;
|
||||
class NamedDecl;
|
||||
class Type;
|
||||
class UnresolvedUsingValueDecl;
|
||||
@ -93,6 +94,12 @@ public:
|
||||
// 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;
|
||||
};
|
||||
|
@ -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 @@ public:
|
||||
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
|
||||
|
@ -6317,54 +6317,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) {
|
||||
@ -6453,7 +6405,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();
|
||||
|
@ -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…
x
Reference in New Issue
Block a user