diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -58,6 +58,10 @@ return S; } +// Forward declaration, needed as this function is mutually recursive +// with some of the other helpers below. +std::vector resolveDependentExprToDecls(const Expr *E); + // Helper function for getMembersReferencedViaDependentName() // which takes a possibly-dependent type `T` and heuristically // resolves it to a CXXRecordDecl in which we can try name lookup. @@ -79,6 +83,19 @@ return TD->getTemplatedDecl(); } +// Try to heuristically resolve the type of a dependent expression `E`. +const Type *resolveDependentExprToType(const Expr *E) { + std::vector Decls = resolveDependentExprToDecls(E); + if (Decls.size() != 1) // Names an overload set -- just bail. + return nullptr; + if (const auto *TD = dyn_cast(Decls[0])) { + return TD->getTypeForDecl(); + } else if (const auto *VD = dyn_cast(Decls[0])) { + return VD->getType().getTypePtrOrNull(); + } + return nullptr; +} + // Given a dependent type and a member name, heuristically resolve the // name to one or more declarations. // The current heuristic is simply to look up the name in the primary @@ -92,12 +109,27 @@ // an ASTContext, because an ASTContext may be needed to obtain the // name (e.g. if it's an operator name), but the caller may not have // access to an ASTContext. +// Optionally, in cases where the type represents the type of a +// dependent expression, the expression `E` in question can be +// provided, which allows us to provide richer heuristics by +// introspecting the expression and trying to reason about its type. std::vector getMembersReferencedViaDependentName( - const Type *T, + Expr *E, const Type *T, llvm::function_ref NameFactory, bool IsNonstaticMember) { if (!T) return {}; + if (auto *BT = T->getAs()) { + // If T is the type of a dependent expression, it's just represented + // as BultinType::Dependent which gives us no information. If the + // caller provides the expression `E`, we can get further by + // analyzing it. + if (E && BT->getKind() == BuiltinType::Dependent) { + T = resolveDependentExprToType(E); + if (!T) + return {}; + } + } if (auto *ET = T->getAs()) { auto Result = ET->getDecl()->lookup(NameFactory(ET->getDecl()->getASTContext())); @@ -132,7 +164,7 @@ // Look up operator-> in the primary template. If we find one, it's probably a // smart pointer type. auto ArrowOps = getMembersReferencedViaDependentName( - T, + nullptr, T, [](ASTContext &Ctx) { return Ctx.DeclarationNames.getCXXOperatorName(OO_Arrow); }, @@ -167,14 +199,15 @@ if (ME->isArrow()) { BaseType = getPointeeType(BaseType); } + Expr *Base = ME->isImplicitAccess() ? nullptr : ME->getBase(); return getMembersReferencedViaDependentName( - BaseType, [ME](ASTContext &) { return ME->getMember(); }, + Base, BaseType, [ME](ASTContext &) { return ME->getMember(); }, /*IsNonstaticMember=*/true); } case Stmt::DependentScopeDeclRefExprClass: { const auto *RE = llvm::cast(E); return getMembersReferencedViaDependentName( - RE->getQualifier()->getAsType(), + nullptr, RE->getQualifier()->getAsType(), [RE](ASTContext &) { return RE->getDeclName(); }, /*IsNonstaticMember=*/false); } diff --git a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp --- a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp @@ -548,6 +548,23 @@ 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 + struct B { + A a; + void bar() { + this->a.[[foo]](); + } + }; + )cpp"; + EXPECT_DECLS("CXXDependentScopeMemberExpr", "void foo()"); +} + TEST_F(TargetDeclTest, ObjC) { Flags = {"-xobjective-c"}; Code = R"cpp(