Skip to content

[clang][Sema] Respect const-qualification of methods in heuristic results #123551

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 2 commits into from
Jan 21, 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
3 changes: 1 addition & 2 deletions clang-tools-extra/clangd/FindTarget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -496,8 +496,7 @@ struct TargetFinder {
return;
case NestedNameSpecifier::Identifier:
if (Resolver) {
add(QualType(Resolver->resolveNestedNameSpecifierToType(NNS), 0),
Flags);
add(Resolver->resolveNestedNameSpecifierToType(NNS), Flags);
}
return;
case NestedNameSpecifier::TypeSpec:
Expand Down
7 changes: 4 additions & 3 deletions clang-tools-extra/clangd/XRefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2034,9 +2034,10 @@ static void unwrapFindType(

// For smart pointer types, add the underlying type
if (H)
if (const auto* PointeeType = H->getPointeeType(T.getNonReferenceType().getTypePtr())) {
unwrapFindType(QualType(PointeeType, 0), H, Out);
return Out.push_back(T);
if (auto PointeeType = H->getPointeeType(T.getNonReferenceType());
!PointeeType.isNull()) {
unwrapFindType(PointeeType, H, Out);
return Out.push_back(T);
}

return Out.push_back(T);
Expand Down
4 changes: 2 additions & 2 deletions clang/include/clang/Sema/HeuristicResolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ class HeuristicResolver {
// Try to heuristically resolve a dependent nested name specifier
// to the type it likely denotes. Note that *dependent* name specifiers always
// denote types, not namespaces.
const Type *
QualType
resolveNestedNameSpecifierToType(const NestedNameSpecifier *NNS) const;

// Given the type T of a dependent expression that appears of the LHS of a
// "->", heuristically find a corresponding pointee type in whose scope we
// could look up the name appearing on the RHS.
const Type *getPointeeType(const Type *T) const;
const QualType getPointeeType(QualType T) const;

private:
ASTContext &Ctx;
Expand Down
87 changes: 49 additions & 38 deletions clang/lib/Sema/HeuristicResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ class HeuristicResolverImpl {
resolveDependentNameType(const DependentNameType *DNT);
std::vector<const NamedDecl *> resolveTemplateSpecializationType(
const DependentTemplateSpecializationType *DTST);
const Type *resolveNestedNameSpecifierToType(const NestedNameSpecifier *NNS);
const Type *getPointeeType(const Type *T);
QualType resolveNestedNameSpecifierToType(const NestedNameSpecifier *NNS);
QualType getPointeeType(QualType T);

private:
ASTContext &Ctx;
Expand All @@ -61,12 +61,12 @@ class HeuristicResolverImpl {
// This heuristic will give the desired answer in many cases, e.g.
// for a call to vector<T>::size().
std::vector<const NamedDecl *>
resolveDependentMember(const Type *T, DeclarationName Name,
resolveDependentMember(QualType T, DeclarationName Name,
llvm::function_ref<bool(const NamedDecl *ND)> Filter);

// Try to heuristically resolve the type of a possibly-dependent expression
// `E`.
const Type *resolveExprToType(const Expr *E);
QualType resolveExprToType(const Expr *E);
std::vector<const NamedDecl *> resolveExprToDecls(const Expr *E);

// Helper function for HeuristicResolver::resolveDependentMember()
Expand Down Expand Up @@ -104,17 +104,17 @@ const auto TemplateFilter = [](const NamedDecl *D) {
return isa<TemplateDecl>(D);
};

const Type *resolveDeclsToType(const std::vector<const NamedDecl *> &Decls,
ASTContext &Ctx) {
QualType resolveDeclsToType(const std::vector<const NamedDecl *> &Decls,
ASTContext &Ctx) {
if (Decls.size() != 1) // Names an overload set -- just bail.
return nullptr;
return QualType();
if (const auto *TD = dyn_cast<TypeDecl>(Decls[0])) {
return Ctx.getTypeDeclType(TD).getTypePtr();
return Ctx.getTypeDeclType(TD);
}
if (const auto *VD = dyn_cast<ValueDecl>(Decls[0])) {
return VD->getType().getTypePtrOrNull();
return VD->getType();
}
return nullptr;
return QualType();
}

TemplateName getReferencedTemplateName(const Type *T) {
Expand All @@ -137,7 +137,8 @@ CXXRecordDecl *HeuristicResolverImpl::resolveTypeToRecordDecl(const Type *T) {
T = T->getCanonicalTypeInternal().getTypePtr();

if (const auto *DNT = T->getAs<DependentNameType>()) {
T = resolveDeclsToType(resolveDependentNameType(DNT), Ctx);
T = resolveDeclsToType(resolveDependentNameType(DNT), Ctx)
.getTypePtrOrNull();
if (!T)
return nullptr;
T = T->getCanonicalTypeInternal().getTypePtr();
Expand All @@ -163,12 +164,12 @@ CXXRecordDecl *HeuristicResolverImpl::resolveTypeToRecordDecl(const Type *T) {
return TD->getTemplatedDecl();
}

const Type *HeuristicResolverImpl::getPointeeType(const Type *T) {
if (!T)
return nullptr;
QualType HeuristicResolverImpl::getPointeeType(QualType T) {
if (T.isNull())
return QualType();

if (T->isPointerType())
return T->castAs<PointerType>()->getPointeeType().getTypePtrOrNull();
return T->castAs<PointerType>()->getPointeeType();

// Try to handle smart pointer types.

Expand All @@ -177,7 +178,7 @@ const Type *HeuristicResolverImpl::getPointeeType(const Type *T) {
auto ArrowOps = resolveDependentMember(
T, Ctx.DeclarationNames.getCXXOperatorName(OO_Arrow), NonStaticFilter);
if (ArrowOps.empty())
return nullptr;
return QualType();

// Getting the return type of the found operator-> method decl isn't useful,
// because we discarded template arguments to perform lookup in the primary
Expand All @@ -187,13 +188,13 @@ const Type *HeuristicResolverImpl::getPointeeType(const Type *T) {
// form of SmartPtr<X, ...>, and assume X is the pointee type.
auto *TST = T->getAs<TemplateSpecializationType>();
if (!TST)
return nullptr;
return QualType();
if (TST->template_arguments().size() == 0)
return nullptr;
return QualType();
const TemplateArgument &FirstArg = TST->template_arguments()[0];
if (FirstArg.getKind() != TemplateArgument::Type)
return nullptr;
return FirstArg.getAsType().getTypePtrOrNull();
return QualType();
return FirstArg.getAsType();
}

std::vector<const NamedDecl *> HeuristicResolverImpl::resolveMemberExpr(
Expand All @@ -210,7 +211,8 @@ std::vector<const NamedDecl *> HeuristicResolverImpl::resolveMemberExpr(
// with `this` as the base expression as `X` as the qualifier
// (which could be valid if `X` names a base class after instantiation).
if (NestedNameSpecifier *NNS = ME->getQualifier()) {
if (const Type *QualifierType = resolveNestedNameSpecifierToType(NNS)) {
if (QualType QualifierType = resolveNestedNameSpecifierToType(NNS);
!QualifierType.isNull()) {
auto Decls =
resolveDependentMember(QualifierType, ME->getMember(), NoFilter);
if (!Decls.empty())
Expand All @@ -225,11 +227,11 @@ std::vector<const NamedDecl *> HeuristicResolverImpl::resolveMemberExpr(
}

// Try resolving the member inside the expression's base type.
const Type *BaseType = ME->getBaseType().getTypePtrOrNull();
QualType BaseType = ME->getBaseType();
if (ME->isArrow()) {
BaseType = getPointeeType(BaseType);
}
if (!BaseType)
if (BaseType.isNull())
return {};
if (const auto *BT = BaseType->getAs<BuiltinType>()) {
// If BaseType is the type of a dependent expression, it's just
Expand All @@ -245,17 +247,17 @@ std::vector<const NamedDecl *> HeuristicResolverImpl::resolveMemberExpr(

std::vector<const NamedDecl *>
HeuristicResolverImpl::resolveDeclRefExpr(const DependentScopeDeclRefExpr *RE) {
return resolveDependentMember(RE->getQualifier()->getAsType(),
return resolveDependentMember(QualType(RE->getQualifier()->getAsType(), 0),
RE->getDeclName(), StaticFilter);
}

std::vector<const NamedDecl *>
HeuristicResolverImpl::resolveTypeOfCallExpr(const CallExpr *CE) {
const auto *CalleeType = resolveExprToType(CE->getCallee());
if (!CalleeType)
QualType CalleeType = resolveExprToType(CE->getCallee());
if (CalleeType.isNull())
return {};
if (const auto *FnTypePtr = CalleeType->getAs<PointerType>())
CalleeType = FnTypePtr->getPointeeType().getTypePtr();
CalleeType = FnTypePtr->getPointeeType();
if (const FunctionType *FnType = CalleeType->getAs<FunctionType>()) {
if (const auto *D =
resolveTypeToRecordDecl(FnType->getReturnType().getTypePtr())) {
Expand All @@ -276,7 +278,7 @@ HeuristicResolverImpl::resolveCalleeOfCallExpr(const CallExpr *CE) {

std::vector<const NamedDecl *> HeuristicResolverImpl::resolveUsingValueDecl(
const UnresolvedUsingValueDecl *UUVD) {
return resolveDependentMember(UUVD->getQualifier()->getAsType(),
return resolveDependentMember(QualType(UUVD->getQualifier()->getAsType(), 0),
UUVD->getNameInfo().getName(), ValueFilter);
}

Expand Down Expand Up @@ -317,18 +319,18 @@ HeuristicResolverImpl::resolveExprToDecls(const Expr *E) {
return {};
}

const Type *HeuristicResolverImpl::resolveExprToType(const Expr *E) {
QualType HeuristicResolverImpl::resolveExprToType(const Expr *E) {
std::vector<const NamedDecl *> Decls = resolveExprToDecls(E);
if (!Decls.empty())
return resolveDeclsToType(Decls, Ctx);

return E->getType().getTypePtr();
return E->getType();
}

const Type *HeuristicResolverImpl::resolveNestedNameSpecifierToType(
QualType HeuristicResolverImpl::resolveNestedNameSpecifierToType(
const NestedNameSpecifier *NNS) {
if (!NNS)
return nullptr;
return QualType();

// The purpose of this function is to handle the dependent (Kind ==
// Identifier) case, but we need to recurse on the prefix because
Expand All @@ -337,7 +339,7 @@ const Type *HeuristicResolverImpl::resolveNestedNameSpecifierToType(
switch (NNS->getKind()) {
case NestedNameSpecifier::TypeSpec:
case NestedNameSpecifier::TypeSpecWithTemplate:
return NNS->getAsType();
return QualType(NNS->getAsType(), 0);
case NestedNameSpecifier::Identifier: {
return resolveDeclsToType(
resolveDependentMember(
Expand All @@ -348,7 +350,7 @@ const Type *HeuristicResolverImpl::resolveNestedNameSpecifierToType(
default:
break;
}
return nullptr;
return QualType();
}

bool isOrdinaryMember(const NamedDecl *ND) {
Expand Down Expand Up @@ -410,8 +412,9 @@ std::vector<const NamedDecl *> HeuristicResolverImpl::lookupDependentName(
}

std::vector<const NamedDecl *> HeuristicResolverImpl::resolveDependentMember(
const Type *T, DeclarationName Name,
QualType QT, DeclarationName Name,
llvm::function_ref<bool(const NamedDecl *ND)> Filter) {
const Type *T = QT.getTypePtrOrNull();
if (!T)
return {};
if (auto *ET = T->getAs<EnumType>()) {
Expand All @@ -422,7 +425,15 @@ std::vector<const NamedDecl *> HeuristicResolverImpl::resolveDependentMember(
if (!RD->hasDefinition())
return {};
RD = RD->getDefinition();
return lookupDependentName(RD, Name, Filter);
return lookupDependentName(RD, Name, [&](const NamedDecl *ND) {
if (!Filter(ND))
return false;
if (const auto *MD = dyn_cast<CXXMethodDecl>(ND)) {
return MD->getMethodQualifiers().compatiblyIncludes(QT.getQualifiers(),
Ctx);
}
return true;
});
}
return {};
}
Expand Down Expand Up @@ -457,11 +468,11 @@ HeuristicResolver::resolveTemplateSpecializationType(
const DependentTemplateSpecializationType *DTST) const {
return HeuristicResolverImpl(Ctx).resolveTemplateSpecializationType(DTST);
}
const Type *HeuristicResolver::resolveNestedNameSpecifierToType(
QualType HeuristicResolver::resolveNestedNameSpecifierToType(
const NestedNameSpecifier *NNS) const {
return HeuristicResolverImpl(Ctx).resolveNestedNameSpecifierToType(NNS);
}
const Type *HeuristicResolver::getPointeeType(const Type *T) const {
const QualType HeuristicResolver::getPointeeType(QualType T) const {
return HeuristicResolverImpl(Ctx).getPointeeType(T);
}

Expand Down
20 changes: 20 additions & 0 deletions clang/unittests/Sema/HeuristicResolverTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,26 @@ TEST(HeuristicResolver, MemberExpr_SmartPointer) {
cxxMethodDecl(hasName("foo")).bind("output"));
}

TEST(HeuristicResolver, MemberExpr_SmartPointer_Qualified) {
std::string Code = R"cpp(
template <typename> struct Waldo {
void find();
void find() const;
};
template <typename T> struct unique_ptr {
T* operator->();
};
template <typename T>
void test(unique_ptr<const Waldo<T>>& w) {
w->find();
}
)cpp";
expectResolution(
Code, &HeuristicResolver::resolveMemberExpr,
cxxDependentScopeMemberExpr(hasMemberName("find")).bind("input"),
cxxMethodDecl(hasName("find"), isConst()).bind("output"));
}

TEST(HeuristicResolver, MemberExpr_Chained) {
std::string Code = R"cpp(
struct A { void foo() {} };
Expand Down
Loading