[clangd] Improve heuristic resolution of dependent types in TargetFinder

* Try to apply heuristic resolution recursively to the base
   expression of a CXXDependentScopeMemberExpr.

 * Try to apply heuristic resolution recursively to the callee
   expression in a call expression.

Fixes https://github.com/clangd/clangd/issues/441

Subscribers: ilya-biryukov, jkorous, arphaman, kadircet, usaxena95, cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D82739
This commit is contained in:
Nathan Ridge 2020-07-08 02:51:34 -04:00
parent 8953376478
commit 9946dcd3e9
2 changed files with 370 additions and 252 deletions

View File

@ -59,24 +59,32 @@ nodeToString(const ast_type_traits::DynTypedNode &N) {
} }
// Helper function for getMembersReferencedViaDependentName() // Helper function for getMembersReferencedViaDependentName()
// which takes a dependent type `T` and heuristically // which takes a possibly-dependent type `T` and heuristically
// resolves it to a CXXRecordDecl in which we can try name lookup. // resolves it to a CXXRecordDecl in which we can try name lookup.
CXXRecordDecl *resolveTypeToRecordDecl(const Type *T) { CXXRecordDecl *resolveTypeToRecordDecl(const Type *T) {
assert(T); assert(T);
if (const auto *ICNT = T->getAs<InjectedClassNameType>()) {
if (const auto *RT = T->getAs<RecordType>())
return dyn_cast<CXXRecordDecl>(RT->getDecl());
if (const auto *ICNT = T->getAs<InjectedClassNameType>())
T = ICNT->getInjectedSpecializationType().getTypePtrOrNull(); T = ICNT->getInjectedSpecializationType().getTypePtrOrNull();
} if (!T)
return nullptr;
const auto *TST = T->getAs<TemplateSpecializationType>(); const auto *TST = T->getAs<TemplateSpecializationType>();
if (!TST) if (!TST)
return nullptr; return nullptr;
const ClassTemplateDecl *TD = dyn_cast_or_null<ClassTemplateDecl>( const ClassTemplateDecl *TD = dyn_cast_or_null<ClassTemplateDecl>(
TST->getTemplateName().getAsTemplateDecl()); TST->getTemplateName().getAsTemplateDecl());
if (!TD) if (!TD)
return nullptr; return nullptr;
return TD->getTemplatedDecl(); return TD->getTemplatedDecl();
} }
// Given a dependent type and a member name, heuristically resolve the // Given a tag-decl type and a member name, heuristically resolve the
// name to one or more declarations. // name to one or more declarations.
// The current heuristic is simply to look up the name in the primary // The current heuristic is simply to look up the name in the primary
// template. This is a heuristic because the template could potentially // template. This is a heuristic because the template could potentially
@ -154,6 +162,10 @@ const Type *getPointeeType(const Type *T) {
return FirstArg.getAsType().getTypePtrOrNull(); return FirstArg.getAsType().getTypePtrOrNull();
} }
// Forward declaration, needed as this function is mutually recursive
// with resolveDependentExprToDecls.
const Type *resolveDependentExprToType(const Expr *E);
// Try to heuristically resolve a dependent expression `E` to one // Try to heuristically resolve a dependent expression `E` to one
// or more declarations that it likely references. // or more declarations that it likely references.
std::vector<const NamedDecl *> resolveDependentExprToDecls(const Expr *E) { std::vector<const NamedDecl *> resolveDependentExprToDecls(const Expr *E) {
@ -163,6 +175,15 @@ std::vector<const NamedDecl *> resolveDependentExprToDecls(const Expr *E) {
if (ME->isArrow()) { if (ME->isArrow()) {
BaseType = getPointeeType(BaseType); BaseType = getPointeeType(BaseType);
} }
if (const auto *BT = BaseType->getAs<BuiltinType>()) {
// If BaseType is the type of a dependent expression, it's just
// represented as BultinType::Dependent which gives us no information. We
// can get further by analyzing the depedent expression.
Expr *Base = ME->isImplicitAccess() ? nullptr : ME->getBase();
if (Base && BT->getKind() == BuiltinType::Dependent) {
BaseType = resolveDependentExprToType(Base);
}
}
return getMembersReferencedViaDependentName( return getMembersReferencedViaDependentName(
BaseType, [ME](ASTContext &) { return ME->getMember(); }, BaseType, [ME](ASTContext &) { return ME->getMember(); },
/*IsNonstaticMember=*/true); /*IsNonstaticMember=*/true);
@ -173,7 +194,36 @@ std::vector<const NamedDecl *> resolveDependentExprToDecls(const Expr *E) {
[RE](ASTContext &) { return RE->getDeclName(); }, [RE](ASTContext &) { return RE->getDeclName(); },
/*IsNonstaticMember=*/false); /*IsNonstaticMember=*/false);
} }
if (const auto *CE = dyn_cast<CallExpr>(E)) {
const auto *CalleeType = resolveDependentExprToType(CE->getCallee());
if (!CalleeType)
return {}; return {};
if (const auto *FnTypePtr = CalleeType->getAs<PointerType>())
CalleeType = FnTypePtr->getPointeeType().getTypePtr();
if (const FunctionType *FnType = CalleeType->getAs<FunctionType>()) {
if (const auto *D =
resolveTypeToRecordDecl(FnType->getReturnType().getTypePtr())) {
return {D};
}
}
}
if (const auto *ME = dyn_cast<MemberExpr>(E)) {
return {ME->getMemberDecl()};
}
return {};
}
// Try to heuristically resolve the type of a dependent expression `E`.
const Type *resolveDependentExprToType(const Expr *E) {
std::vector<const NamedDecl *> Decls = resolveDependentExprToDecls(E);
if (Decls.size() != 1) // Names an overload set -- just bail.
return nullptr;
if (const auto *TD = dyn_cast<TypeDecl>(Decls[0])) {
return TD->getTypeForDecl();
} else if (const auto *VD = dyn_cast<ValueDecl>(Decls[0])) {
return VD->getType().getTypePtrOrNull();
}
return nullptr;
} }
const NamedDecl *getTemplatePattern(const NamedDecl *D) { const NamedDecl *getTemplatePattern(const NamedDecl *D) {
@ -235,9 +285,8 @@ const NamedDecl *getTemplatePattern(const NamedDecl *D) {
// and both are lossy. We must know upfront what the caller ultimately wants. // and both are lossy. We must know upfront what the caller ultimately wants.
// //
// FIXME: improve common dependent scope using name lookup in primary templates. // FIXME: improve common dependent scope using name lookup in primary templates.
// e.g. template<typename T> int foo() { return std::vector<T>().size(); } // We currently handle DependentScopeDeclRefExpr and
// formally size() is unresolved, but the primary template is a good guess. // CXXDependentScopeMemberExpr, but some other constructs remain to be handled:
// This affects:
// - DependentTemplateSpecializationType, // - DependentTemplateSpecializationType,
// - DependentNameType // - DependentNameType
// - UnresolvedUsingValueDecl // - UnresolvedUsingValueDecl

View File

@ -561,6 +561,74 @@ TEST_F(TargetDeclTest, OverloadExpr) {
EXPECT_DECLS("UnresolvedMemberExpr", "void func(int *)", "void func(char *)"); EXPECT_DECLS("UnresolvedMemberExpr", "void func(int *)", "void func(char *)");
} }
TEST_F(TargetDeclTest, DependentExprs) {
Flags = {"-fno-delayed-template-parsing"};
// Heuristic resolution of method of dependent field
Code = R"cpp(
struct A { void foo() {} };
template <typename T>
struct B {
A a;
void bar() {
this->a.[[foo]]();
}
};
)cpp";
EXPECT_DECLS("CXXDependentScopeMemberExpr", "void foo()");
// Similar to above but base expression involves a function call.
Code = R"cpp(
struct A {
void foo() {}
};
struct B {
A getA();
};
template <typename T>
struct C {
B c;
void bar() {
this->c.getA().[[foo]]();
}
};
)cpp";
EXPECT_DECLS("CXXDependentScopeMemberExpr", "void foo()");
// Similar to above but uses a function pointer.
Code = R"cpp(
struct A {
void foo() {}
};
struct B {
using FPtr = A(*)();
FPtr fptr;
};
template <typename T>
struct C {
B c;
void bar() {
this->c.fptr().[[foo]]();
}
};
)cpp";
EXPECT_DECLS("CXXDependentScopeMemberExpr", "void foo()");
// Base expression involves a member access into this.
Code = R"cpp(
struct Bar {
int aaaa;
};
template <typename T> struct Foo {
Bar func(int);
void test() {
func(1).[[aaaa]];
}
};
)cpp";
EXPECT_DECLS("CXXDependentScopeMemberExpr", "int aaaa");
}
TEST_F(TargetDeclTest, ObjC) { TEST_F(TargetDeclTest, ObjC) {
Flags = {"-xobjective-c"}; Flags = {"-xobjective-c"};
Code = R"cpp( Code = R"cpp(
@ -718,7 +786,8 @@ protected:
TEST_F(FindExplicitReferencesTest, All) { TEST_F(FindExplicitReferencesTest, All) {
std::pair</*Code*/ llvm::StringRef, /*References*/ llvm::StringRef> Cases[] = std::pair</*Code*/ llvm::StringRef, /*References*/ llvm::StringRef> Cases[] =
{// Simple expressions. {
// Simple expressions.
{R"cpp( {R"cpp(
int global; int global;
int func(); int func();